Merge pull request #2269 from Bilb/get-initials-skip-non-alphabet-chars

Get initials skip non alphabet chars
pull/2270/head
Audric Ackermann 3 years ago committed by GitHub
commit bfb33d14c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,95 +0,0 @@
import React from 'react';
import LinkifyIt from 'linkify-it';
import { RenderTextCallbackType } from '../../types/Util';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { shell } from 'electron';
import { MessageInteraction } from '../../interactions';
import { useDispatch } from 'react-redux';
import { LinkPreviews } from '../../util/linkPreviews';
const linkify = LinkifyIt();
type Props = {
text: string;
/** Allows you to customize now non-links are rendered. Simplest is just a <span>. */
renderNonLink?: RenderTextCallbackType;
isGroup: boolean;
};
const SUPPORTED_PROTOCOLS = /^(http|https):/i;
const defaultRenderNonLink = ({ text }: { text: string }) => <>{text}</>;
export const Linkify = (props: Props): JSX.Element => {
const { text, isGroup, renderNonLink } = props;
const results: Array<any> = [];
let count = 1;
const dispatch = useDispatch();
const matchData = linkify.match(text) || [];
let last = 0;
// disable click on <a> elements so clicking a message containing a link doesn't
// select the message.The link will still be opened in the browser.
const handleClick = (e: any) => {
e.preventDefault();
e.stopPropagation();
const url = e.target.href;
const openLink = () => {
void shell.openExternal(url);
};
dispatch(
updateConfirmModal({
title: window.i18n('linkVisitWarningTitle'),
message: window.i18n('linkVisitWarningMessage', url),
okText: window.i18n('open'),
cancelText: window.i18n('editMenuCopy'),
showExitIcon: true,
onClickOk: openLink,
onClickClose: () => {
dispatch(updateConfirmModal(null));
},
onClickCancel: () => {
MessageInteraction.copyBodyToClipboard(url);
},
})
);
};
const renderWith = renderNonLink || defaultRenderNonLink;
if (matchData.length === 0) {
return renderWith({ text, key: 0, isGroup });
}
matchData.forEach((match: { index: number; url: string; lastIndex: number; text: string }) => {
if (last < match.index) {
const textWithNoLink = text.slice(last, match.index);
results.push(renderWith({ text: textWithNoLink, isGroup, key: count++ }));
}
const { url, text: originalText } = match;
const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url);
if (isLink) {
results.push(
<a key={count++} href={url} onClick={handleClick}>
{originalText}
</a>
);
} else {
results.push(renderWith({ text: originalText, isGroup, key: count++ }));
}
last = match.lastIndex;
});
if (last < text.length) {
results.push(renderWith({ text: text.slice(last), isGroup, key: count++ }));
}
return <>{results}</>;
};

@ -1,16 +1,24 @@
import React from 'react';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { shell } from 'electron';
import LinkifyIt from 'linkify-it';
import { RenderTextCallbackType } from '../../../../types/Util';
import { getEmojiSizeClass, SizeClassType } from '../../../../util/emoji';
import { AddMentions } from '../../AddMentions';
import { AddNewLines } from '../../AddNewLines';
import { Emojify } from '../../Emojify';
import { Linkify } from '../../Linkify';
import { MessageInteraction } from '../../../../interactions';
import { updateConfirmModal } from '../../../../state/ducks/modalDialog';
import { LinkPreviews } from '../../../../util/linkPreviews';
const linkify = LinkifyIt();
type Props = {
text: string;
/** If set, all emoji will be the same size. Otherwise, just one emoji will be large. */
disableJumbomoji: boolean;
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
/** If set, links will be left alone instead of turned into clickable `<a>` tags. Used in quotes, convo list item, etc */
disableLinks: boolean;
isGroup: boolean;
};
@ -77,6 +85,7 @@ const JsxSelectable = (jsx: JSX.Element): JSX.Element => {
</span>
);
};
export const MessageBody = (props: Props) => {
const { text, disableJumbomoji, disableLinks, isGroup } = props;
const sizeClass: SizeClassType = disableJumbomoji ? 'default' : getEmojiSizeClass(text);
@ -113,3 +122,83 @@ export const MessageBody = (props: Props) => {
/>
);
};
type LinkifyProps = {
text: string;
/** Allows you to customize now non-links are rendered. Simplest is just a <span>. */
renderNonLink: RenderTextCallbackType;
isGroup: boolean;
};
const SUPPORTED_PROTOCOLS = /^(http|https):/i;
const Linkify = (props: LinkifyProps): JSX.Element => {
const { text, isGroup, renderNonLink } = props;
const results: Array<any> = [];
let count = 1;
const dispatch = useDispatch();
const matchData = linkify.match(text) || [];
let last = 0;
// disable click on <a> elements so clicking a message containing a link doesn't
// select the message. The link will still be opened in the browser.
const handleClick = useCallback((e: any) => {
e.preventDefault();
e.stopPropagation();
const url = e.target.href;
const openLink = () => {
void shell.openExternal(url);
};
dispatch(
updateConfirmModal({
title: window.i18n('linkVisitWarningTitle'),
message: window.i18n('linkVisitWarningMessage', url),
okText: window.i18n('open'),
cancelText: window.i18n('editMenuCopy'),
showExitIcon: true,
onClickOk: openLink,
onClickClose: () => {
dispatch(updateConfirmModal(null));
},
onClickCancel: () => {
MessageInteraction.copyBodyToClipboard(url);
},
})
);
}, []);
if (matchData.length === 0) {
return renderNonLink({ text, key: 0, isGroup });
}
matchData.forEach((match: { index: number; url: string; lastIndex: number; text: string }) => {
if (last < match.index) {
const textWithNoLink = text.slice(last, match.index);
results.push(renderNonLink({ text: textWithNoLink, isGroup, key: count++ }));
}
const { url, text: originalText } = match;
const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url);
if (isLink) {
results.push(
<a key={count++} href={url} onClick={handleClick}>
{originalText}
</a>
);
} else {
results.push(renderNonLink({ text: originalText, isGroup, key: count++ }));
}
last = match.lastIndex;
});
if (last < text.length) {
results.push(renderNonLink({ text: text.slice(last), isGroup, key: count++ }));
}
return <>{results}</>;
};

@ -59,6 +59,35 @@ describe('getInitials', () => {
});
});
describe('name has a - in its content', () => {
it('initials: return the first char of each first 2 words if a - is present ', () => {
expect(getInitials('John-Doe')).to.be.equal('JD', 'should have return JD');
});
it('initials: return the first char capitalized of each first 2 words if a - is present ', () => {
expect(getInitials('John-doe')).to.be.equal('JD', 'should have return JD capitalized');
});
it('initials: return the first char capitalized of each first 2 words if a - is present, even with more than 2 words ', () => {
expect(getInitials('John-Doe-Alice')).to.be.equal('JD', 'should have return JD capitalized');
});
it('initials: return the first char capitalized of each first 2 words if a - is present, even with more than 2 words ', () => {
expect(getInitials('John-doe-Alice')).to.be.equal('JD', 'should have return JD capitalized');
});
describe('name is not ascii', () => {
// ß maps to SS in uppercase
it('initials: shorten to 2 char at most if the uppercase form length is > 2 ', () => {
expect(getInitials('John-ß')).to.be.equal('JS', 'should have return JS capitalized');
});
it('initials: shorten to 2 char at most if the uppercase form length is > 2 ', () => {
expect(getInitials('ß-ß')).to.be.equal('SS', 'should have return SS capitalized');
});
});
});
describe('name has NO spaces in its content', () => {
it('initials: return the first 2 chars of the first word if the name has no space ', () => {
expect(getInitials('JOHNY')).to.be.equal('JO', 'should have return JO');

@ -8,11 +8,14 @@ export function getInitials(name?: string): string {
return upperAndShorten(name[2]);
}
if (name.indexOf(' ') === -1) {
// there is no space, just return the first 2 chars of the name
if (name.split(/[-\s]/).length === 1) {
// there is one word, so just return the first 2 alphanumeric chars of the name
if (name.length > 1) {
return upperAndShorten(name.slice(0, 2));
const alphanum = name.match(/[\p{L}\p{N}]+/u);
if (alphanum) {
return upperAndShorten(alphanum[0].slice(0, 2));
}
}
return upperAndShorten(name[0]);
}
@ -20,11 +23,12 @@ export function getInitials(name?: string): string {
// name has a space, just extract the first char of each words
return upperAndShorten(
name
.split(' ')
.split(/[-\s]/)
.slice(0, 2)
.map(n => {
return n[0];
})
.map(n =>
// Allow a letter or a digit from any alphabet.
n.match(/^[\p{L}\p{N}]/u)
)
.join('')
);
}

Loading…
Cancel
Save