Skip to content

Commit b52b871

Browse files
committed
Migrated @tryghost/custom-fonts code and history from Ghost repo
fix https://linear.app/ghost/issue/ENG-2401/custom-fonts - this commit moves the code and history for custom-fonts from the Ghost repo to the SDK repo as we're moving packages out of Ghost
2 parents 224b121 + 8964d05 commit b52b871

File tree

8 files changed

+488
-0
lines changed

8 files changed

+488
-0
lines changed

packages/custom-fonts/.eslintrc.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
plugins: ['ghost'],
4+
extends: [
5+
'plugin:ghost/node'
6+
]
7+
};

packages/custom-fonts/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Custom Fonts
2+
3+
Custom fonts mapping module
4+
5+
6+
## Usage
7+
8+
9+
## Develop
10+
11+
This is a monorepo package.
12+
13+
Follow the instructions for the top-level repo.
14+
1. `git clone` this repo & `cd` into it as usual
15+
2. Run `yarn` to install top-level dependencies.
16+
17+
18+
19+
## Test
20+
21+
- `yarn lint` run just eslint
22+
- `yarn test` run lint and tests
23+

packages/custom-fonts/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@tryghost/custom-fonts",
3+
"version": "0.0.0",
4+
"repository": "https://github.com/TryGhost/Ghost/tree/main/packages/custom-fonts",
5+
"author": "Ghost Foundation",
6+
"private": true,
7+
"main": "./build/cjs/index.js",
8+
"module": "./build/esm/index.js",
9+
"types": "./build/types/index.d.ts",
10+
"exports": {
11+
".": {
12+
"import": "./build/esm/index.js",
13+
"require": "./build/cjs/index.js",
14+
"types": "./build/types/index.d.ts"
15+
}
16+
},
17+
"scripts": {
18+
"dev": "tsc --watch --preserveWatchOutput --sourceMap",
19+
"build": "yarn build:cjs && yarn build:esm && yarn build:types",
20+
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly --declaration --declarationDir ./build/types",
21+
"build:cjs": "tsc -p tsconfig.json --outDir ./build/cjs --module CommonJS",
22+
"build:esm": "tsc -p tsconfig.esm.json --outDir ./build/esm --module ES2020",
23+
"build:ts": "yarn build",
24+
"prepare": "yarn build",
25+
"test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
26+
"test": "yarn test:ts && yarn test:unit",
27+
"test:ts": "tsc --noEmit",
28+
"lint:code": "eslint src/ --ext .ts --cache",
29+
"lint": "yarn lint:code && yarn lint:test",
30+
"lint:test": "eslint -c test/.eslintrc.js test/ --ext .ts --cache"
31+
},
32+
"files": [
33+
"build"
34+
],
35+
"devDependencies": {
36+
"c8": "10.1.2",
37+
"mocha": "10.8.2",
38+
"sinon": "19.0.5",
39+
"ts-node": "10.9.2",
40+
"typescript": "5.8.3"
41+
},
42+
"dependencies": {}
43+
}

packages/custom-fonts/src/index.ts

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
export type BodyFontName =
2+
| 'Fira Mono'
3+
| 'Fira Sans'
4+
| 'IBM Plex Serif'
5+
| 'Inter'
6+
| 'JetBrains Mono'
7+
| 'Lora'
8+
| 'Manrope'
9+
| 'Merriweather'
10+
| 'Nunito'
11+
| 'Noto Sans'
12+
| 'Noto Serif'
13+
| 'Poppins'
14+
| 'Roboto'
15+
| 'Space Mono';
16+
17+
export type HeadingFontName =
18+
| 'Cardo'
19+
| 'Chakra Petch'
20+
| 'Old Standard TT'
21+
| 'Libre Baskerville'
22+
| 'Rufina'
23+
| 'Space Grotesk'
24+
| 'Tenor Sans'
25+
| BodyFontName;
26+
27+
export type Font<T extends string> = {
28+
name: T;
29+
creator: string;
30+
};
31+
32+
export type HeadingFont = Font<HeadingFontName>;
33+
export type BodyFont = Font<BodyFontName>;
34+
35+
export type CustomFonts = {
36+
heading: HeadingFont[];
37+
body: BodyFont[];
38+
};
39+
40+
export type FontSelection = {
41+
heading?: HeadingFontName;
42+
body?: BodyFontName;
43+
};
44+
45+
export const CUSTOM_FONTS: CustomFonts = {
46+
heading: [
47+
{name: 'Cardo', creator: 'David Perry'},
48+
{name: 'Chakra Petch', creator: 'Cadson Demak'},
49+
{name: 'Fira Mono', creator: 'Carrois Apostrophe'},
50+
{name: 'Fira Sans', creator: 'Carrois Apostrophe'},
51+
{name: 'IBM Plex Serif', creator: 'Mike Abbink'},
52+
{name: 'Inter', creator: 'Rasmus Andersson'},
53+
{name: 'JetBrains Mono', creator: 'JetBrains'},
54+
{name: 'Libre Baskerville', creator: 'Impallari Type'},
55+
{name: 'Lora', creator: 'Cyreal'},
56+
{name: 'Manrope', creator: 'Mikhail Sharanda'},
57+
{name: 'Merriweather', creator: 'Sorkin Type'},
58+
{name: 'Noto Sans', creator: 'Google'},
59+
{name: 'Noto Serif', creator: 'Google'},
60+
{name: 'Nunito', creator: 'Vernon Adams'},
61+
{name: 'Old Standard TT', creator: 'Alexey Kryukov'},
62+
{name: 'Poppins', creator: 'Indian Type Foundry'},
63+
{name: 'Roboto', creator: 'Christian Robertson'},
64+
{name: 'Rufina', creator: 'Martin Sommaruga'},
65+
{name: 'Space Grotesk', creator: 'Florian Karsten'},
66+
{name: 'Space Mono', creator: 'Colophon Foundry'},
67+
{name: 'Tenor Sans', creator: 'Denis Masharov'}
68+
],
69+
body: [
70+
{name: 'Fira Mono', creator: 'Carrois Apostrophe'},
71+
{name: 'Fira Sans', creator: 'Carrois Apostrophe'},
72+
{name: 'IBM Plex Serif', creator: 'Mike Abbink'},
73+
{name: 'Inter', creator: 'Rasmus Andersson'},
74+
{name: 'JetBrains Mono', creator: 'JetBrains'},
75+
{name: 'Lora', creator: 'Cyreal'},
76+
{name: 'Manrope', creator: 'Mikhail Sharanda'},
77+
{name: 'Merriweather', creator: 'Sorkin Type'},
78+
{name: 'Noto Sans', creator: 'Google'},
79+
{name: 'Noto Serif', creator: 'Google'},
80+
{name: 'Nunito', creator: 'Vernon Adams'},
81+
{name: 'Poppins', creator: 'Indian Type Foundry'},
82+
{name: 'Roboto', creator: 'Christian Robertson'},
83+
{name: 'Space Mono', creator: 'Colophon Foundry'}
84+
]
85+
};
86+
87+
const classFontNames = {
88+
Cardo: 'cardo',
89+
Manrope: 'manrope',
90+
Merriweather: 'merriweather',
91+
Nunito: 'nunito',
92+
'Old Standard TT': 'old-standard-tt',
93+
Roboto: 'roboto',
94+
Rufina: 'rufina',
95+
'Tenor Sans': 'tenor-sans',
96+
'Space Grotesk': 'space-grotesk',
97+
'Chakra Petch': 'chakra-petch',
98+
'Noto Sans': 'noto-sans',
99+
Poppins: 'poppins',
100+
'Fira Sans': 'fira-sans',
101+
Inter: 'inter',
102+
'Noto Serif': 'noto-serif',
103+
Lora: 'lora',
104+
'IBM Plex Serif': 'ibm-plex-serif',
105+
'Space Mono': 'space-mono',
106+
'Fira Mono': 'fira-mono',
107+
'JetBrains Mono': 'jetbrains-mono',
108+
'Libre Baskerville': 'libre-baskerville'
109+
};
110+
111+
export function generateCustomFontCss(fonts: FontSelection) {
112+
let fontImports: string = '';
113+
let fontCSS: string = '';
114+
115+
const importStrings = {
116+
Cardo: {
117+
family: 'cardo:400,700'
118+
},
119+
Manrope: {
120+
family: 'manrope:300,500,700'
121+
},
122+
Merriweather: {
123+
family: 'merriweather:300,700'
124+
},
125+
Nunito: {
126+
family: 'nunito:400,600,700'
127+
},
128+
'Old Standard TT': {
129+
family: 'old-standard-tt:400,700'
130+
},
131+
Roboto: {
132+
family: 'roboto:400,500,700'
133+
},
134+
Rufina: {
135+
family: 'rufina:400,500,700'
136+
},
137+
'Tenor Sans': {
138+
family: 'tenor-sans:400'
139+
},
140+
'Space Grotesk': {
141+
family: 'space-grotesk:700'
142+
},
143+
'Chakra Petch': {
144+
family: 'chakra-petch:400'
145+
},
146+
'Noto Sans': {
147+
family: 'noto-sans:400,700'
148+
},
149+
Poppins: {
150+
family: 'poppins:400,500,600'
151+
},
152+
'Fira Sans': {
153+
family: 'fira-sans:400,500,600'
154+
},
155+
Inter: {
156+
family: 'inter:400,500,600'
157+
},
158+
'Noto Serif': {
159+
family: 'noto-serif:400,700'
160+
},
161+
Lora: {
162+
family: 'lora:400,700'
163+
},
164+
'IBM Plex Serif': {
165+
family: 'ibm-plex-serif:400,500,600'
166+
},
167+
'Space Mono': {
168+
family: 'space-mono:400,700'
169+
},
170+
'Fira Mono': {
171+
family: 'fira-mono:400,700'
172+
},
173+
'JetBrains Mono': {
174+
family: 'jetbrains-mono:400,700'
175+
},
176+
'Libre Baskerville': {
177+
family: 'libre-baskerville:700'
178+
}
179+
};
180+
181+
if (fonts?.heading && fonts?.body && fonts?.heading === fonts?.body) {
182+
fontImports = `<link rel="stylesheet" href="https://fonts.bunny.net/css?family=${importStrings[fonts?.heading]?.family}">`;
183+
} else {
184+
fontImports = '';
185+
186+
if (fonts?.heading && fonts?.body) {
187+
fontImports += `<link rel="stylesheet" href="https://fonts.bunny.net/css?family=${importStrings[fonts?.heading]?.family}|${importStrings[fonts?.body]?.family}">`;
188+
} else {
189+
if (fonts?.heading) {
190+
fontImports += `<link rel="stylesheet" href="https://fonts.bunny.net/css?family=${importStrings[fonts?.heading]?.family}">`;
191+
}
192+
193+
if (fonts?.body) {
194+
fontImports += `<link rel="stylesheet" href="https://fonts.bunny.net/css?family=${importStrings[fonts?.body]?.family}">`;
195+
}
196+
}
197+
}
198+
199+
if (fonts?.body || fonts?.heading) {
200+
fontCSS = ':root {';
201+
202+
if (fonts?.heading) {
203+
fontCSS += `--gh-font-heading: ${fonts.heading};`;
204+
}
205+
206+
if (fonts?.body) {
207+
fontCSS += `--gh-font-body: ${fonts.body};`;
208+
}
209+
210+
fontCSS += '}';
211+
}
212+
213+
return `<link rel="preconnect" href="https://fonts.bunny.net">${fontImports}<style>${fontCSS}</style>`;
214+
}
215+
216+
export function generateCustomFontBodyClass(fonts: FontSelection) {
217+
let bodyClass = '';
218+
219+
if (fonts?.heading) {
220+
bodyClass += getCustomFontClassName({font: fonts.heading, heading: true});
221+
if (fonts?.body) {
222+
bodyClass += ' ';
223+
}
224+
}
225+
226+
if (fonts?.body) {
227+
bodyClass += getCustomFontClassName({font: fonts.body, heading: false});
228+
}
229+
230+
return bodyClass;
231+
}
232+
233+
export function getCSSFriendlyFontClassName(font: string) {
234+
return classFontNames[font as keyof typeof classFontNames] || '';
235+
}
236+
237+
export function getCustomFontClassName({font, heading}: {font: string, heading: boolean}) {
238+
const cssFriendlyFontClassName = getCSSFriendlyFontClassName(font);
239+
240+
if (!cssFriendlyFontClassName) {
241+
return '';
242+
}
243+
244+
return `gh-font-${heading ? 'heading' : 'body'}-${cssFriendlyFontClassName}`;
245+
}
246+
247+
export function getCustomFonts(): CustomFonts {
248+
return CUSTOM_FONTS;
249+
}
250+
251+
export function isValidCustomFont(fontName: string): fontName is BodyFontName {
252+
return CUSTOM_FONTS.body.some(font => font.name === fontName);
253+
}
254+
255+
export function isValidCustomHeadingFont(fontName: string): fontName is HeadingFontName {
256+
return CUSTOM_FONTS.heading.some(font => font.name === fontName);
257+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
plugins: ['ghost'],
4+
extends: [
5+
'plugin:ghost/test'
6+
]
7+
};

0 commit comments

Comments
 (0)