Skip to content

Commit f04e307

Browse files
committed
Added types to all content format transformation utilities
1 parent d2c67cb commit f04e307

33 files changed

+654
-272
lines changed

packages/url-utils/.eslintrc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ module.exports = {
33
extends: [
44
'plugin:ghost/ts'
55
],
6+
rules: {
7+
// Allow 'any' types for backward compatibility with existing API
8+
'@typescript-eslint/no-explicit-any': 'warn',
9+
// Allow unused vars that start with underscore (common pattern for intentionally unused params)
10+
'@typescript-eslint/no-unused-vars': ['error', {
11+
argsIgnorePattern: '^_',
12+
varsIgnorePattern: '^_'
13+
}]
14+
},
615
overrides: [
716
{
817
// Files with @ts-nocheck are intentionally not fully migrated yet
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
// @ts-nocheck
2-
const htmlTransform = require('./html-transform');
3-
const absoluteToRelative = require('./absolute-to-relative');
1+
import htmlTransform from './html-transform';
2+
import absoluteToRelative from './absolute-to-relative';
43

5-
function htmlAbsoluteToRelative(html = '', siteUrl, _options) {
6-
const defaultOptions = {assetsOnly: false, ignoreProtocol: true};
4+
interface HtmlAbsoluteToRelativeOptions {
5+
assetsOnly?: boolean;
6+
ignoreProtocol?: boolean;
7+
earlyExitMatchStr?: string;
8+
}
9+
10+
function htmlAbsoluteToRelative(html: string = '', siteUrl: string, _options: HtmlAbsoluteToRelativeOptions = {}): string {
11+
const defaultOptions: Required<Pick<HtmlAbsoluteToRelativeOptions, 'assetsOnly' | 'ignoreProtocol'>> = {assetsOnly: false, ignoreProtocol: true};
712
const options = Object.assign({}, defaultOptions, _options || {});
813

914
// exit early and avoid parsing if the content does not contain the siteUrl
1015
options.earlyExitMatchStr = options.ignoreProtocol ? siteUrl.replace(/http:|https:/, '') : siteUrl;
1116
options.earlyExitMatchStr = options.earlyExitMatchStr.replace(/\/$/, '');
1217

1318
// need to ignore itemPath because absoluteToRelative doesn't take that option
14-
const transformFunction = function (_url, _siteUrl, _itemPath, __options) {
19+
const transformFunction = function (_url: string, _siteUrl: string, _itemPath: string, __options: any): string {
1520
return absoluteToRelative(_url, _siteUrl, __options);
1621
};
1722

1823
return htmlTransform(html, siteUrl, transformFunction, '', options);
1924
}
2025

26+
export default htmlAbsoluteToRelative;
2127
module.exports = htmlAbsoluteToRelative;
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
// @ts-nocheck
2-
const htmlTransform = require('./html-transform');
3-
const absoluteToTransformReady = require('./absolute-to-transform-ready');
1+
import htmlTransform from './html-transform';
2+
import absoluteToTransformReady from './absolute-to-transform-ready';
43

5-
const htmlAbsoluteToTransformReady = function (html = '', siteUrl, _options) {
6-
const defaultOptions = {assetsOnly: false, ignoreProtocol: true};
4+
interface HtmlAbsoluteToTransformReadyOptions {
5+
assetsOnly?: boolean;
6+
ignoreProtocol?: boolean;
7+
earlyExitMatchStr?: string;
8+
}
9+
10+
const htmlAbsoluteToTransformReady = function (html: string = '', siteUrl: string, _options: HtmlAbsoluteToTransformReadyOptions = {}): string {
11+
const defaultOptions: Required<Pick<HtmlAbsoluteToTransformReadyOptions, 'assetsOnly' | 'ignoreProtocol'>> = {assetsOnly: false, ignoreProtocol: true};
712
const options = Object.assign({}, defaultOptions, _options || {});
813

914
// exit early and avoid parsing if the content does not contain the siteUrl
1015
options.earlyExitMatchStr = options.ignoreProtocol ? siteUrl.replace(/http:|https:/, '') : siteUrl;
1116
options.earlyExitMatchStr = options.earlyExitMatchStr.replace(/\/$/, '');
1217

1318
// need to ignore itemPath because absoluteToRelative doesn't take that option
14-
const transformFunction = function (_url, _siteUrl, _itemPath, __options) {
19+
const transformFunction = function (_url: string, _siteUrl: string, _itemPath: string, __options: any): string {
1520
return absoluteToTransformReady(_url, _siteUrl, __options);
1621
};
1722

1823
return htmlTransform(html, siteUrl, transformFunction, '', options);
1924
};
2025

26+
export default htmlAbsoluteToTransformReady;
2127
module.exports = htmlAbsoluteToTransformReady;
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
// @ts-nocheck
2-
const htmlTransform = require('./html-transform');
3-
const relativeToAbsolute = require('./relative-to-absolute');
1+
import htmlTransform from './html-transform';
2+
import relativeToAbsolute from './relative-to-absolute';
43

5-
function htmlRelativeToAbsolute(html = '', siteUrl, itemPath, _options) {
6-
const defaultOptions = {assetsOnly: false, secure: false};
4+
interface HtmlRelativeToAbsoluteOptions {
5+
assetsOnly?: boolean;
6+
secure?: boolean;
7+
staticImageUrlPrefix?: string;
8+
earlyExitMatchStr?: string;
9+
}
10+
11+
function htmlRelativeToAbsolute(html: string = '', siteUrl: string, itemPath?: string, _options: HtmlRelativeToAbsoluteOptions = {}): string {
12+
const defaultOptions: Required<Pick<HtmlRelativeToAbsoluteOptions, 'assetsOnly' | 'secure'>> = {assetsOnly: false, secure: false};
713
const options = Object.assign({}, defaultOptions, _options || {});
814

915
// exit early and avoid parsing if the content does not contain an attribute we might transform
@@ -12,7 +18,8 @@ function htmlRelativeToAbsolute(html = '', siteUrl, itemPath, _options) {
1218
options.earlyExitMatchStr = options.staticImageUrlPrefix;
1319
}
1420

15-
return htmlTransform(html, siteUrl, relativeToAbsolute, itemPath, options);
21+
return htmlTransform(html, siteUrl, relativeToAbsolute, itemPath || '', options);
1622
}
1723

24+
export default htmlRelativeToAbsolute;
1825
module.exports = htmlRelativeToAbsolute;
Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
1-
// @ts-nocheck
2-
const htmlTransform = require('./html-transform');
3-
const relativeToTransformReady = require('./relative-to-transform-ready');
1+
import htmlTransform from './html-transform';
2+
import relativeToTransformReady from './relative-to-transform-ready';
43

5-
const htmlRelativeToTransformReady = function (html = '', root, itemPath, _options) {
4+
interface HtmlRelativeToTransformReadyOptions {
5+
replacementStr?: string;
6+
secure?: boolean;
7+
assetsOnly?: boolean;
8+
staticImageUrlPrefix?: string;
9+
earlyExitMatchStr?: string;
10+
}
11+
12+
const htmlRelativeToTransformReady = function (
13+
html: string = '',
14+
root: string,
15+
itemPath?: string | HtmlRelativeToTransformReadyOptions | null,
16+
_options?: HtmlRelativeToTransformReadyOptions
17+
): string {
618
// itemPath is optional, if it's an object may be the options param instead
7-
if (typeof itemPath === 'object' && !_options) {
8-
_options = itemPath;
9-
itemPath = null;
19+
let actualItemPath: string | null = null;
20+
let actualOptions: HtmlRelativeToTransformReadyOptions;
21+
22+
if (itemPath && typeof itemPath === 'object' && !_options) {
23+
actualOptions = itemPath;
24+
actualItemPath = null;
25+
} else {
26+
actualOptions = _options || {};
27+
actualItemPath = typeof itemPath === 'string' ? itemPath : null;
1028
}
1129

12-
const defaultOptions = {
30+
const defaultOptions: Required<Pick<HtmlRelativeToTransformReadyOptions, 'replacementStr'>> = {
1331
replacementStr: '__GHOST_URL__'
1432
};
1533
const overrideOptions = {
1634
secure: false
1735
};
18-
const options = Object.assign({}, defaultOptions, _options, overrideOptions);
36+
const options = Object.assign({}, defaultOptions, actualOptions, overrideOptions);
1937

2038
// exit early and avoid parsing if the content does not contain an attribute we might transform
2139
options.earlyExitMatchStr = 'href=|src=|srcset=';
2240
if (options.assetsOnly) {
2341
options.earlyExitMatchStr = options.staticImageUrlPrefix;
2442
}
2543

26-
return htmlTransform(html, root, relativeToTransformReady, itemPath, options);
44+
return htmlTransform(html, root, relativeToTransformReady, actualItemPath || '', options);
2745
};
2846

47+
export default htmlRelativeToTransformReady;
2948
module.exports = htmlRelativeToTransformReady;
Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1-
// @ts-nocheck
2-
const htmlRelativeToAbsolute = require('./html-relative-to-absolute');
3-
const htmlAbsoluteToTransformReady = require('./html-absolute-to-transform-ready');
1+
import htmlRelativeToAbsolute from './html-relative-to-absolute';
2+
import htmlAbsoluteToTransformReady from './html-absolute-to-transform-ready';
43

5-
function htmlToTransformReady(html, siteUrl, itemPath, options) {
6-
if (typeof itemPath === 'object' && !options) {
7-
options = itemPath;
8-
itemPath = null;
4+
interface HtmlToTransformReadyOptions {
5+
assetsOnly?: boolean;
6+
secure?: boolean;
7+
ignoreProtocol?: boolean;
8+
staticImageUrlPrefix?: string;
9+
}
10+
11+
function htmlToTransformReady(
12+
html: string,
13+
siteUrl: string,
14+
itemPath?: string | HtmlToTransformReadyOptions | null,
15+
options?: HtmlToTransformReadyOptions
16+
): string {
17+
let actualItemPath: string | null = null;
18+
let actualOptions: HtmlToTransformReadyOptions;
19+
20+
if (itemPath && typeof itemPath === 'object' && !options) {
21+
actualOptions = itemPath;
22+
actualItemPath = null;
23+
} else {
24+
actualOptions = options || {};
25+
actualItemPath = typeof itemPath === 'string' ? itemPath : null;
926
}
10-
const absolute = htmlRelativeToAbsolute(html, siteUrl, itemPath, options);
11-
return htmlAbsoluteToTransformReady(absolute, siteUrl, options);
27+
const absolute = htmlRelativeToAbsolute(html, siteUrl, actualItemPath || '', actualOptions);
28+
return htmlAbsoluteToTransformReady(absolute, siteUrl, actualOptions);
1229
}
1330

31+
export default htmlToTransformReady;
1432
module.exports = htmlToTransformReady;

packages/url-utils/src/utils/html-transform.ts

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
1-
// @ts-nocheck
2-
function escapeRegExp(string) {
1+
import * as cheerio from 'cheerio';
2+
3+
type TransformFunction = (url: string, siteUrl: string, itemPath: string, options: HtmlTransformOptions) => string;
4+
5+
interface HtmlTransformOptions {
6+
assetsOnly?: boolean;
7+
secure?: boolean;
8+
earlyExitMatchStr?: string;
9+
}
10+
11+
interface Replacement {
12+
name: string;
13+
originalValue: string;
14+
transformedValue?: string;
15+
skip?: boolean;
16+
}
17+
18+
function escapeRegExp(string: string): string {
319
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
420
}
521

6-
function extractSrcsetUrls(srcset = '') {
22+
function extractSrcsetUrls(srcset: string = ''): string[] {
723
return srcset.split(',').map((part) => {
824
return part.trim().split(/\s+/)[0];
925
});
1026
}
1127

12-
function extractStyleUrls(style = '') {
13-
const urls = [];
28+
function extractStyleUrls(style: string = ''): string[] {
29+
const urls: string[] = [];
1430
const regex = /url\(['|"]([^)]+)['|"]\)/g;
15-
let match;
31+
let match: RegExpExecArray | null;
1632

1733
while ((match = regex.exec(style)) !== null) {
1834
urls.push(match[1]);
@@ -21,15 +37,20 @@ function extractStyleUrls(style = '') {
2137
return urls;
2238
}
2339

24-
function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options) {
25-
const defaultOptions = {assetsOnly: false, secure: false};
40+
function htmlTransform(
41+
html: string = '',
42+
siteUrl: string,
43+
transformFunction: TransformFunction,
44+
itemPath: string,
45+
_options: HtmlTransformOptions = {}
46+
): string {
47+
const defaultOptions: Required<Pick<HtmlTransformOptions, 'assetsOnly' | 'secure'>> = {assetsOnly: false, secure: false};
2648
const options = Object.assign({}, defaultOptions, _options || {});
2749

2850
if (!html || (options.earlyExitMatchStr && !html.match(new RegExp(options.earlyExitMatchStr)))) {
2951
return html;
3052
}
3153

32-
const cheerio = require('cheerio');
3354
const htmlContent = cheerio.load(html, {decodeEntities: false});
3455

3556
// replacements is keyed with the attr name + original relative value so
@@ -42,9 +63,9 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
4263
// {name: 'href', originalValue: '/test', absoluteValue: '.../test'},
4364
// ]
4465
// }
45-
const replacements = {};
66+
const replacements: Record<string, Replacement[]> = {};
4667

47-
function addReplacement(replacement) {
68+
function addReplacement(replacement: Replacement): void {
4869
const key = `${replacement.name}="${replacement.originalValue}"`;
4970

5071
if (!replacements[key]) {
@@ -55,23 +76,29 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
5576
}
5677

5778
// find all of the relative url attributes that we care about
58-
['href', 'src', 'srcset', 'style'].forEach((attributeName) => {
59-
htmlContent('[' + attributeName + ']').each((ix, el) => {
79+
(['href', 'src', 'srcset', 'style'] as const).forEach((attributeName) => {
80+
htmlContent('[' + attributeName + ']').each((_ix, el) => {
81+
const $el = htmlContent(el);
6082
// ignore <stream> elems and html inside of <code> elements
61-
if (el.name === 'stream' || htmlContent(el).closest('code').length) {
62-
addReplacement({
63-
name: attributeName,
64-
originalValue: htmlContent(el).attr(attributeName),
65-
skip: true
66-
});
83+
if ((el as any).name === 'stream' || $el.closest('code').length) {
84+
const attrValue = $el.attr(attributeName);
85+
if (attrValue) {
86+
addReplacement({
87+
name: attributeName,
88+
originalValue: attrValue,
89+
skip: true
90+
});
91+
}
6792
return;
6893
}
6994

70-
el = htmlContent(el);
71-
const originalValue = el.attr(attributeName);
95+
const originalValue = $el.attr(attributeName);
96+
if (!originalValue) {
97+
return;
98+
}
7299

73100
if (attributeName === 'srcset' || attributeName === 'style') {
74-
let urls;
101+
let urls: string[];
75102

76103
if (attributeName === 'srcset') {
77104
urls = extractSrcsetUrls(originalValue);
@@ -83,7 +110,7 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
83110

84111
urls.forEach((url, i) => {
85112
if (absoluteUrls[i]) {
86-
let regex = new RegExp(escapeRegExp(url), 'g');
113+
const regex = new RegExp(escapeRegExp(url), 'g');
87114
transformedValue = transformedValue.replace(regex, absoluteUrls[i]);
88115
}
89116
});
@@ -114,7 +141,7 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
114141
for (const [, attrs] of Object.entries(replacements)) {
115142
let skipCount = 0;
116143

117-
attrs.forEach(({skip, name, originalValue, transformedValue}) => {
144+
attrs.forEach(({skip, name, originalValue, transformedValue}: Replacement) => {
118145
if (skip) {
119146
skipCount += 1;
120147
return;
@@ -129,7 +156,7 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
129156
let matchCount = 0;
130157
html = html.replace(regex, (match, p1) => {
131158
let result = match;
132-
if (matchCount === skipCount) {
159+
if (matchCount === skipCount && transformedValue) {
133160
result = match.replace(p1, p1.replace(originalValue, transformedValue));
134161
}
135162
matchCount += 1;
@@ -141,4 +168,5 @@ function htmlTransform(html = '', siteUrl, transformFunction, itemPath, _options
141168
return html;
142169
}
143170

171+
export default htmlTransform;
144172
module.exports = htmlTransform;

0 commit comments

Comments
 (0)