Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/kg-clean-basic-html/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
build/
cjs/
es/
tsconfig.tsbuildinfo
70 changes: 29 additions & 41 deletions packages/kg-clean-basic-html/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
import {fixupPluginRules} from '@eslint/compat';
import {defineConfig} from 'eslint/config';
import eslint from '@eslint/js';
import ghostPlugin from 'eslint-plugin-ghost';
import globals from 'globals';
import tseslint from 'typescript-eslint';

const ghost = fixupPluginRules(ghostPlugin);

export default [
{ignores: ['build/**', 'cjs/**', 'es/**']},
eslint.configs.recommended,
{
files: ['**/*.js'],
plugins: {ghost},
languageOptions: {
globals: {
...globals.node,
...globals.browser
}
},
rules: {
...ghostPlugin.configs.node.rules,
// match ESLint 8 behavior for catch clause variables
'no-unused-vars': ['error', {caughtErrors: 'none'}],
// disable rules incompatible with ESLint 9 flat config
'ghost/filenames/match-exported-class': 'off',
'ghost/filenames/match-exported': 'off',
'ghost/filenames/match-regex': 'off'
}
export default defineConfig([
{ ignores: ['build/**'] },
{
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
tseslint.configs.recommended,
],
languageOptions: {
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
},
plugins: { ghost: ghostPlugin },
rules: {
...ghostPlugin.configs.ts.rules,
'@typescript-eslint/no-explicit-any': 'error',
},
},
{
files: ['test/**/*.ts'],
rules: {
...ghostPlugin.configs['ts-test'].rules,
'ghost/mocha/no-global-tests': 'off',
'ghost/mocha/handle-done-callback': 'off',
'ghost/mocha/no-mocha-arrows': 'off',
'ghost/mocha/max-top-level-suites': 'off',
},
{
files: ['test/**/*.js'],
plugins: {ghost},
languageOptions: {
globals: {
...globals.node,
...globals.mocha,
should: true,
sinon: true
}
},
rules: {
...ghostPlugin.configs.test.rules
}
}
];
},
]);
44 changes: 25 additions & 19 deletions packages/kg-clean-basic-html/package.json
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
{
"name": "@tryghost/kg-clean-basic-html",
"version": "4.2.20",
"repository": "https://github.com/TryGhost/Koenig/tree/master/packages/kg-clean-basic-html",
"repository": "https://github.com/TryGhost/Koenig/tree/main/packages/kg-clean-basic-html",
"author": "Ghost Foundation",
"license": "MIT",
"main": "cjs/clean-basic-html.js",
"module": "es/clean-basic-html.js",
"source": "lib/clean-basic-html.js",
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/esm/index.d.ts",
"exports": {
".": {
"types": "./build/esm/index.d.ts",
"import": "./build/esm/index.js",
"require": "./build/cjs/index.js"
}
},
"scripts": {
"dev": "echo \"Implement me!\"",
"build": "rollup -c",
"prepare": "NODE_ENV=production yarn build",
"pretest": "yarn build",
"test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
"dev": "tsc --watch --preserveWatchOutput",
"build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json",
"prepare": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json",
"pretest": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json && tsc -p tsconfig.test.json",
"test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura tsx node_modules/.bin/mocha './test/**/*.test.ts'",
"lint": "eslint . --cache",
"posttest": "yarn lint"
},
"engines": {
"node": "^22.13.1 || ^24.0.0"
},
"files": [
"LICENSE",
"README.md",
"cjs/",
"es/",
"lib/"
"build"
],
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@babel/core": "7.29.0",
"@babel/preset-env": "7.29.0",
"@rollup/plugin-babel": "7.0.0",
"@eslint/js": "9.28.0",
"@types/mocha": "^10.0.0",
"@types/node": "^22.0.0",
"@types/sinon": "^17.0.0",
"c8": "11.0.0",
"jsdom": "29.0.0",
"mocha": "11.7.5",
"rollup": "4.59.0",
"should": "13.2.3",
"sinon": "21.0.2"
"sinon": "21.0.2",
"tsx": "^4.0.0",
"typescript": "^5.7.0",
"typescript-eslint": "8.33.1"
}
}
43 changes: 0 additions & 43 deletions packages/kg-clean-basic-html/rollup.config.mjs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,42 +1,32 @@
/**
* Removes any <code> wrappers around replacement strings {foo}
* Example input: <code><span>{foo}</span></code>
* Example output: <span>{foo}</span>
* @param {string} html
* @returns {string}
*/
function removeCodeWrappers(html) {
interface CleanBasicHtmlOptions {
allowBr?: boolean;
firstChildInnerContent?: boolean;
removeCodeWrappers?: boolean;
createDocument?: (html: string) => Document;
}

function removeCodeWrappers(html: string): string {
return html.replace(/<code\b[^>]*>((.*?){.*?}(.*?))<\/code>/gi, '$1');
}

/**
* Parses an html string and returns a cleaned version
* @param {string} html
* @param {Object} _options
* @param {boolean} [_options.allowBr] - if true, <br> tags will be kept
* @param {boolean} [_options.firstChildInnerContent] - if true, only the innerHTML of the first element will be returned
* @param {boolean} [_options.removeCodeWrappers] - if true, <code> wrappers around replacement strings {foo} will be removed
* @returns {string}
*/
export default function cleanBasicHtml(html = '', _options = {}) {
export default function cleanBasicHtml(html: string = '', _options: CleanBasicHtmlOptions = {}): string | null {
const defaults = {};
const options = Object.assign({}, defaults, _options);
const options: CleanBasicHtmlOptions = Object.assign({}, defaults, _options);

if (!options.createDocument) {
const Parser = (typeof DOMParser !== 'undefined' && DOMParser) || (typeof window !== 'undefined' && window.DOMParser);

if (!Parser) {
// eslint-disable-next-line ghost/ghost-custom/no-native-error
throw new Error('cleanBasicHtml() must be passed a `createDocument` function as an option when used in a non-browser environment');
}

options.createDocument = function (docHtml) {
options.createDocument = function (docHtml: string): Document {
const parser = new Parser();
return parser.parseFromString(docHtml, 'text/html');
};
}

let cleanHtml = html;
let cleanHtml: string = html;

if (!options.allowBr || cleanHtml === '<br>') {
cleanHtml = cleanHtml
Expand All @@ -55,7 +45,7 @@ export default function cleanBasicHtml(html = '', _options = {}) {

// remove any elements that have a blank textContent
if (cleanHtml) {
let doc = options.createDocument(cleanHtml);
const doc = options.createDocument(cleanHtml);

// don't analyze the document if it's empty (can result in storing <br> tags if allowed)
if (doc.body.textContent === '') {
Expand All @@ -64,17 +54,17 @@ export default function cleanBasicHtml(html = '', _options = {}) {

doc.body.querySelectorAll('*').forEach((element) => {
// Treat Zero Width Non-Joiner characters as spaces
if (!element.textContent.trim().replace(/\u200c+/g, '')) {
if (!element.textContent?.trim().replace(/\u200c+/g, '')) {
if (options.allowBr && element.tagName === 'BR') {
// keep it
return;
}
if (options.allowBr && element.querySelector('br')) {
return element.replaceWith(doc.createElement('br'));
}
if (element.textContent.length > 0) {
if (element.textContent && element.textContent.length > 0) {
// keep a single space to avoid collapsing spaces
let space = doc.createTextNode(' ');
const space = doc.createTextNode(' ');
return element.replaceWith(space);
}
return element.remove();
Expand Down
1 change: 1 addition & 0 deletions packages/kg-clean-basic-html/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from './clean-basic-html.js';
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');
// import testUtils from './utils/index.js';
import './utils/index.js';

const {JSDOM} = require('jsdom');
const cleanBasicHtml = require('../');
import {JSDOM} from 'jsdom';
import cleanBasicHtml from '../src/clean-basic-html.js';

describe('cleanBasicHtml', function () {
let options = {};
let options: {createDocument: (html: string) => Document};

before(function () {
options = {
createDocument(html) {
createDocument(html: string) {
return (new JSDOM(html)).window.document;
}
};
Expand All @@ -26,64 +26,64 @@ describe('cleanBasicHtml', function () {
const html = ' <br>&nbsp;&nbsp; &nbsp;';
const result = cleanBasicHtml(html, options);

result.should.equal('');
(result as string).should.equal('');
});

it('keeps whitespace between text', function () {
const html = '&nbsp; <br>Testing &nbsp;Significant Whitespace<br />&nbsp;';
const result = cleanBasicHtml(html, options);

result.should.equal('Testing Significant Whitespace');
(result as string).should.equal('Testing Significant Whitespace');
});

it('removes DOM elements with blank text content', function () {
const html = '&nbsp; <p> &nbsp;&nbsp;<br></p>';
const result = cleanBasicHtml(html, options);

result.should.equal('');
(result as string).should.equal('');
});

it('keeps elements with text content', function () {
const html = ' &nbsp;<strong> Test&nbsp;</strong> ';
const result = cleanBasicHtml(html, options);

result.should.equal('<strong> Test&nbsp;</strong>');
(result as string).should.equal('<strong> Test&nbsp;</strong>');
});

it('can extract first element content', function () {
const html = '<p><span>Headline</span> <em>italic</em></p>';
const result = cleanBasicHtml(html, {...options, firstChildInnerContent: true});

result.should.equal('<span>Headline</span> <em>italic</em>');
(result as string).should.equal('<span>Headline</span> <em>italic</em>');
});

it('return empty string if firstChildInnerContent option enabled and there is no first child element ', function () {
const html = '';
const result = cleanBasicHtml(html, {...options, firstChildInnerContent: true});

result.should.equal('');
(result as string).should.equal('');
});

describe('options.removeCodeWrappers', function () {
it('removes any <code> wrappers around replacement strings {foo}', function () {
const html = '<code><span>{foo}</span></code>';
const result = cleanBasicHtml(html, {...options, removeCodeWrappers: true});

result.should.equal('<span>{foo}</span>');
(result as string).should.equal('<span>{foo}</span>');
});

it('removes any <code> wrappers around replacement strings <span>{foo}</span>', function () {
const html = '<code><span>{foo}</span></code>';
const result = cleanBasicHtml(html, {...options, removeCodeWrappers: true});

result.should.equal('<span>{foo}</span>');
(result as string).should.equal('<span>{foo}</span>');
});

it('removes any <code> wrappers around replacement strings {foo, "default"}', function () {
const html = '<p>Hey <code>{first_name, "there"}</code>,</p>';
const result = cleanBasicHtml(html, {...options, removeCodeWrappers: true});

result.should.equal('<p>Hey {first_name, "there"},</p>');
(result as string).should.equal('<p>Hey {first_name, "there"},</p>');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

// Require overrides - these add globals for tests
require('./overrides');
import './overrides.js';

// Require assertions - adds custom should assertions
require('./assertions');
import './assertions.js';
10 changes: 0 additions & 10 deletions packages/kg-clean-basic-html/test/utils/overrides.js

This file was deleted.

Loading
Loading