feat(editor): Make ‘Execute workflow’ a split button (#15933)

This commit is contained in:
Suguru Inoue
2025-06-06 13:05:53 +02:00
committed by GitHub
parent eb71c41e93
commit ac1a1dfbc2
20 changed files with 619 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
import { truncate } from './truncate';
import { truncateBeforeLast, truncate } from './truncate';
describe('truncate', () => {
it('should truncate text to 30 chars by default', () => {
@@ -13,3 +13,37 @@ describe('truncate', () => {
);
});
});
describe(truncateBeforeLast, () => {
it('should return unmodified text if the length does not exceed max length', () => {
expect(truncateBeforeLast('I love nodemation', 25)).toBe('I love nodemation');
expect(truncateBeforeLast('I ❤️ nodemation', 25)).toBe('I ❤️ nodemation');
expect(truncateBeforeLast('Nodemation is cool', 25)).toBe('Nodemation is cool');
expect(truncateBeforeLast('Internationalization', 25)).toBe('Internationalization');
expect(truncateBeforeLast('I love 👨‍👩‍👧‍👦', 8)).toBe('I love 👨‍👩‍👧‍👦');
});
it('should remove chars just before the last word, as long as the last word is under 15 chars', () => {
expect(truncateBeforeLast('I love nodemation', 15)).toBe('I lo…nodemation');
expect(truncateBeforeLast('I love "nodemation"', 15)).toBe('I …"nodemation"');
expect(truncateBeforeLast('I ❤️ nodemation', 13)).toBe('I …nodemation');
expect(truncateBeforeLast('Nodemation is cool', 15)).toBe('Nodemation…cool');
expect(truncateBeforeLast('"Nodemation" is cool', 15)).toBe('"Nodematio…cool');
expect(truncateBeforeLast('Is it fun to automate boring stuff?', 15)).toBe('Is it fu…stuff?');
expect(truncateBeforeLast('Is internationalization fun?', 15)).toBe('Is interna…fun?');
expect(truncateBeforeLast('I love 👨‍👩‍👧‍👦', 7)).toBe('I lov…👨👩👧👦');
});
it('should preserve last 5 characters if the last word is longer than 15 characters', () => {
expect(truncateBeforeLast('I love internationalization', 25)).toBe('I love internationa…ation');
expect(truncateBeforeLast('I love "internationalization"', 25)).toBe(
'I love "internation…tion"',
);
expect(truncateBeforeLast('I "love" internationalization', 25)).toBe(
'I "love" internatio…ation',
);
expect(truncateBeforeLast('I ❤️ internationalization', 9)).toBe('I ❤…ation');
expect(truncateBeforeLast('I ❤️ internationalization', 8)).toBe('I …ation');
expect(truncateBeforeLast('Internationalization', 15)).toBe('Internati…ation');
});
});

View File

@@ -1,2 +1,44 @@
export const truncate = (text: string, length = 30): string =>
text.length > length ? text.slice(0, length) + '...' : text;
/**
* Replace part of given text with ellipsis following the rules below:
*
* - Remove chars just before the last word, as long as the last word is under 15 chars
* - Otherwise preserve the last 5 chars of the name and remove chars before that
*/
export function truncateBeforeLast(text: string, maxLength: number): string {
const chars: string[] = [];
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
for (const { segment } of segmenter.segment(text)) {
chars.push(segment);
}
if (chars.length <= maxLength) {
return text;
}
const lastWhitespaceIndex = chars.findLastIndex((ch) => ch.match(/^\s+$/));
const lastWordIndex = lastWhitespaceIndex + 1;
const lastWord = chars.slice(lastWordIndex);
const ellipsis = '…';
const ellipsisLength = ellipsis.length;
if (lastWord.length < 15) {
const charsToRemove = chars.length - maxLength + ellipsisLength;
const indexBeforeLastWord = lastWordIndex;
const keepLength = indexBeforeLastWord - charsToRemove;
if (keepLength > 0) {
return (
chars.slice(0, keepLength).join('') + ellipsis + chars.slice(indexBeforeLastWord).join('')
);
}
}
return (
chars.slice(0, maxLength - 5 - ellipsisLength).join('') + ellipsis + chars.slice(-5).join('')
);
}