├── .appcast.xml
├── .babelrc
├── .editorconfig
├── .eslintrc.yml
├── .gitignore
├── .nvmrc
├── CHANGELOG.md
├── README.md
├── docs
├── demo.gif
└── sketch-templates
│ ├── SanFranciscoTypeface.sketch
│ └── TypesettingsStarter.sketch
├── package.json
├── src
├── assets
│ └── icons
│ │ ├── characterSpacingRunner.png
│ │ ├── lineHeightRunner.png
│ │ ├── mark.png
│ │ └── registerRunner.png
├── directory
│ ├── SFCompactText.json
│ ├── SFProDisplay.json
│ └── SFProText.json
├── plugin
│ ├── Typesetter.js
│ ├── commands
│ │ ├── auto.js
│ │ ├── preferences.js
│ │ ├── register.js
│ │ └── typeset.js
│ ├── manifest.json
│ ├── storage
│ │ └── index.js
│ └── utils
│ │ ├── fonts.js
│ │ └── helpers.js
└── webview
│ ├── client.js
│ ├── components
│ ├── Button
│ │ └── index.jsx
│ ├── Checkbox
│ │ ├── index.jsx
│ │ └── styles.js
│ ├── Field
│ │ └── index.jsx
│ ├── Page
│ │ ├── Footer
│ │ │ ├── index.jsx
│ │ │ └── mark.svg
│ │ ├── Header
│ │ │ └── index.jsx
│ │ └── index.jsx
│ └── TextField
│ │ ├── index.jsx
│ │ └── styles.js
│ ├── index.html
│ ├── pages
│ ├── SettingsPage
│ │ ├── Section.jsx
│ │ └── index.jsx
│ └── index.js
│ └── style
│ ├── globals
│ └── index.js
│ └── variables
│ ├── index.js
│ └── typesettings.js
└── webpack.skpm.config.js
/.appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 | -
8 |
9 |
10 | -
11 |
12 |
13 | -
14 |
15 |
16 | -
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@emotion/babel-plugin-core",
4 | "@babel/plugin-proposal-class-properties"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
9 | [*.md]
10 | trim_trailing_whitespace = false
11 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | root: true
3 |
4 | extends:
5 | - sketch
6 | - airbnb
7 | - plugin:import/errors
8 |
9 | globals:
10 | CGColorCreateGenericRGB: true
11 | CGRectGetMaxY: true
12 | COSAlertWindow: true
13 | NSOffState: true
14 | NSOnState: true
15 | NSSwitchButton: true
16 | NSHomeDirectory: true
17 | NSStringPboardType: true
18 | document: true
19 | window: true
20 |
21 | plugins:
22 | - react
23 |
24 | parser: babel-eslint
25 |
26 | settings:
27 | import/resolver:
28 | webpack:
29 | config: 'webpack.skpm.config.js'
30 | node:
31 | paths:
32 | - src
33 | import/core-modules:
34 | - sketch/ui
35 | - sketch/dom
36 | - sketch/settings
37 |
38 | rules:
39 | import/prefer-default-export: off
40 | no-param-reassign: off
41 | no-console: off
42 | arrow-parens:
43 | - error
44 | - as-needed
45 | - requireForBlockBody: true
46 | object-curly-spacing:
47 | - error
48 | - always
49 | array-bracket-spacing:
50 | - error
51 | - always
52 | template-curly-spacing:
53 | - error
54 | - always
55 | semi:
56 | - error
57 | - never
58 | comma-dangle:
59 | - error
60 | - never
61 | keyword-spacing:
62 | - error
63 | - before: true
64 | after: true
65 | overrides:
66 | return:
67 | after: true
68 | throw:
69 | after: true
70 | case:
71 | after: true
72 | react/jsx-curly-spacing:
73 | - error
74 | - when: always
75 | attributes:
76 | allowMultiline: true
77 | children: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | dist
3 | build
4 | node_modules
5 | .npm
6 | *.log
7 | *.lock
8 | .DS_Store
9 | typesettings.sketchplugin
10 | src/directory/PostmatesStd.json
11 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.8.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v0.2.0 (2018-09-03)
2 |
3 | #### :rocket: Enhancement
4 | * [#6](https://github.com/buames/typesettings-sketch-plugin/issues/6) Plugin preferences moved to WebView
5 | * [#9](https://github.com/buames/typesettings-sketch-plugin/issues/9) Improve managing local directory paths
6 | * [#11](https://github.com/buames/typesettings-sketch-plugin/issues/11) New props add to typesettings json. Allows for use with [typesettings-js](https://github.com/buames/typesettings-js)
7 |
8 | #### :bug: Bug Fix
9 | * [#13](https://github.com/buames/typesettings-sketch-plugin/issues/13) Paragraph spacing isn't appling when setting line height
10 |
11 | #### :house: Internal
12 | * [#7](https://github.com/buames/typesettings-sketch-plugin/issues/7) Add skpm to deps
13 | * [#8](https://github.com/buames/typesettings-sketch-plugin/issues/8) Linting for webviews
14 | * [#10](https://github.com/buames/typesettings-sketch-plugin/issues/10) Move dialogs to use @skpm/dialog
15 |
16 | ## v0.1.1 (2018-07-04)
17 |
18 | #### :rocket: Enhancement
19 | * [#4](https://github.com/buames/typesettings-sketch-plugin/pull/4) Improved workflow and Sketch Runner support
20 |
21 | #### :house: Internal
22 | * [#3](https://github.com/buames/typesettings-sketch-plugin/pull/3) Add linting and changelog
23 |
24 | ## v0.1.0 (2018-06-29)
25 |
26 | #### :rocket: Enhancement
27 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Choose to Merge or Replace typesettings that exists
28 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Ignore text layers with a prefix or suffix
29 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Add ability to set ignore prefix and suffix to the plugin preferences
30 |
31 | #### :memo: Documentation
32 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Add an icon
33 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Add SF typeface sketch file to use for registering
34 |
35 | #### :house: Internal
36 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Add version checking and backwards compatibility
37 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Updated directory json to latest version of plugin
38 | * [#2](https://github.com/buames/typesettings-sketch-plugin/pull/2) Code refactor and clean up
39 |
40 | ## v0.0.2 (2018-06-24)
41 |
42 | #### :rocket: Enhancement
43 | * [#1](https://github.com/buames/typesettings-sketch-plugin/pull/1) Add San Francisco Typesettings
44 | * [#1](https://github.com/buames/typesettings-sketch-plugin/pull/1) Add SF Compact Typesettings
45 |
46 | #### :bug: Bug Fix
47 | * [#1](https://github.com/buames/typesettings-sketch-plugin/pull/1) Fix issue where fonts are added to wrong family when multple families are selected
48 |
49 | ## v0.0.1 (2018-06-24)
50 |
51 | * Initial Release
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typesettings Sketch Plugin
2 |
3 | Create typesettings once and use them anywhere. Set character spacing and line height of text layers based on the `font family`, `font weight`, `font size`, and `letter casing`. Better yet, let the plugin do it for you automatically when you allow it to from the plugin preferences.
4 |
5 | [Below](#pre-registered) you'll find typsettings that are already included with the plugin.
6 |
7 | ## Demo
8 |
9 | 
10 |
11 | Get the [TypesettingsStarter.sketch](/docs/sketch-templates/TypesettingsStarter.sketch) file to get going easily.
12 |
13 | ## Installation
14 |
15 | You can install the plugin with [Sketch Runner](https://sketchrunner.com) or by downloading it from the [latest release](https://github.com/buames/typesettings-sketch-plugin/releases/latest).
16 |
17 | ## Commands
18 |
19 | ### Set Character Spacing
20 |
21 | ```
22 | cmd + shift + right arrow
23 | ```
24 |
25 | Sets the character spacing of any selected text layer based on the font and text size. You can `enable automatic character spacing` in the plugin preferences.
26 |
27 | ### Set Line Height
28 |
29 | ```
30 | cmd + shift + down arrow
31 | ```
32 |
33 | Sets the line height of any selected text layer based on the font and text size. You can `enable automatic line height` in the plugin preferences.
34 |
35 | ### Register Typesettings
36 |
37 | ```
38 | ctrl + alt + cmd + return
39 | ```
40 |
41 | To register typesettings, you'll want to do the following:
42 |
43 | 1. Create your text layers using any size, weight, and letter casing permutation
44 | 2. Set the character spacing and line height for each
45 | 2. Select all text layers
46 | 3. Go to `Plugins` > `Typesettings` > `Register Typesettings`
47 |
48 | **Letter Casing**
49 |
50 | You can create typesettings for uppercase, lowercase (if that's a thing?), and no casing.
51 |
52 | In order to properly save the typesettings for a given letter casing, you'll need to make sure that the text layers has the desired `Text Transform` option when registering the typesettings.
53 |
54 | For example:
55 | 1. Create two text layers using the same font family, font size, and weight
56 | 2. Select one of the text layers and set the `Text Transform` option to `Uppercase` using the [Text Inspector](https://sketchapp.com/docs/text/text-inspector)
57 | 3. Set the character spacing and line height of each text layer to whatever you want
58 | 4. Select both layers and Register them
59 |
60 | ### Preferences
61 |
62 | ```
63 | ctrl + alt + cmd + t
64 | ```
65 |
66 | **Local Registry**
67 |
68 | When registering your typesettings, the `.json` file will save to your `Desktop` by default. You can change the directory (for instance, to a shared folder) from the plugin preferences.
69 |
70 | **Automatic Typesetting**
71 |
72 | - `Enable Automatic Character Spacing`: Automatically sets character spacing—if the typesetting is registered —when deselecting text layers. This is turned off by default.
73 | - `Enable Automatic Line Height`: Automatically sets line height—if the typesetting is registered —when deselecting text layers. This is turned off by default.
74 |
75 | **Ignoring Text Layers**
76 |
77 | When `automatic character spacing` and/or `automatic line height` are enabled, you can ignoring any text layer by adding the prefix `^` or the suffix `^` to text layer name. Note that if you manually typeset the text layers they will not be ignored.
78 |
79 | You can change both the prefix and suffix in the plugin preferences.
80 |
81 | ## (Pre) Registered
82 |
83 | The following typefaces have been registered and automatically included with the plugin. If you'd like to add more, put the typesettings in the `directory` folder and open a pull request.
84 |
85 | - **[SF Pro Display](https://developer.apple.com/fonts)** from iOS resource [guidelines](https://developer.apple.com/design/resources)
86 | - **[SF Pro Text](https://developer.apple.com/fonts)** from iOS resource [guidelines](https://developer.apple.com/design/resources)
87 | - **[SF Compact](https://developer.apple.com/fonts)** from watchOS resource [guidelines](https://developer.apple.com/design/resources)
88 |
89 | ## Development
90 |
91 | _This plugin was created using `skpm`. For a detailed explanation on how things work, checkout the [skpm Readme](https://github.com/skpm/skpm/blob/master/README.md)._
92 |
93 |
--------------------------------------------------------------------------------
/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/docs/demo.gif
--------------------------------------------------------------------------------
/docs/sketch-templates/SanFranciscoTypeface.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/docs/sketch-templates/SanFranciscoTypeface.sketch
--------------------------------------------------------------------------------
/docs/sketch-templates/TypesettingsStarter.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/docs/sketch-templates/TypesettingsStarter.sketch
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typesettings-sketch-plugin",
3 | "version": "0.2.0",
4 | "description": "A quest for consistency",
5 | "engines": {
6 | "sketch": ">=3.0"
7 | },
8 | "skpm": {
9 | "name": "Typesettings",
10 | "manifest": "src/plugin/manifest.json",
11 | "main": "typesettings.sketchplugin",
12 | "assets": [
13 | "src/assets/**/*",
14 | "src/directory/**/*"
15 | ],
16 | "resources": [
17 | "src/webview/**/*.js"
18 | ]
19 | },
20 | "scripts": {
21 | "build": "skpm-build",
22 | "watch": "skpm-build --watch",
23 | "start": "skpm-build --watch --run",
24 | "publish": "skpm publish",
25 | "postinstall": "npm run build && skpm-link",
26 | "precommit": "lint-staged",
27 | "lint": "yarn lint:webview && yarn lint:plugin",
28 | "lint:webview": "eslint --ext .js,.jsx src/webview",
29 | "lint:plugin": "eslint --ext .js src/plugin",
30 | "changelog": "lerna-changelog"
31 | },
32 | "lint-staged": {
33 | "*{.js,.jsx}": [
34 | "yarn lint --fix",
35 | "git add"
36 | ]
37 | },
38 | "changelog": {
39 | "labels": {
40 | "Tag: Breaking Change": ":boom: Breaking Change",
41 | "Tag: Enhancement": ":rocket: Enhancement",
42 | "Tag: Bug Fix": ":bug: Bug Fix",
43 | "Tag: Documentation": ":memo: Documentation",
44 | "Tag: Internal": ":house: Internal"
45 | }
46 | },
47 | "dependencies": {
48 | "@emotion/core": "^0.13.0",
49 | "@emotion/styled": "^0.10.5",
50 | "@skpm/dialog": "^0.2.3",
51 | "@skpm/fs": "^0.1.2",
52 | "@skpm/path": "^0.1.0",
53 | "deepmerge": "^2.1.1",
54 | "prop-types": "^15.6.2",
55 | "react": "^16.4.2",
56 | "react-dom": "^16.4.2",
57 | "react-router": "^4.3.1",
58 | "react-router-config": "^1.0.0-beta.4",
59 | "react-router-dom": "^4.3.1",
60 | "sketch-module-web-view": "^1.2.3",
61 | "svg-inline-react": "^3.1.0",
62 | "typesettings-js": "^0.0.3"
63 | },
64 | "devDependencies": {
65 | "@babel/core": "^7.0.0",
66 | "@babel/plugin-proposal-class-properties": "^7.0.0",
67 | "@emotion/babel-plugin-core": "^0.6.8",
68 | "@skpm/builder": "^0.5.2",
69 | "@skpm/extract-loader": "^2.0.2",
70 | "babel-eslint": "9.0.0",
71 | "eslint": "5.5.0",
72 | "eslint-config-airbnb": "^17.1.0",
73 | "eslint-config-sketch": "^0.2.4",
74 | "eslint-plugin-import": "^2.14.0",
75 | "eslint-plugin-jsx-a11y": "^6.1.1",
76 | "eslint-plugin-react": "^7.11.1",
77 | "html-loader": "^0.5.5",
78 | "husky": "^0.14.3",
79 | "lerna-changelog": "^0.8.0",
80 | "lint-staged": "^7.2.0",
81 | "skpm": "1.1.4",
82 | "svg-inline-loader": "^0.8.0"
83 | },
84 | "homepage": "https://github.com/buames/typesettings-sketch-plugin",
85 | "repository": {
86 | "type": "git",
87 | "url": "git+https://github.com/buames/typesettings-sketch-plugin.git"
88 | },
89 | "author": "buames ",
90 | "license": "MIT"
91 | }
92 |
--------------------------------------------------------------------------------
/src/assets/icons/characterSpacingRunner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/src/assets/icons/characterSpacingRunner.png
--------------------------------------------------------------------------------
/src/assets/icons/lineHeightRunner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/src/assets/icons/lineHeightRunner.png
--------------------------------------------------------------------------------
/src/assets/icons/mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/src/assets/icons/mark.png
--------------------------------------------------------------------------------
/src/assets/icons/registerRunner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buames/typesettings-sketch-plugin/3fd88f8a5c4317fc857c16a17592776619947382/src/assets/icons/registerRunner.png
--------------------------------------------------------------------------------
/src/directory/SFCompactText.json:
--------------------------------------------------------------------------------
1 | {
2 | "family": "SF Compact Text",
3 | "SFCompactText-Regular": {
4 | "fontFamily": "SF Compact Text",
5 | "fontName": "SFCompactText-Regular",
6 | "fontWeight": 400,
7 | "fontStyle": "normal",
8 | "sources": {
9 | "locals": [
10 | "SF Compact Text Regular",
11 | "SFCompactText-Regular"
12 | ]
13 | },
14 | "normalcase": {
15 | "11": {
16 | "characterSpacing": 0.3,
17 | "lineHeight": 13.5,
18 | "paragraphSpacing": 0
19 | },
20 | "12": {
21 | "characterSpacing": 0.2,
22 | "lineHeight": 14.5,
23 | "paragraphSpacing": 0
24 | },
25 | "13": {
26 | "characterSpacing": 0.15,
27 | "lineHeight": 15.5,
28 | "paragraphSpacing": 0
29 | },
30 | "14": {
31 | "characterSpacing": 0.25,
32 | "lineHeight": 16.5,
33 | "paragraphSpacing": 0
34 | },
35 | "15": {
36 | "characterSpacing": 0.1,
37 | "lineHeight": 17.5,
38 | "paragraphSpacing": 0
39 | },
40 | "16": {
41 | "characterSpacing": 0,
42 | "lineHeight": 18.5,
43 | "paragraphSpacing": 0
44 | },
45 | "17": {
46 | "characterSpacing": 0,
47 | "lineHeight": 19.5,
48 | "paragraphSpacing": 0
49 | },
50 | "18": {
51 | "characterSpacing": -0.05,
52 | "lineHeight": 20.5,
53 | "paragraphSpacing": 0
54 | },
55 | "19": {
56 | "characterSpacing": 0,
57 | "lineHeight": 21.5,
58 | "paragraphSpacing": 0
59 | },
60 | "20": {
61 | "characterSpacing": 0,
62 | "lineHeight": 22.5,
63 | "paragraphSpacing": 0
64 | },
65 | "21": {
66 | "characterSpacing": 0,
67 | "lineHeight": 23.5,
68 | "paragraphSpacing": 0
69 | },
70 | "22": {
71 | "characterSpacing": 0,
72 | "lineHeight": 24.5,
73 | "paragraphSpacing": 0
74 | },
75 | "24": {
76 | "characterSpacing": 0,
77 | "lineHeight": 26.5,
78 | "paragraphSpacing": 0
79 | },
80 | "26": {
81 | "characterSpacing": -0.1000000014901161,
82 | "lineHeight": 26.5,
83 | "paragraphSpacing": 0
84 | },
85 | "27": {
86 | "characterSpacing": -0.1000000014901161,
87 | "lineHeight": 26.5,
88 | "paragraphSpacing": 0
89 | },
90 | "28": {
91 | "characterSpacing": -0.2000000029802322,
92 | "lineHeight": 26.5,
93 | "paragraphSpacing": 0
94 | },
95 | "30": {
96 | "characterSpacing": -0.1000000014901161,
97 | "lineHeight": 26.5,
98 | "paragraphSpacing": 0
99 | },
100 | "31": {
101 | "characterSpacing": -0.1000000014901161,
102 | "lineHeight": 26.5,
103 | "paragraphSpacing": 0
104 | },
105 | "32": {
106 | "characterSpacing": -0.2000000029802322,
107 | "lineHeight": 34.5,
108 | "paragraphSpacing": 0
109 | },
110 | "34": {
111 | "characterSpacing": -0.1000000014901161,
112 | "lineHeight": 26.5,
113 | "paragraphSpacing": 0
114 | },
115 | "36": {
116 | "characterSpacing": -0.699999988079071,
117 | "lineHeight": 38.5,
118 | "paragraphSpacing": 0
119 | },
120 | "38": {
121 | "characterSpacing": -0.699999988079071,
122 | "lineHeight": 40.5,
123 | "paragraphSpacing": 0
124 | },
125 | "39": {
126 | "characterSpacing": -0.699999988079071,
127 | "lineHeight": 41.5,
128 | "paragraphSpacing": 0
129 | },
130 | "40": {
131 | "characterSpacing": -0.699999988079071,
132 | "lineHeight": 42.5,
133 | "paragraphSpacing": 0
134 | },
135 | "41": {
136 | "characterSpacing": -0.699999988079071,
137 | "lineHeight": 43.5,
138 | "paragraphSpacing": 0
139 | },
140 | "42": {
141 | "characterSpacing": -0.699999988079071,
142 | "lineHeight": 44.5,
143 | "paragraphSpacing": 0
144 | }
145 | }
146 | },
147 | "SFCompactText-Semibold": {
148 | "fontFamily": "SF Compact Text",
149 | "fontName": "SFCompactText-Semibold",
150 | "fontWeight": 600,
151 | "fontStyle": "normal",
152 | "sources": {
153 | "locals": [
154 | "SF Compact Text Semibold",
155 | "SFCompactText-Semibold"
156 | ]
157 | },
158 | "normalcase": {
159 | "14": {
160 | "characterSpacing": 0.15,
161 | "lineHeight": 16.5,
162 | "paragraphSpacing": 0
163 | },
164 | "15": {
165 | "characterSpacing": 0.05,
166 | "lineHeight": 17.5,
167 | "paragraphSpacing": 0
168 | },
169 | "16": {
170 | "characterSpacing": 0.05,
171 | "lineHeight": 18.5,
172 | "paragraphSpacing": 0
173 | },
174 | "17": {
175 | "characterSpacing": 0,
176 | "lineHeight": 19.5,
177 | "paragraphSpacing": 0
178 | },
179 | "18": {
180 | "characterSpacing": -0.1,
181 | "lineHeight": 20.5,
182 | "paragraphSpacing": 0
183 | },
184 | "19": {
185 | "characterSpacing": -0.15,
186 | "lineHeight": 21.5,
187 | "paragraphSpacing": 0
188 | }
189 | }
190 | },
191 | "lastUpdated": "2018-09-03T17:25:31.947Z",
192 | "version": "0.1.1",
193 | "compatibleVersion": "0.0.3"
194 | }
--------------------------------------------------------------------------------
/src/directory/SFProDisplay.json:
--------------------------------------------------------------------------------
1 | {
2 | "family": "SF Pro Display",
3 | "SFProDisplay-Regular": {
4 | "fontFamily": "SF Pro Display",
5 | "fontName": "SFProDisplay-Regular",
6 | "fontWeight": 400,
7 | "fontStyle": "normal",
8 | "sources": {
9 | "locals": [
10 | "SF Pro Display Regular",
11 | "SFProDisplay-Regular"
12 | ]
13 | },
14 | "normalcase": {
15 | "20": {
16 | "characterSpacing": 0.3799999952316284,
17 | "lineHeight": 25,
18 | "paragraphSpacing": 0
19 | },
20 | "21": {
21 | "characterSpacing": 0.3600000143051147,
22 | "lineHeight": 26,
23 | "paragraphSpacing": 0
24 | },
25 | "22": {
26 | "characterSpacing": 0.3499999940395355,
27 | "lineHeight": 28,
28 | "paragraphSpacing": 0
29 | },
30 | "23": {
31 | "characterSpacing": 0.3700000047683716,
32 | "lineHeight": 29,
33 | "paragraphSpacing": 0
34 | },
35 | "24": {
36 | "characterSpacing": 0.3600000143051147,
37 | "lineHeight": 30,
38 | "paragraphSpacing": 0
39 | },
40 | "25": {
41 | "characterSpacing": 0.3499999940395355,
42 | "lineHeight": 31,
43 | "paragraphSpacing": 0
44 | },
45 | "26": {
46 | "characterSpacing": 0.3600000143051147,
47 | "lineHeight": 32,
48 | "paragraphSpacing": 0
49 | },
50 | "27": {
51 | "characterSpacing": 0.3499999940395355,
52 | "lineHeight": 33,
53 | "paragraphSpacing": 0
54 | },
55 | "28": {
56 | "characterSpacing": 0.3600000143051147,
57 | "lineHeight": 34,
58 | "paragraphSpacing": 0
59 | },
60 | "29": {
61 | "characterSpacing": 0.3199999928474426,
62 | "lineHeight": 35,
63 | "paragraphSpacing": 0
64 | },
65 | "30": {
66 | "characterSpacing": 0.36,
67 | "lineHeight": 37,
68 | "paragraphSpacing": 0
69 | },
70 | "31": {
71 | "characterSpacing": 0.4000000059604645,
72 | "lineHeight": 38,
73 | "paragraphSpacing": 0
74 | },
75 | "32": {
76 | "characterSpacing": 0.3799999952316284,
77 | "lineHeight": 39,
78 | "paragraphSpacing": 0
79 | },
80 | "33": {
81 | "characterSpacing": 0.3799999952316284,
82 | "lineHeight": 40,
83 | "paragraphSpacing": 0
84 | },
85 | "34": {
86 | "characterSpacing": 0.3700000047683716,
87 | "lineHeight": 41,
88 | "paragraphSpacing": 0
89 | },
90 | "36": {
91 | "characterSpacing": 0.3799999952316284,
92 | "lineHeight": 43,
93 | "paragraphSpacing": 0
94 | },
95 | "37": {
96 | "characterSpacing": 0.4799999892711639,
97 | "lineHeight": 44,
98 | "paragraphSpacing": 0
99 | },
100 | "38": {
101 | "characterSpacing": 0.4199999868869781,
102 | "lineHeight": 46,
103 | "paragraphSpacing": 0
104 | },
105 | "39": {
106 | "characterSpacing": 0.5400000214576721,
107 | "lineHeight": 47,
108 | "paragraphSpacing": 0
109 | },
110 | "40": {
111 | "characterSpacing": 0.4000000059604645,
112 | "lineHeight": 48,
113 | "paragraphSpacing": 0
114 | },
115 | "42": {
116 | "characterSpacing": 0.5199999809265137,
117 | "lineHeight": 50,
118 | "paragraphSpacing": 0
119 | },
120 | "43": {
121 | "characterSpacing": 0.449999988079071,
122 | "lineHeight": 51,
123 | "paragraphSpacing": 0
124 | },
125 | "44": {
126 | "characterSpacing": 0.3100000023841858,
127 | "lineHeight": 52,
128 | "paragraphSpacing": 0
129 | },
130 | "47": {
131 | "characterSpacing": 1.059999942779541,
132 | "lineHeight": 56,
133 | "paragraphSpacing": 0
134 | },
135 | "48": {
136 | "characterSpacing": 0.2899999916553497,
137 | "lineHeight": 57,
138 | "paragraphSpacing": 0
139 | },
140 | "49": {
141 | "characterSpacing": 0.3600000143051147,
142 | "lineHeight": 58,
143 | "paragraphSpacing": 0
144 | },
145 | "50": {
146 | "characterSpacing": 0.3199999928474426,
147 | "lineHeight": 59,
148 | "paragraphSpacing": 0
149 | },
150 | "51": {
151 | "characterSpacing": 0.3499999940395355,
152 | "lineHeight": 60,
153 | "paragraphSpacing": 0
154 | },
155 | "52": {
156 | "characterSpacing": 0.3799999952316284,
157 | "lineHeight": 61,
158 | "paragraphSpacing": 0
159 | },
160 | "53": {
161 | "characterSpacing": 0.3700000047683716,
162 | "lineHeight": 62,
163 | "paragraphSpacing": 0
164 | },
165 | "55": {
166 | "characterSpacing": 0.33,
167 | "lineHeight": 65,
168 | "paragraphSpacing": 0
169 | },
170 | "56": {
171 | "characterSpacing": 0.3100000023841858,
172 | "lineHeight": 66,
173 | "paragraphSpacing": 0
174 | },
175 | "58": {
176 | "characterSpacing": 0.300000011920929,
177 | "lineHeight": 68,
178 | "paragraphSpacing": 0
179 | },
180 | "60": {
181 | "characterSpacing": 0.25,
182 | "lineHeight": 70,
183 | "paragraphSpacing": 0
184 | }
185 | }
186 | },
187 | "SFProDisplay-Semibold": {
188 | "fontFamily": "SF Pro Display",
189 | "fontName": "SFProDisplay-Semibold",
190 | "fontWeight": 600,
191 | "fontStyle": "normal",
192 | "sources": {
193 | "locals": [
194 | "SF Pro Display Semibold",
195 | "SFProDisplay-Semibold"
196 | ]
197 | },
198 | "normalcase": {
199 | "21": {
200 | "characterSpacing": 0.3600000143051147,
201 | "lineHeight": 26,
202 | "paragraphSpacing": 0
203 | },
204 | "23": {
205 | "characterSpacing": 0.3700000047683716,
206 | "lineHeight": 29,
207 | "paragraphSpacing": 0
208 | },
209 | "28": {
210 | "characterSpacing": 0.3400000035762787,
211 | "lineHeight": 34,
212 | "paragraphSpacing": 0
213 | },
214 | "33": {
215 | "characterSpacing": 0.3899999856948853,
216 | "lineHeight": 40,
217 | "paragraphSpacing": 0
218 | },
219 | "40": {
220 | "characterSpacing": 0.4099999964237213,
221 | "lineHeight": 48,
222 | "paragraphSpacing": 0
223 | },
224 | "47": {
225 | "characterSpacing": 0.3600000143051147,
226 | "lineHeight": 56,
227 | "paragraphSpacing": 0
228 | },
229 | "53": {
230 | "characterSpacing": 0.25,
231 | "lineHeight": 62,
232 | "paragraphSpacing": 0
233 | }
234 | }
235 | },
236 | "lastUpdated": "2018-09-03T17:25:41.817Z",
237 | "version": "0.1.1",
238 | "compatibleVersion": "0.0.3"
239 | }
--------------------------------------------------------------------------------
/src/directory/SFProText.json:
--------------------------------------------------------------------------------
1 | {
2 | "family": "SF Pro Text",
3 | "SFProText-Regular": {
4 | "fontFamily": "SF Pro Text",
5 | "fontName": "SFProText-Regular",
6 | "fontWeight": 400,
7 | "fontStyle": "normal",
8 | "sources": {
9 | "locals": [
10 | "SF Pro Text Regular",
11 | "SFProText-Regular"
12 | ]
13 | },
14 | "normalcase": {
15 | "11": {
16 | "characterSpacing": 0.07000000029802322,
17 | "lineHeight": 13,
18 | "paragraphSpacing": 0
19 | },
20 | "12": {
21 | "characterSpacing": 0,
22 | "lineHeight": 16,
23 | "paragraphSpacing": 0
24 | },
25 | "13": {
26 | "characterSpacing": -0.07999999821186066,
27 | "lineHeight": 18,
28 | "paragraphSpacing": 0
29 | },
30 | "14": {
31 | "characterSpacing": -0.1500000059604645,
32 | "lineHeight": 19,
33 | "paragraphSpacing": 0
34 | },
35 | "15": {
36 | "characterSpacing": -0.239999994635582,
37 | "lineHeight": 20,
38 | "paragraphSpacing": 0
39 | },
40 | "16": {
41 | "characterSpacing": -0.3199999928474426,
42 | "lineHeight": 21,
43 | "paragraphSpacing": 0
44 | },
45 | "17": {
46 | "characterSpacing": -0.4099999964237213,
47 | "lineHeight": 22,
48 | "paragraphSpacing": 0
49 | },
50 | "18": {
51 | "characterSpacing": -0.449999988079071,
52 | "lineHeight": 23,
53 | "paragraphSpacing": 0
54 | },
55 | "19": {
56 | "characterSpacing": -0.4900000095367432,
57 | "lineHeight": 24,
58 | "paragraphSpacing": 0
59 | }
60 | }
61 | },
62 | "SFProText-Semibold": {
63 | "fontFamily": "SF Pro Text",
64 | "fontName": "SFProText-Semibold",
65 | "fontWeight": 600,
66 | "fontStyle": "normal",
67 | "sources": {
68 | "locals": [
69 | "SF Pro Text Semibold",
70 | "SFProText-Semibold"
71 | ]
72 | },
73 | "normalcase": {
74 | "14": {
75 | "characterSpacing": -0.1500000059604645,
76 | "lineHeight": 19,
77 | "paragraphSpacing": 0
78 | },
79 | "15": {
80 | "characterSpacing": -0.239999994635582,
81 | "lineHeight": 20,
82 | "paragraphSpacing": 0
83 | },
84 | "16": {
85 | "characterSpacing": -0.3199999928474426,
86 | "lineHeight": 21,
87 | "paragraphSpacing": 0
88 | },
89 | "17": {
90 | "characterSpacing": -0.4099999964237213,
91 | "lineHeight": 22,
92 | "paragraphSpacing": 0
93 | },
94 | "19": {
95 | "characterSpacing": -0.4900000095367432,
96 | "lineHeight": 24,
97 | "paragraphSpacing": 0
98 | }
99 | }
100 | },
101 | "lastUpdated": "2018-09-03T17:25:41.817Z",
102 | "version": "0.1.1",
103 | "compatibleVersion": "0.0.3"
104 | }
--------------------------------------------------------------------------------
/src/plugin/Typesetter.js:
--------------------------------------------------------------------------------
1 | import fs from '@skpm/fs'
2 | import {
3 | getLetterCasing,
4 | getStyleOfFont,
5 | getWeightOfFont
6 | } from 'plugin/utils/fonts'
7 | import { MIN_VERSION, preferences } from './storage'
8 |
9 | const filePath = (fontFamily) => {
10 | const fileName = `${ fontFamily.replace(/\s/g, '') }.json`
11 |
12 | const pluginDefinedDirectory = `${ preferences.pluginDefinedDirectory }/${ fileName }`
13 | if (fs.existsSync(pluginDefinedDirectory)) {
14 | return pluginDefinedDirectory
15 | }
16 |
17 | // If the user defined directory path already includes the home path,
18 | // we don't want to prepend it or its not going to work correctly
19 | const userDirPath = `${ preferences.userDefinedDirectory }/${ fileName }`
20 |
21 | if (userDirPath.includes(String(NSHomeDirectory()))) {
22 | return userDirPath
23 | }
24 |
25 | return `${ NSHomeDirectory() }/${ userDirPath }`
26 | }
27 |
28 | const transform = (textLayer) => {
29 | const layer = textLayer.sketchObject || textLayer
30 | const attrs = layer.style().textStyle().attributes()
31 |
32 | return {
33 | fontFamily: String(attrs.NSFont.familyName()),
34 | fontName: String(attrs.NSFont.fontName()),
35 | fontDisplayName: String(attrs.NSFont.displayName()),
36 | fontPostscriptName: String(layer.fontPostscriptName()),
37 | fontSize: Number(layer.fontSize()),
38 | fontStyle: String(getStyleOfFont(attrs.NSFont)),
39 | fontWeight: Number(getWeightOfFont(attrs.NSFont)),
40 | casing: getLetterCasing(attrs),
41 | characterSpacing: layer.characterSpacing()
42 | ? Number(layer.characterSpacing())
43 | : layer.characterSpacing(),
44 | lineHeight: Number(layer.lineHeight()),
45 | paragraphSpacing: Number(attrs.NSParagraphStyle.paragraphSpacing())
46 | }
47 | }
48 |
49 | const toVariant = textLayer => ({
50 | [textLayer.fontName]: {
51 | fontFamily: textLayer.fontFamily,
52 | fontName: textLayer.fontName,
53 | fontWeight: textLayer.fontWeight,
54 | fontStyle: textLayer.fontStyle,
55 | sources: {
56 | locals: [
57 | textLayer.fontDisplayName,
58 | textLayer.fontPostscriptName
59 | ]
60 | },
61 | [textLayer.casing]: {
62 | [textLayer.fontSize]: {
63 | characterSpacing: textLayer.characterSpacing,
64 | lineHeight: textLayer.lineHeight,
65 | paragraphSpacing: textLayer.paragraphSpacing
66 | }
67 | }
68 | }
69 | })
70 |
71 | // Returns typesettings for a given text layer
72 | let typesettings = null
73 | const fetch = (context, layer) => {
74 | const {
75 | fontFamily,
76 | fontSize,
77 | fontName,
78 | casing
79 | } = transform(layer)
80 |
81 | if (!typesettings || (typesettings.family !== fontFamily)) {
82 | const file = filePath(fontFamily)
83 |
84 | if (!fs.existsSync(file)) {
85 | return 'There are no registered typesettings for the text layer.'
86 | }
87 |
88 | try {
89 | typesettings = JSON.parse(fs.readFileSync(file, 'utf8'))
90 | } catch (err) {
91 | return err
92 | }
93 | }
94 |
95 | const version = context.plugin.version().UTF8String()
96 | const { compatibleVersion } = typesettings
97 |
98 | // Check if the settings are compatible
99 | // (v0.0.2 and below will not have a compatibleVersion)
100 | if (!compatibleVersion || compatibleVersion < Number(MIN_VERSION)) {
101 | if (!typesettings[fontName]
102 | || !typesettings[fontName][fontSize]
103 | || !typesettings[fontName][fontSize][casing]) {
104 | return 'No typesettings registered for the current casing and font size.'
105 | }
106 | return typesettings[fontName][fontSize][casing]
107 | }
108 |
109 | // Check if plugin is out of date and incompatible with a newer typesettings version
110 | if (compatibleVersion && compatibleVersion > version) {
111 | return 'Your plugin is out of date. Please update to the latest version of Typesettings.'
112 | }
113 |
114 | // Latest Version
115 | if (compatibleVersion && compatibleVersion <= version) {
116 | if (!typesettings[fontName]
117 | || !typesettings[fontName][casing]
118 | || !typesettings[fontName][casing][fontSize]) {
119 | return 'No typesettings registered for the current casing and font size.'
120 | }
121 | return typesettings[fontName][casing][fontSize]
122 | }
123 |
124 | return 'There are no registered typesettings for the text layer.'
125 | }
126 |
127 | const setType = (layer, settings, opts) => {
128 | const { kern, lineHeight } = opts
129 |
130 | if (kern) {
131 | layer.setCharacterSpacing(settings.characterSpacing)
132 | }
133 |
134 | if (lineHeight) {
135 | layer.style().textStyle().attributes().NSParagraphStyle
136 | .setParagraphSpacing(settings.paragraphSpacing)
137 | layer.setLineHeight(settings.lineHeight)
138 | }
139 | }
140 |
141 | const Typesetter = {
142 | filePath,
143 | transform,
144 | toVariant,
145 | fetch,
146 | setType
147 | }
148 |
149 | export default Typesetter
150 |
--------------------------------------------------------------------------------
/src/plugin/commands/auto.js:
--------------------------------------------------------------------------------
1 | import Typesetter from 'plugin/Typesetter'
2 | import { preferences } from 'plugin/storage'
3 | import { getMSTextLayers } from 'plugin/utils/helpers'
4 |
5 | export const onSelectionFinish = (context) => {
6 | const {
7 | allowsAutoKerning,
8 | allowsAutoLineHeight,
9 | ignorePrefix,
10 | ignoreSuffix
11 | } = preferences
12 |
13 | if (allowsAutoKerning || allowsAutoLineHeight) {
14 | const selection = getMSTextLayers(context.actionContext.oldSelection)
15 |
16 | selection.forEach((layer) => {
17 | // If the layer name matches the ignore prefix or suffix, bail
18 | const name = layer.name()
19 | if (name.hasPrefix(ignorePrefix) || name.hasSuffix(ignoreSuffix)) {
20 | return
21 | }
22 |
23 | // Get the typesettings for the given layer
24 | const settings = Typesetter.fetch(context, layer)
25 |
26 | if (typeof settings === 'string') {
27 | return
28 | }
29 |
30 | // If we don't have settings, bail
31 | if (settings && settings.length === 0) {
32 | return
33 | }
34 |
35 | Typesetter.setType(layer, settings, {
36 | kern: allowsAutoKerning,
37 | lineHeight: allowsAutoLineHeight
38 | })
39 | })
40 | }
41 | }
42 |
43 | export default onSelectionFinish
44 |
--------------------------------------------------------------------------------
/src/plugin/commands/preferences.js:
--------------------------------------------------------------------------------
1 | import dialog from '@skpm/dialog'
2 | import UI from 'sketch/ui'
3 | import BrowserWindow from 'sketch-module-web-view'
4 | import WebviewEntry from 'webview/index.html'
5 | import { preferences, savePreferences } from 'plugin/storage'
6 |
7 | export default () => {
8 | const options = {
9 | identifier: 'typesettings',
10 | redirectTo: '/',
11 | width: 310,
12 | height: 500,
13 | minWidth: 260,
14 | minHeight: 450,
15 | show: false,
16 | loaded: false,
17 | backgroundColor: '#FFFFFF',
18 | fullscreenable: false,
19 | devTools: true
20 | }
21 |
22 | const window = new BrowserWindow(options)
23 | const { webContents } = window
24 |
25 | // Only show the window when the page has loaded
26 | window.once('ready-to-show', () => {
27 | window.show()
28 | })
29 |
30 | // Returns the plugin preferences to the webview
31 | webContents.on('getPreferences', () => {
32 | webContents.executeJavaScript(`preferences = ${ JSON.stringify(preferences) }`)
33 | })
34 |
35 | // Updates the plugin preferences from the webview
36 | webContents.on('setPreferences', (newState) => {
37 | const newPrefs = savePreferences(newState)
38 | webContents.executeJavaScript(`reloadData(${ JSON.stringify(newPrefs) })`)
39 | })
40 |
41 | // Prompts to select a local directory file path
42 | webContents.on('selectUserDefinedDirectory', (newState) => {
43 | const opts = {
44 | buttonLabel: 'Set Directory',
45 | properties: [ 'openDirectory' ],
46 | filters: [
47 | { name: 'DirectoriesOnly', extensions: [ '' ] }
48 | ]
49 | }
50 |
51 | dialog.showOpenDialog(opts, ((selection) => {
52 | const newPrefs = savePreferences({
53 | ...newState,
54 | userDefinedDirectory: selection[0]
55 | })
56 | webContents.executeJavaScript(`reloadData(${ JSON.stringify(newPrefs) })`)
57 | }))
58 | })
59 |
60 | // Copies the userDefinedDirectory to the clipboard
61 | webContents.on('copyUserDefinedDirectoryPath', () => {
62 | const pasteboard = NSPasteboard.generalPasteboard()
63 | pasteboard.clearContents()
64 | pasteboard.setString_forType_(preferences.userDefinedDirectory, NSStringPboardType) // eslint-disable-line
65 | UI.message('Copied to clipboard!')
66 | })
67 |
68 | // Copies the userDefinedDirectory to the clipboard
69 | webContents.on('showUserDefinedDirectory', () => (
70 | NSWorkspace.sharedWorkspace()
71 | .selectFile_inFileViewerRootedAtPath(preferences.userDefinedDirectory, null)
72 | ))
73 |
74 | // Open an external url
75 | webContents.on('openUrl', url => (
76 | NSWorkspace.sharedWorkspace()
77 | .openURL(NSURL.URLWithString(url))
78 | ))
79 |
80 | window.loadURL(WebviewEntry)
81 | }
82 |
--------------------------------------------------------------------------------
/src/plugin/commands/register.js:
--------------------------------------------------------------------------------
1 | import UI from 'sketch/ui'
2 | import fs from '@skpm/fs'
3 | import dialog from '@skpm/dialog'
4 | import merge from 'deepmerge'
5 | import Typesetter from 'plugin/Typesetter'
6 | import { MIN_VERSION } from 'plugin/storage'
7 | import { getJSTextLayers, pluck } from 'plugin/utils/helpers'
8 |
9 | const mergeOptions = {
10 | arrayMerge: (destinationArray, sourceArray, options) => sourceArray // eslint-disable-line
11 | }
12 |
13 | export default (context) => {
14 | const selection = getJSTextLayers(context.selection)
15 |
16 | if (selection.length === 0) {
17 | return UI.message('You need to select atleast 1 text layer')
18 | }
19 |
20 | const version = context.plugin.version().UTF8String()
21 | const lastUpdated = new Date().toISOString()
22 | const textLayers = selection.map(Typesetter.transform)
23 |
24 | // Get all of the selected font families
25 | const families = pluck(textLayers, 'fontFamily')
26 |
27 | // Create the typesettings for each font family
28 | const typesettings = families.map((family) => {
29 | const fonts = textLayers.filter(layer => layer.fontFamily === family)
30 | const variants = merge.all(fonts.map(Typesetter.toVariant), mergeOptions)
31 | return {
32 | family,
33 | ...variants,
34 | lastUpdated,
35 | version, // plugin version used to register typesettings
36 | compatibleVersion: MIN_VERSION // min plugin version to load typesettings
37 | }
38 | })
39 |
40 | // Replace or update the typesettings
41 | const done = typesettings.map((settings) => {
42 | const filePath = Typesetter.filePath(settings.family)
43 |
44 | if (fs.existsSync(filePath)) {
45 | const currSettings = JSON.parse(fs.readFileSync(filePath, 'utf8'))
46 | const { compatibleVersion } = currSettings
47 | let status = ''
48 |
49 | // Check if plugin is out of date and incompatible with a newer typesettings version
50 | if (compatibleVersion && compatibleVersion > version) {
51 | status = 'Your plugin is out of date. Please update to the latest version of Typesettings.'
52 | }
53 |
54 | // Check if the existing settings are compatible and override if not
55 | // (v0.0.2 and below will not have a compatibleVersion)
56 | if (!compatibleVersion || compatibleVersion < Number(MIN_VERSION)) {
57 | dialog.showMessageBox({
58 | message: `Incompatible Typesetting Versions for ${ settings.family }`,
59 | detail: `Your typesettings were created with an incompatible version of the Typesettings plugin. Would you like to replace all typesettings for ${ settings.family }?`,
60 | buttons: [ 'Replace', 'Cancel' ]
61 | }, ({ response }) => {
62 | if (response === 1) {
63 | status = 'Nothing saved'
64 | }
65 |
66 | if (response === 0) {
67 | fs.writeFileSync(filePath, JSON.stringify(settings, null, 2))
68 | status = `Replaced ${ settings.family } typesettings`
69 | }
70 | })
71 | } else {
72 | // Latest Version
73 | dialog.showMessageBox({
74 | message: `Typesettings Exists for ${ settings.family }`,
75 | detail: `Do you want to merge or replace current typesettings for ${ settings.family }?`,
76 | buttons: [ 'Merge', 'Replace', 'Cancel' ]
77 | }, ({ response }) => {
78 | // Cancel clicked
79 | if (response === 2) {
80 | status = 'Nothing saved'
81 | }
82 |
83 | // Replace clicked
84 | if (response === 1) {
85 | fs.writeFileSync(filePath, JSON.stringify(settings, null, 2))
86 | status = `Replaced ${ settings.family } typesettings`
87 | }
88 |
89 | if (response === 0) {
90 | // Merge clicked
91 | const updatedSettings = merge(currSettings, settings, mergeOptions)
92 | fs.writeFileSync(filePath, JSON.stringify(updatedSettings, null, 2))
93 | status = `Updated ${ settings.family } typesettings`
94 | }
95 | })
96 | }
97 |
98 | return status
99 | }
100 |
101 | // Typesettings for not exists. Let's write them all out.
102 | fs.writeFileSync(filePath, JSON.stringify(settings, null, 2))
103 | return `Registered ${ settings.family } typesettings`
104 | })
105 |
106 | const msg = (done.length === 1) ? done.join('') : `Registered typesettings for ${ done.length } fonts`
107 | return UI.message(msg)
108 | }
109 |
--------------------------------------------------------------------------------
/src/plugin/commands/typeset.js:
--------------------------------------------------------------------------------
1 | import UI from 'sketch/ui'
2 | import Typesetter from 'plugin/Typesetter'
3 | import { getMSTextLayers } from 'plugin/utils/helpers'
4 |
5 | const typeset = (context, opts) => {
6 | const { setCharacterSpacing, setLineHeight } = opts
7 | const selection = getMSTextLayers(context.selection)
8 |
9 | if (selection.length === 0) {
10 | return 'You need to select atleast 1 text layer'
11 | }
12 |
13 | const counter = { set: 0, skipped: 0 }
14 |
15 | selection.forEach((layer) => {
16 | const settings = Typesetter.fetch(context, layer)
17 |
18 | if (typeof settings === 'string') {
19 | counter.skipped += 1
20 | UI.message(settings)
21 | return
22 | }
23 |
24 | if (settings && settings.length === 0) {
25 | counter.skipped += 1
26 | return
27 | }
28 |
29 | Typesetter.setType(layer, settings, {
30 | kern: setCharacterSpacing,
31 | lineHeight: setLineHeight
32 | })
33 | counter.set += 1
34 | })
35 |
36 | // Reload and return the count
37 | context.document.reloadInspector()
38 |
39 | if (counter.set === 0 && counter.skipped > 0) {
40 | return 'There are no registered typesettings for the text layer.'
41 | }
42 |
43 | return `Set: ${ counter.set }, Skipped: ${ counter.skipped }`
44 | }
45 |
46 | // onRun Typset Character Spacing
47 | export const onSetCharacterSpacing = (context) => {
48 | const done = typeset(context, {
49 | setCharacterSpacing: true,
50 | setLineHeight: false
51 | })
52 | UI.message(done)
53 | }
54 |
55 | // onRun Typset Line Height
56 | export const onSetLineHeight = (context) => {
57 | const done = typeset(context, {
58 | setCharacterSpacing: false,
59 | setLineHeight: true
60 | })
61 | UI.message(done)
62 | }
63 |
--------------------------------------------------------------------------------
/src/plugin/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compatibleVersion": 3,
3 | "bundleVersion": 1,
4 | "icon": "assets/icons/mark.png",
5 | "commands": [
6 | {
7 | "name": "Automatically Typeset Text Layers",
8 | "handlers": {
9 | "actions": {
10 | "SelectionChanged.finish": "onSelectionFinish"
11 | }
12 | },
13 | "script": "commands/auto.js"
14 | },
15 | {
16 | "name": "Set Character Spacing",
17 | "description": "Set selected text layer's character spacing if the typesettings are registered.",
18 | "shortcut" : "cmd shift →",
19 | "icon": "assets/icons/characterSpacingRunner.png",
20 | "handlers": {
21 | "run": "onSetCharacterSpacing"
22 | },
23 | "identifier": "typesetCharacterSpacing",
24 | "script": "commands/typeset.js"
25 | },
26 | {
27 | "name": "Set Line Height",
28 | "description": "Set selected text layer's line height if the typesettings are registered.",
29 | "shortcut" : "cmd shift ↓",
30 | "icon": "assets/icons/lineHeightRunner.png",
31 | "handlers": {
32 | "run": "onSetLineHeight"
33 | },
34 | "identifier": "typesetLineHeight",
35 | "script": "commands/typeset.js"
36 | },
37 | {
38 | "name": "Register Typesettings",
39 | "description": "Register typsettings of all selected layers.",
40 | "shortcut" : "ctrl alt cmd ↩",
41 | "icon": "assets/icons/registerRunner.png",
42 | "identifier": "registerTypesettings",
43 | "script": "commands/register.js"
44 | },
45 | {
46 | "name": "Preferences",
47 | "description": "Manage your Typesettings Plugin preferences.",
48 | "shortcut" : "ctrl alt cmd t",
49 | "icon": "assets/icons/mark.png",
50 | "identifier": "preferences",
51 | "script": "commands/preferences.js"
52 | }
53 | ],
54 | "menu": {
55 | "title": "Typesettings",
56 | "items": [
57 | "typesetCharacterSpacing",
58 | "typesetLineHeight",
59 | "-",
60 | "registerTypesettings",
61 | "preferences"
62 | ]
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/plugin/storage/index.js:
--------------------------------------------------------------------------------
1 | import Settings from 'sketch/settings'
2 | import { __DEV__ } from 'plugin/utils/helpers'
3 |
4 | export const MIN_VERSION = '0.0.3'
5 |
6 | export const preferences = {
7 | pluginDefinedDirectory: __DEV__
8 | ? `${ NSHomeDirectory() }/Development/typesettings-sketch-plugin/src/directory`
9 | : `${ NSHomeDirectory() }/Library/Application Support/com.bohemiancoding.sketch3/Plugins/typesettings.sketchplugin/Contents/Resources`,
10 | userDefinedDirectory: Settings.settingForKey('userDefinedDirectory') || `${ NSHomeDirectory() }/Desktop`,
11 | allowsAutoKerning: Settings.settingForKey('allowsAutoKerning') || false,
12 | allowsAutoLineHeight: Settings.settingForKey('allowsAutoLineHeight') || false,
13 | ignorePrefix: Settings.settingForKey('ignorePrefix') || '^',
14 | ignoreSuffix: Settings.settingForKey('ignoreSuffix') || '^'
15 | }
16 |
17 | export const savePreferences = (newPrefs) => {
18 | const prefs = { ...preferences, ...newPrefs }
19 | Object.keys(prefs).forEach((key) => {
20 | Settings.setSettingForKey(key, prefs[key])
21 | })
22 | return prefs
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugin/utils/fonts.js:
--------------------------------------------------------------------------------
1 | export const TEXT_TRANSFORM = {
2 | 0: 'normalcase',
3 | 1: 'uppercase',
4 | 2: 'lowercase'
5 | }
6 |
7 | export const TEXT_ALIGNMENT = {
8 | 0: 'left',
9 | 1: 'right',
10 | 2: 'center',
11 | 3: 'justified'
12 | }
13 |
14 | export const FONT_STYLES = {
15 | 0: 'normal',
16 | 1: 'italic'
17 | }
18 |
19 | // Maps AppKit weightOfFont_ to CSS Values
20 | // See https://github.com/chromium/chromium/blob/1fa6069561057a05c66dc1a7e5b3c5a4beb519c6/third_party/blink/renderer/platform/fonts/mac/font_family_matcher_mac.mm#L293-L313
21 | export const FONT_WEIGHTS = {
22 | 2: 100,
23 | 3: 200,
24 | 4: 300,
25 | 5: 400,
26 | 6: 500,
27 | 8: 600,
28 | 9: 700,
29 | 10: 800,
30 | 12: 900
31 | }
32 |
33 | export const isItalicFont = (font) => {
34 | const traits = font.fontDescriptor().objectForKey(NSFontTraitsAttribute)
35 | const symbolicTraits = traits[NSFontSymbolicTrait].unsignedIntValue()
36 | return (symbolicTraits & NSFontItalicTrait) !== 0 //eslint-disable-line
37 | }
38 |
39 | export const getStyleOfFont = (font) => {
40 | const isItalic = isItalicFont(font) ? 1 : 0
41 | return FONT_STYLES[isItalic]
42 | }
43 |
44 | export const getWeightOfFont = (font) => {
45 | const appKitWeight = NSFontManager.sharedFontManager().weightOfFont_(font) // eslint-disable-line
46 | return FONT_WEIGHTS[appKitWeight]
47 | }
48 |
49 | export const getLetterCasing = attrs => (
50 | TEXT_TRANSFORM[attrs.MSAttributedStringTextTransformAttribute || 0]
51 | )
52 |
--------------------------------------------------------------------------------
/src/plugin/utils/helpers.js:
--------------------------------------------------------------------------------
1 | import { fromNative } from 'sketch/dom'
2 |
3 | export const __DEV__ = process.env.NODE_ENV !== 'production' // eslint-disable-line
4 |
5 | export const pluck = (arr, prop) => {
6 | const mapped = arr.map(item => item[prop])
7 | return mapped.filter((value, index, self) => self.indexOf(value) === index)
8 | }
9 |
10 | export const getMSTextLayers = (selection) => {
11 | const predicate = NSPredicate.predicateWithFormat('className == %@', 'MSTextLayer')
12 | return selection.filteredArrayUsingPredicate(predicate)
13 | }
14 |
15 | export const getJSTextLayers = (selection) => {
16 | if (Array.isArray(selection)) {
17 | return selection.filter(layer => layer.type === 'Text')
18 | }
19 |
20 | const arr = []
21 | getMSTextLayers(selection).forEach(layer => arr.push(fromNative(layer)))
22 | return arr
23 | }
24 |
--------------------------------------------------------------------------------
/src/webview/client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-filename-extension */
2 | import React, { Fragment } from 'react'
3 | import { render } from 'react-dom'
4 | import { HashRouter } from 'react-router-dom'
5 | import { renderRoutes } from 'react-router-config'
6 | import pluginCall from 'sketch-module-web-view/client'
7 | import { Global } from '@emotion/core'
8 | import globals from 'webview/style/globals'
9 | import routes from 'webview/pages'
10 |
11 | const renderApp = (props) => {
12 | render(
13 |
14 |
15 |
16 | { renderRoutes(routes, props) }
17 |
18 | ,
19 | document.getElementById('root')
20 | )
21 | }
22 |
23 | // Disable the context menu to have a more native feel
24 | document.addEventListener('contextmenu', (e => (
25 | e.preventDefault()
26 | )))
27 |
28 | // Call the sketch plugin to get the current user preferences
29 | pluginCall('getPreferences')
30 |
31 | // Now render the app
32 | renderApp({
33 | preferences: window.preferences
34 | })
35 |
36 | // This is called from the plugin and tells the webview to refresh
37 | window.reloadData = (newProps) => {
38 | renderApp({
39 | preferences: newProps
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/src/webview/components/Button/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styled from '@emotion/styled'
4 | import {
5 | colors,
6 | fonts,
7 | radii,
8 | space
9 | } from 'webview/style/variables'
10 |
11 | const Base = styled.button`
12 | appearance: none;
13 | border: 0;
14 | padding: calc(${ space[1] } * 1.25) ${ space[2] };
15 | margin: ${ space[1] } ${ space[1] } 0 0;
16 | background: ${ colors.black.base };
17 | border-radius: ${ radii[2] };
18 |
19 | ${ fonts.s10.n700 };
20 | color: ${ colors.white.base };
21 | text-align: center;
22 |
23 | &:hover {
24 | cursor: pointer;
25 | }
26 | `
27 |
28 | const Button = ({ label, name, onClick }) => (
29 |
34 | { label }
35 |
36 | )
37 |
38 | Button.propTypes = {
39 | label: PropTypes.string.isRequired,
40 | name: PropTypes.string.isRequired,
41 | onClick: PropTypes.func.isRequired
42 | }
43 |
44 | export default Button
45 |
--------------------------------------------------------------------------------
/src/webview/components/Checkbox/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import Field from 'webview/components/Field'
4 | import { CheckboxInput, CheckboxLabel } from './styles'
5 |
6 | class Checkbox extends PureComponent {
7 | static propTypes = {
8 | onChange: PropTypes.func.isRequired,
9 | name: PropTypes.string.isRequired,
10 | checked: PropTypes.oneOfType([
11 | PropTypes.bool,
12 | PropTypes.number
13 | ]),
14 | label: PropTypes.string
15 | }
16 |
17 | static defaultProps = {
18 | checked: true,
19 | label: null
20 | }
21 |
22 | constructor(props) {
23 | super(props)
24 | this.handleOnChange = this.handleOnChange.bind(this)
25 | }
26 |
27 | handleOnChange(evt) {
28 | const { onChange } = this.props
29 | onChange(evt.target)
30 | }
31 |
32 | render() {
33 | const {
34 | name,
35 | checked,
36 | label
37 | } = this.props
38 |
39 | return (
40 |
41 |
48 | { label && (
49 |
50 | { label }
51 |
52 | ) }
53 |
54 | )
55 | }
56 | }
57 |
58 | export default Checkbox
59 |
--------------------------------------------------------------------------------
/src/webview/components/Checkbox/styles.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import {
3 | colors,
4 | fonts,
5 | radii,
6 | space
7 | } from 'webview/style/variables'
8 |
9 | export const CheckboxInput = styled.input`
10 | appearance: none;
11 | background: ${ colors.black.l100 };
12 | border: 0;
13 | border-radius: ${ radii[1] };
14 | box-shadow: inset 0 0 0 1px ${ colors.black.l92 };
15 | height: 14px;
16 | margin: 0;
17 | width: 14px;
18 |
19 | &:checked {
20 | box-shadow: inset 0 0 0 1px ${ colors.black.base };
21 | background: ${ colors.black.base };
22 | outline: none;
23 | },
24 |
25 | &:hover {
26 | cursor: pointer;
27 | }
28 | `
29 |
30 | export const CheckboxLabel = styled.label`
31 | ${ fonts.s12.n400 };
32 | color: ${ colors.black.l10 };
33 | padding-left: ${ space[1] };
34 | width: 100%;
35 | &:hover {
36 | cursor: pointer;
37 | }
38 | `
39 |
--------------------------------------------------------------------------------
/src/webview/components/Field/index.jsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import { space } from 'webview/style/variables'
3 |
4 | const Field = styled.div`
5 | display: flex;
6 | align-items: center;
7 | flex-wrap: ${ props => (props.wrap ? 'wrap' : null) };
8 | padding-top: ${ space[1] };
9 | padding-bottom: ${ space[1] };
10 | `
11 |
12 | export default Field
13 |
--------------------------------------------------------------------------------
/src/webview/components/Page/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from '@emotion/styled'
3 | import pluginCall from 'sketch-module-web-view/client'
4 | import InlineSVG from 'svg-inline-react'
5 | import { colors, space } from 'webview/style/variables'
6 | import markSvg from './mark.svg'
7 |
8 | const Footer = styled.footer`
9 | background: ${ colors.black.l97 };
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | padding-top: ${ space[6] };
14 | padding-left: ${ space[2] };
15 | padding-right: ${ space[2] };
16 | padding-bottom: ${ space[6] };
17 | `
18 |
19 | const Mark = styled(InlineSVG)`
20 | width: 24px;
21 | height: 24px;
22 |
23 | path {
24 | fill: ${ colors.black.l30 };
25 | }
26 |
27 | &:hover {
28 | cursor: pointer;
29 | }
30 | `
31 |
32 | const handleOnClickMark = () => (
33 | pluginCall('openUrl', 'https://github.com/buames/typesettings-sketch-plugin')
34 | )
35 |
36 | const PageFooter = () => (
37 |
44 | )
45 |
46 | export default PageFooter
47 |
--------------------------------------------------------------------------------
/src/webview/components/Page/Footer/mark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/webview/components/Page/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styled from '@emotion/styled'
4 | import {
5 | borders,
6 | colors,
7 | fonts,
8 | space
9 | } from 'webview/style/variables'
10 |
11 | export const headerHeight = '48px'
12 |
13 | const Header = styled.header`
14 | background: ${ colors.white.base };
15 | border-bottom: ${ borders[1] } ${ colors.black.l92 };
16 | padding-left: ${ space[2] };
17 | padding-right: ${ space[2] };
18 | height: ${ headerHeight };
19 | position: fixed;
20 | top: 0;
21 | left: 0;
22 | right: 0;
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 | `
27 |
28 | const Title = styled.h1`
29 | ${ fonts.s14.n700 };
30 | color: ${ colors.black.l10 };
31 | `
32 |
33 | const PageHeader = ({ title }) => (
34 |
35 |
36 | { title }
37 |
38 |
39 | )
40 |
41 | PageHeader.propTypes = {
42 | title: PropTypes.string
43 | }
44 |
45 | PageHeader.defaultProps = {
46 | title: null
47 | }
48 |
49 | export default PageHeader
50 |
--------------------------------------------------------------------------------
/src/webview/components/Page/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import PropTypes from 'prop-types'
3 | import styled from '@emotion/styled'
4 | import { space } from 'webview/style/variables'
5 | import PageHeader, { headerHeight } from './Header'
6 | import PageFooter from './Footer'
7 |
8 | const Main = styled.main`
9 | padding-top: ${ headerHeight };
10 | padding-left: ${ space[2] };
11 | padding-right: ${ space[2] };
12 | `
13 |
14 | const Page = ({ title, children }) => (
15 |
16 |
17 |
18 | { children }
19 |
20 |
21 |
22 | )
23 |
24 | Page.propTypes = {
25 | children: PropTypes.node.isRequired,
26 | title: PropTypes.string
27 | }
28 |
29 | Page.defaultProps = {
30 | title: null
31 | }
32 |
33 | export default Page
34 |
--------------------------------------------------------------------------------
/src/webview/components/TextField/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import Field from 'webview/components/Field'
4 | import { Input, InputLabel } from './styles'
5 |
6 | class TextField extends PureComponent {
7 | static propTypes = {
8 | onChange: PropTypes.func.isRequired,
9 | name: PropTypes.string.isRequired,
10 | placeholder: PropTypes.string,
11 | value: PropTypes.string,
12 | label: PropTypes.string,
13 | disabled: PropTypes.bool
14 | }
15 |
16 | static defaultProps = {
17 | placeholder: null,
18 | value: null,
19 | label: null,
20 | disabled: false
21 | }
22 |
23 | constructor(props) {
24 | super(props)
25 | this.handleOnChange = this.handleOnChange.bind(this)
26 | }
27 |
28 | handleOnChange(evt) {
29 | const { onChange } = this.props
30 | onChange(evt.target)
31 | }
32 |
33 | render() {
34 | const {
35 | name,
36 | placeholder,
37 | value,
38 | label,
39 | disabled
40 | } = this.props
41 |
42 | return (
43 |
44 | { label && (
45 |
46 | { label }
47 |
48 | ) }
49 |
57 |
58 | )
59 | }
60 | }
61 |
62 | export default TextField
63 |
--------------------------------------------------------------------------------
/src/webview/components/TextField/styles.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import {
3 | colors,
4 | fonts,
5 | radii,
6 | space
7 | } from 'webview/style/variables'
8 |
9 | export const Input = styled.input`
10 | appearance: none;
11 | background: ${ colors.white.base };
12 | border: 0;
13 | border-radius: ${ radii[1] };
14 | box-shadow: inset 0 0 0 1px ${ colors.black.l92 };
15 | margin: 0;
16 | padding: ${ space[1] };
17 | width: 100%;
18 |
19 | ${ fonts.s12.n400 };
20 | color: ${ colors.black.l10 };
21 |
22 | &:focus {
23 | box-shadow: inset 0 0 0 1px ${ colors.black.base };
24 | outline: none;
25 | }
26 |
27 | &:disabled {
28 | background-color: ${ colors.black.l97 };
29 | cursor: not-allowed;
30 | }
31 |
32 | &::placeholder {
33 | ${ fonts.s12.n400 };
34 | color: ${ colors.black.l55 };
35 | }
36 | `
37 |
38 | export const InputLabel = styled.label`
39 | ${ fonts.s12.n400 };
40 | color: ${ colors.black.l10 };
41 | padding-top: ${ space[1] };
42 | padding-bottom: ${ space[1] };
43 | width: 100%;
44 | `
45 |
--------------------------------------------------------------------------------
/src/webview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Typesettings
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/webview/pages/SettingsPage/Section.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styled from '@emotion/styled'
4 | import {
5 | colors,
6 | borders,
7 | fonts,
8 | space
9 | } from 'webview/style/variables'
10 |
11 | const Container = styled.section`
12 | padding-top: ${ space[3] };
13 | padding-bottom: ${ space[3] };
14 | border-top: ${ borders[1] } ${ colors.black.l92 };
15 |
16 | &:first-of-type {
17 | border-top: none;
18 | }
19 | `
20 |
21 | const TitleLabel = styled.h2`
22 | ${ fonts.s12.n700 };
23 | color: ${ colors.black.l10 };
24 | padding-bottom: ${ space[2] };
25 | `
26 |
27 | const Section = ({ children, title }) => (
28 |
29 | { title && (
30 |
31 | { title }
32 |
33 | ) }
34 | { children }
35 |
36 | )
37 |
38 | Section.propTypes = {
39 | title: PropTypes.string,
40 | children: PropTypes.node.isRequired
41 | }
42 |
43 | Section.defaultProps = {
44 | title: null
45 | }
46 |
47 | export default Section
48 |
--------------------------------------------------------------------------------
/src/webview/pages/SettingsPage/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import pluginCall from 'sketch-module-web-view/client'
4 | import Page from 'webview/components/Page'
5 | import Button from 'webview/components/Button'
6 | import Checkbox from 'webview/components/Checkbox'
7 | import TextField from 'webview/components/TextField'
8 | import Section from './Section'
9 |
10 | class SettingsPage extends Component {
11 | state = {
12 | ...this.props.preferences // eslint-disable-line
13 | }
14 |
15 | static propTypes = {
16 | route: PropTypes.objectOf(PropTypes.any).isRequired,
17 | preferences: PropTypes.shape({
18 | pluginDefinedDirectory: PropTypes.string,
19 | userDefinedDirectory: PropTypes.string,
20 | allowsAutoKerning: PropTypes.oneOfType([
21 | PropTypes.bool,
22 | PropTypes.number
23 | ]),
24 | allowsAutoLineHeight: PropTypes.bool,
25 | ignorePrefix: PropTypes.string,
26 | ignoreSuffix: PropTypes.string
27 | }).isRequired
28 | }
29 |
30 | static getDerivedStateFromProps = (props, state) => {
31 | if (props.preferences !== state.preferences) {
32 | return { ...props.preferences }
33 | }
34 | return null
35 | }
36 |
37 | handleOnChange = (target) => {
38 | const { name } = target
39 | const newPref = (target.type === 'checkbox')
40 | ? target.checked
41 | : target.value
42 |
43 | this.state[name] = newPref
44 | pluginCall('setPreferences', this.state)
45 | }
46 |
47 | handleOnClickDirectoryButton = (evt) => {
48 | pluginCall(evt.target.name, this.state)
49 | }
50 |
51 | render() {
52 | const { route } = this.props
53 | const {
54 | userDefinedDirectory,
55 | allowsAutoKerning,
56 | allowsAutoLineHeight,
57 | ignorePrefix,
58 | ignoreSuffix
59 | } = this.state
60 | return (
61 |
62 |
86 |
100 |
116 |
117 | )
118 | }
119 | }
120 |
121 | export default SettingsPage
122 |
--------------------------------------------------------------------------------
/src/webview/pages/index.js:
--------------------------------------------------------------------------------
1 | import SettingsPage from './SettingsPage'
2 |
3 | const routes = [
4 | {
5 | exact: true,
6 | path: '/',
7 | component: SettingsPage,
8 | title: 'Plugin Settings'
9 | }
10 | ]
11 |
12 | export default routes
13 |
--------------------------------------------------------------------------------
/src/webview/style/globals/index.js:
--------------------------------------------------------------------------------
1 | import { css } from '@emotion/core'
2 |
3 | const globals = css`
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | html {
9 | line-height: 1.15;
10 | font-size: 100%;
11 | }
12 |
13 | html,
14 | body,
15 | h1,
16 | h2,
17 | h3,
18 | h4,
19 | h5,
20 | h6,
21 | p {
22 | margin: 0;
23 | padding: 0;
24 | }
25 | `
26 |
27 | export default globals
28 |
--------------------------------------------------------------------------------
/src/webview/style/variables/index.js:
--------------------------------------------------------------------------------
1 | import { generateFonts } from 'typesettings-js'
2 | import Typesettings from './typesettings'
3 |
4 | export const borders = [
5 | 0,
6 | '1px solid',
7 | '2px solid'
8 | ]
9 |
10 | export const colors = {
11 | black: {
12 | base: '#000000',
13 | l10: '#191919',
14 | l55: '#8C8C8C',
15 | l30: '#B2B2B2',
16 | l92: '#EBEBEB',
17 | l97: '#F7F7F7',
18 | l100: '#FFFFFF'
19 | },
20 | white: {
21 | base: '#FFFFFF',
22 | l10: '#E6E6E6',
23 | l55: '#737373',
24 | l30: '#4D4D4D',
25 | l92: '#141414',
26 | l97: '#080808',
27 | l100: '#000000'
28 | },
29 | brand: {
30 | base: '#03A87C'
31 | }
32 | }
33 |
34 | export const fonts = generateFonts(Typesettings)
35 |
36 | export const radii = [
37 | 0,
38 | '3px',
39 | '5px',
40 | '10px',
41 | '1000px'
42 | ]
43 |
44 | export const space = [
45 | '0px',
46 | '8px',
47 | '16px',
48 | '24px',
49 | '32px',
50 | '40px',
51 | '48px'
52 | ]
53 |
54 | export default {
55 | borders,
56 | colors,
57 | fonts,
58 | radii,
59 | space
60 | }
61 |
--------------------------------------------------------------------------------
/src/webview/style/variables/typesettings.js:
--------------------------------------------------------------------------------
1 | const typesettings = {
2 | family: '-apple-system',
3 | fallback: "BlinkMacSystemFont, 'Helvetica Neue', 'Arial', sans-serif",
4 | regular: {
5 | fontStyle: 'normal',
6 | fontWeight: 400,
7 | normalcase: {
8 | 10: {
9 | characterSpacing: 0,
10 | lineHeight: null,
11 | paragraphSpacing: 0
12 | },
13 | 12: {
14 | characterSpacing: -0.07999999821186066,
15 | lineHeight: 18,
16 | paragraphSpacing: 0
17 | },
18 | 14: {
19 | characterSpacing: -0.1500000059604645,
20 | lineHeight: 19,
21 | paragraphSpacing: 0
22 | }
23 | }
24 | },
25 | medium: {
26 | fontStyle: 'normal',
27 | fontWeight: 500,
28 | normalcase: {
29 | 10: {
30 | characterSpacing: 0,
31 | lineHeight: null,
32 | paragraphSpacing: 0
33 | },
34 | 12: {
35 | characterSpacing: -0.07999999821186066,
36 | lineHeight: null,
37 | paragraphSpacing: 0
38 | },
39 | 14: {
40 | characterSpacing: -0.1500000059604645,
41 | lineHeight: null,
42 | paragraphSpacing: 0
43 | }
44 | }
45 | },
46 | bold: {
47 | fontStyle: 'normal',
48 | fontWeight: 700,
49 | normalcase: {
50 | 10: {
51 | characterSpacing: 0,
52 | lineHeight: null,
53 | paragraphSpacing: 0
54 | },
55 | 12: {
56 | characterSpacing: -0.07999999821186066,
57 | lineHeight: null,
58 | paragraphSpacing: 0
59 | },
60 | 14: {
61 | characterSpacing: -0.1500000059604645,
62 | lineHeight: null,
63 | paragraphSpacing: 0
64 | }
65 | }
66 | }
67 | }
68 |
69 | export default typesettings
70 |
--------------------------------------------------------------------------------
/webpack.skpm.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const context = path.resolve(process.cwd(), 'src')
4 |
5 | module.exports = (config, isPluginCommand) => {
6 | config.context = context
7 | config.resolve.modules.push(context)
8 |
9 | if (!isPluginCommand) {
10 | config.resolve.extensions.push('.json', '.jsx')
11 | config.module.rules.push({
12 | test: /\.(html)$/,
13 | use: [
14 | { loader: '@skpm/extract-loader' },
15 | {
16 | loader: 'html-loader',
17 | options: {
18 | attrs: [
19 | 'img:src',
20 | 'link:href'
21 | ],
22 | interpolate: true
23 | }
24 | }
25 | ]
26 | })
27 | config.module.rules.push({
28 | test: /\.svg$/,
29 | use: [
30 | { loader: 'svg-inline-loader' }
31 | ]
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------