Add text limit to payment method descriptions in the page editor (https://github.com/woocommerce/woocommerce-blocks/pull/9708)

* Convert summary/utils to TS

* Add @wordpress/wordcount type defs

* Move trimWords, trimCharacters, remoteTags & appendMoreText to own file

* Add tests for trimWords and related functions

* Trim payment method description if it is longer than 30 words
This commit is contained in:
Thomas Roberts 2023-06-07 12:29:15 +03:00 committed by GitHub
parent cbfd2977dd
commit aa1fe5c308
7 changed files with 221 additions and 63 deletions

View File

@ -0,0 +1,60 @@
/**
* External dependencies
*/
import { autop } from '@wordpress/autop';
import { trimCharacters, trimWords } from '@woocommerce/utils';
import { count, CountType } from '@wordpress/wordcount';
/**
* Get first paragraph from some HTML text, or return whole string.
*
* @param {string} source Source text.
* @return {string} First paragraph found in string.
*/
const getFirstParagraph = ( source: string ) => {
const pIndex = source.indexOf( '</p>' );
if ( pIndex === -1 ) {
return source;
}
return source.substr( 0, pIndex + 4 );
};
/**
* Generates the summary text from a string of text.
*
* @param {string} source Source text.
* @param {number} maxLength Limit number of countType returned if text has multiple paragraphs.
* @param {string} countType What is being counted. One of words, characters_excluding_spaces, or characters_including_spaces.
* @return {string} Generated summary.
*/
export const generateSummary = (
source: string,
maxLength = 15,
countType: CountType = 'words'
) => {
const sourceWithParagraphs = autop( source );
const sourceWordCount = count( sourceWithParagraphs, countType );
if ( sourceWordCount <= maxLength ) {
return sourceWithParagraphs;
}
const firstParagraph = getFirstParagraph( sourceWithParagraphs );
const firstParagraphWordCount = count( firstParagraph, countType );
if ( firstParagraphWordCount <= maxLength ) {
return firstParagraph;
}
if ( countType === 'words' ) {
return trimWords( firstParagraph, maxLength );
}
return trimCharacters(
firstParagraph,
maxLength,
countType === 'characters_including_spaces'
);
};

View File

@ -12,6 +12,8 @@ import Noninteractive from '@woocommerce/base-components/noninteractive';
import { GlobalPaymentMethod } from '@woocommerce/types';
import { useSelect } from '@wordpress/data';
import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
import { blocksConfig } from '@woocommerce/block-settings';
import { trimCharacters, trimWords } from '@woocommerce/utils';
/**
* Internal dependencies
@ -49,6 +51,7 @@ export const Edit = ( {
'Incompatible with block-based checkout',
'woo-gutenberg-products-block'
);
const wordCountType = blocksConfig.wordCountType;
return (
<FormStepBlock
@ -77,12 +80,32 @@ export const Edit = ( {
const isIncompatible =
!! incompatiblePaymentMethods[ method.id ];
let trimmedDescription;
if ( wordCountType === 'words' ) {
trimmedDescription = trimWords(
method.description,
30,
undefined,
false
);
} else {
trimmedDescription = trimCharacters(
method.description,
30,
wordCountType ===
'characters_including_spaces',
undefined,
false
);
}
return (
<ExternalLinkCard
key={ method.id }
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=checkout&section=${ method.id }` }
title={ method.title }
description={ method.description }
description={ trimmedDescription }
{ ...( isIncompatible
? {
warning:

View File

@ -9,3 +9,4 @@ export * from './shared-attributes';
export * from './sanitize-html';
export * from './is-site-editor-page';
export * from './is-widget-editor-page';
export * from './trim-words';

View File

@ -0,0 +1,91 @@
/**
* External dependencies
*/
import {
appendMoreText,
removeTags,
trimCharacters,
trimWords,
} from '@woocommerce/utils';
describe( 'trim-words', () => {
describe( 'removeTags', () => {
it( 'Removes HTML tags from a string', () => {
const string = '<div><a href="/index.php">trim-words.ts</a></div>';
const trimmedString = removeTags( string );
expect( trimmedString ).toEqual( 'trim-words.ts' );
} );
} );
describe( 'appendMoreText', () => {
it( 'Removes trailing punctuation and appends some characters to a string', () => {
const string = 'trim-words.ts,';
const appendedString = appendMoreText( string, '...' );
expect( appendedString ).toEqual( 'trim-words.ts...' );
} );
} );
describe( 'trimWords', () => {
const testContent =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.';
it( 'Limits words in string and returns trimmed version', () => {
const trimmedString = trimWords( testContent, 3 );
expect( trimmedString ).toBe(
'<p>Lorem ipsum dolor&hellip;</p>\n'
);
} );
it( 'Limits words in string and returns trimmed version with custom moreText', () => {
const trimmedString = trimWords( testContent, 4, '... read more.' );
expect( trimmedString ).toEqual(
'<p>Lorem ipsum dolor sit... read more.</p>\n'
);
} );
it( 'Limits words in string and returns trimmed version without autop', () => {
const trimmedString = trimWords(
testContent,
3,
'&hellip;',
false
);
expect( trimmedString ).toEqual( 'Lorem ipsum dolor&hellip;' );
} );
it( 'does not append anything if the text is shorter than the trim limit', () => {
const trimmedString = trimWords( testContent, 100 );
expect( trimmedString ).toEqual( '<p>' + testContent + '</p>\n' );
} );
} );
describe( 'trimCharacters', () => {
const testContent = 'Lorem ipsum dolor sit amet.';
it( 'Limits characters in string and returns trimmed version including spaces', () => {
const result = trimCharacters( testContent, 10 );
expect( result ).toEqual( '<p>Lorem ipsu&hellip;</p>\n' );
} );
it( 'Limits characters in string and returns trimmed version excluding spaces', () => {
const result = trimCharacters( testContent, 10, false );
expect( result ).toEqual( '<p>Lorem ipsum&hellip;</p>\n' );
} );
it( 'Limits characters in string and returns trimmed version with custom moreText', () => {
const result = trimCharacters(
testContent,
10,
false,
'... read more.'
);
expect( result ).toEqual( '<p>Lorem ipsum... read more.</p>\n' );
} );
it( 'Limits characters in string and returns trimmed version without autop', () => {
const result = trimCharacters(
testContent,
10,
false,
'... read more.',
false
);
expect( result ).toEqual( 'Lorem ipsum... read more.' );
} );
it( 'does not append anything if the text is shorter than the trim limit', () => {
const trimmedString = trimCharacters( testContent, 1000 );
expect( trimmedString ).toEqual( '<p>' + testContent + '</p>\n' );
} );
} );
} );

View File

@ -1,70 +1,15 @@
/**
* External dependencies
*/
import { count } from '@wordpress/wordcount';
import { autop } from '@wordpress/autop';
/**
* Generates the summary text from a string of text.
*
* @param {string} source Source text.
* @param {number} maxLength Limit number of countType returned if text has multiple paragraphs.
* @param {string} countType What is being counted. One of words, characters_excluding_spaces, or characters_including_spaces.
* @return {string} Generated summary.
*/
export const generateSummary = (
source,
maxLength = 15,
countType = 'words'
) => {
const sourceWithParagraphs = autop( source );
const sourceWordCount = count( sourceWithParagraphs, countType );
if ( sourceWordCount <= maxLength ) {
return sourceWithParagraphs;
}
const firstParagraph = getFirstParagraph( sourceWithParagraphs );
const firstParagraphWordCount = count( firstParagraph, countType );
if ( firstParagraphWordCount <= maxLength ) {
return firstParagraph;
}
if ( countType === 'words' ) {
return trimWords( firstParagraph, maxLength );
}
return trimCharacters(
firstParagraph,
maxLength,
countType === 'characters_including_spaces'
);
};
/**
* Get first paragraph from some HTML text, or return whole string.
*
* @param {string} source Source text.
* @return {string} First paragraph found in string.
*/
const getFirstParagraph = ( source ) => {
const pIndex = source.indexOf( '</p>' );
if ( pIndex === -1 ) {
return source;
}
return source.substr( 0, pIndex + 4 );
};
/**
* Remove HTML tags from a string.
*
* @param {string} htmlString String to remove tags from.
* @return {string} Plain text string.
*/
const removeTags = ( htmlString ) => {
export const removeTags = ( htmlString: string ) => {
const tagsRegExp = /<\/?[a-z][^>]*?>/gi;
return htmlString.replace( tagsRegExp, '' );
};
@ -76,7 +21,7 @@ const removeTags = ( htmlString ) => {
* @param {string} moreText Text to append.
* @return {string} String with appended characters.
*/
const appendMoreText = ( text, moreText ) => {
export const appendMoreText = ( text: string, moreText: string ) => {
return text.replace( /[\s|\.\,]+$/i, '' ) + moreText;
};
@ -86,15 +31,29 @@ const appendMoreText = ( text, moreText ) => {
* @param {string} text Text to trim.
* @param {number} maxLength Number of countType to limit to.
* @param {string} moreText Appended to the trimmed string.
* @param {string} useAutop Whether to format with autop before returning.
* @return {string} Trimmed string.
*/
const trimWords = ( text, maxLength, moreText = '&hellip;' ) => {
export const trimWords = (
text: string,
maxLength: number,
moreText = '&hellip;',
useAutop = true
) => {
const textToTrim = removeTags( text );
const trimmedText = textToTrim
.split( ' ' )
.splice( 0, maxLength )
.join( ' ' );
if ( trimmedText === textToTrim ) {
return useAutop ? autop( textToTrim ) : textToTrim;
}
if ( ! useAutop ) {
return appendMoreText( trimmedText, moreText );
}
return autop( appendMoreText( trimmedText, moreText ) );
};
@ -105,17 +64,23 @@ const trimWords = ( text, maxLength, moreText = '&hellip;' ) => {
* @param {number} maxLength Number of countType to limit to.
* @param {boolean} includeSpaces Should spaces be included in the count.
* @param {string} moreText Appended to the trimmed string.
* @param {string} useAutop Whether to format with autop before returning.
* @return {string} Trimmed string.
*/
const trimCharacters = (
text,
maxLength,
export const trimCharacters = (
text: string,
maxLength: number,
includeSpaces = true,
moreText = '&hellip;'
moreText = '&hellip;',
useAutop = true
) => {
const textToTrim = removeTags( text );
const trimmedText = textToTrim.slice( 0, maxLength );
if ( trimmedText === textToTrim ) {
return useAutop ? autop( textToTrim ) : textToTrim;
}
if ( includeSpaces ) {
return autop( appendMoreText( trimmedText, moreText ) );
}
@ -127,5 +92,9 @@ const trimCharacters = (
maxLength + spaceCount
);
if ( ! useAutop ) {
return appendMoreText( trimmedTextExcludingSpaces, moreText );
}
return autop( appendMoreText( trimmedTextExcludingSpaces, moreText ) );
};

View File

@ -95,6 +95,7 @@
"@types/wordpress__data-controls": "2.2.0",
"@types/wordpress__editor": "^11.0.0",
"@types/wordpress__notices": "^3.5.0",
"@types/wordpress__wordcount": "^2.4.2",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.35.1",
"@woocommerce/api": "0.2.0",
@ -12027,6 +12028,12 @@
"@types/wordpress__rich-text": "*"
}
},
"node_modules/@types/wordpress__wordcount": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/wordpress__wordcount/-/wordpress__wordcount-2.4.2.tgz",
"integrity": "sha512-bMiBGqZTRV0/VzHjwlVcAVXsjvdlCWF7N6p7b5vOj2O+EWu35WQdTCWPxvBGp14CKdpK4AGm19v10z9zg3X5vA==",
"dev": true
},
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -60299,6 +60306,12 @@
"@types/wordpress__rich-text": "*"
}
},
"@types/wordpress__wordcount": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/wordpress__wordcount/-/wordpress__wordcount-2.4.2.tgz",
"integrity": "sha512-bMiBGqZTRV0/VzHjwlVcAVXsjvdlCWF7N6p7b5vOj2O+EWu35WQdTCWPxvBGp14CKdpK4AGm19v10z9zg3X5vA==",
"dev": true
},
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",

View File

@ -142,6 +142,7 @@
"@types/wordpress__data-controls": "2.2.0",
"@types/wordpress__editor": "^11.0.0",
"@types/wordpress__notices": "^3.5.0",
"@types/wordpress__wordcount": "^2.4.2",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.35.1",
"@woocommerce/api": "0.2.0",