mirror of https://github.com/zulip/zulip.git
markdown: Move translate_emoticons_to_names().
Most of this logic is specific to markdown message processing, so we move the code to markdown.js. The only responsibility that we leave with `emoji.js` is to provide us with a list of translations (regex and replacement text). But now `markdown.js` actually (directly) executes those translations against Zulip messages as part of its preprocessing. This should simplify the upcoming mobile conversion. Instead of mobile needing to duplicate this fairly complex function, they will just need to pass us in a list similar to `emoji_translations` inside of `emoji.js`. That code has a comment that shows what the data structure looks like.
This commit is contained in:
parent
6050a5bdd6
commit
5e9df7a0a6
|
@ -6,7 +6,6 @@ set_global('page_params', {
|
|||
set_global('upload_widget', {});
|
||||
set_global('blueslip', global.make_zblueslip());
|
||||
|
||||
const emoji_codes = zrequire('emoji_codes', 'generated/emoji/emoji_codes.json');
|
||||
zrequire('emoji');
|
||||
zrequire('markdown');
|
||||
|
||||
|
@ -81,39 +80,3 @@ run_test('get_canonical_name', () => {
|
|||
assert.equal(blueslip.get_test_logs('error').length, 1);
|
||||
blueslip.clear_test_data();
|
||||
});
|
||||
|
||||
run_test('translate_emoticons_to_names', () => {
|
||||
// Simple test
|
||||
const test_text = 'Testing :)';
|
||||
const expected = 'Testing :slight_smile:';
|
||||
const result = emoji.translate_emoticons_to_names(test_text);
|
||||
assert.equal(expected, result);
|
||||
|
||||
// Extensive tests.
|
||||
// The following code loops over the test cases and each emoticon conversion
|
||||
// to generate multiple test cases.
|
||||
const testcases = [
|
||||
{name: 'only emoticon', original: '<original>', expected: '<converted>'},
|
||||
{name: 'space at start', original: ' <original>', expected: ' <converted>'},
|
||||
{name: 'space at end', original: '<original> ', expected: '<converted> '},
|
||||
{name: 'symbol at end', original: '<original>!', expected: '<converted>!'},
|
||||
{name: 'symbol at start', original: 'Hello,<original>', expected: 'Hello,<converted>'},
|
||||
{name: 'after a word', original: 'Hello<original>', expected: 'Hello<original>'},
|
||||
{name: 'between words', original: 'Hello<original>World', expected: 'Hello<original>World'},
|
||||
{name: 'end of sentence', original: 'End of sentence. <original>', expected: 'End of sentence. <converted>'},
|
||||
{name: 'between symbols', original: 'Hello.<original>! World.', expected: 'Hello.<original>! World.'},
|
||||
{name: 'before end of sentence', original: 'Hello <original>!', expected: 'Hello <converted>!'},
|
||||
];
|
||||
for (const [shortcut, full_name] of Object.entries(emoji_codes.emoticon_conversions)) {
|
||||
for (const t of testcases) {
|
||||
const converted_value = full_name;
|
||||
let original = t.original;
|
||||
let expected = t.expected;
|
||||
original = original.replace(/(<original>)/g, shortcut);
|
||||
expected = expected.replace(/(<original>)/g, shortcut)
|
||||
.replace(/(<converted>)/g, converted_value);
|
||||
const result = emoji.translate_emoticons_to_names(original);
|
||||
assert.equal(result, expected);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -608,3 +608,39 @@ run_test('misc_helpers', () => {
|
|||
markdown.set_name_in_mention_element(elem, 'Aaron, but silent');
|
||||
assert.equal(elem.text(), 'Aaron, but silent');
|
||||
});
|
||||
|
||||
run_test('translate_emoticons_to_names', () => {
|
||||
// Simple test
|
||||
const test_text = 'Testing :)';
|
||||
const expected = 'Testing :slight_smile:';
|
||||
const result = markdown.translate_emoticons_to_names(test_text);
|
||||
assert.equal(expected, result);
|
||||
|
||||
// Extensive tests.
|
||||
// The following code loops over the test cases and each emoticon conversion
|
||||
// to generate multiple test cases.
|
||||
const testcases = [
|
||||
{name: 'only emoticon', original: '<original>', expected: '<converted>'},
|
||||
{name: 'space at start', original: ' <original>', expected: ' <converted>'},
|
||||
{name: 'space at end', original: '<original> ', expected: '<converted> '},
|
||||
{name: 'symbol at end', original: '<original>!', expected: '<converted>!'},
|
||||
{name: 'symbol at start', original: 'Hello,<original>', expected: 'Hello,<converted>'},
|
||||
{name: 'after a word', original: 'Hello<original>', expected: 'Hello<original>'},
|
||||
{name: 'between words', original: 'Hello<original>World', expected: 'Hello<original>World'},
|
||||
{name: 'end of sentence', original: 'End of sentence. <original>', expected: 'End of sentence. <converted>'},
|
||||
{name: 'between symbols', original: 'Hello.<original>! World.', expected: 'Hello.<original>! World.'},
|
||||
{name: 'before end of sentence', original: 'Hello <original>!', expected: 'Hello <converted>!'},
|
||||
];
|
||||
for (const [shortcut, full_name] of Object.entries(emoji_codes.emoticon_conversions)) {
|
||||
for (const t of testcases) {
|
||||
const converted_value = full_name;
|
||||
let original = t.original;
|
||||
let expected = t.expected;
|
||||
original = original.replace(/(<original>)/g, shortcut);
|
||||
expected = expected.replace(/(<original>)/g, shortcut)
|
||||
.replace(/(<converted>)/g, converted_value);
|
||||
const result = markdown.translate_emoticons_to_names(original);
|
||||
assert.equal(result, expected);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -184,43 +184,6 @@ exports.get_canonical_name = function (emoji_name) {
|
|||
return emoji_codes.codepoint_to_name[codepoint];
|
||||
};
|
||||
|
||||
// Translates emoticons in a string to their colon syntax.
|
||||
exports.translate_emoticons_to_names = function translate_emoticons_to_names(text) {
|
||||
let translated = text;
|
||||
let replacement_text;
|
||||
const terminal_symbols = ',.;?!()[] "\'\n\t'; // From composebox_typeahead
|
||||
const symbols_except_space = terminal_symbols.replace(' ', '');
|
||||
|
||||
const emoticon_replacer = function (match, g1, offset, str) {
|
||||
const prev_char = str[offset - 1];
|
||||
const next_char = str[offset + match.length];
|
||||
|
||||
const symbol_at_start = terminal_symbols.includes(prev_char);
|
||||
const symbol_at_end = terminal_symbols.includes(next_char);
|
||||
const non_space_at_start = symbols_except_space.includes(prev_char);
|
||||
const non_space_at_end = symbols_except_space.includes(next_char);
|
||||
const valid_start = symbol_at_start || offset === 0;
|
||||
const valid_end = symbol_at_end || offset === str.length - match.length;
|
||||
|
||||
if (non_space_at_start && non_space_at_end) { // Hello!:)?
|
||||
return match;
|
||||
}
|
||||
if (valid_start && valid_end) {
|
||||
return replacement_text;
|
||||
}
|
||||
return match;
|
||||
};
|
||||
|
||||
for (const translation of emoticon_translations) {
|
||||
// We can't pass replacement_text directly into
|
||||
// emoticon_replacer, because emoticon_replacer is
|
||||
// a callback for `replace()`. Instead we just mutate
|
||||
// the `replacement_text` that the function closes on.
|
||||
replacement_text = translation.replacement_text;
|
||||
translated = translated.replace(translation.regex, emoticon_replacer);
|
||||
}
|
||||
|
||||
return translated;
|
||||
};
|
||||
exports.get_emoticon_translations = () => emoticon_translations;
|
||||
|
||||
window.emoji = exports;
|
||||
|
|
|
@ -34,6 +34,45 @@ exports.set_name_in_mention_element = function (element, name) {
|
|||
}
|
||||
};
|
||||
|
||||
exports.translate_emoticons_to_names = (text) => {
|
||||
// Translates emoticons in a string to their colon syntax.
|
||||
let translated = text;
|
||||
let replacement_text;
|
||||
const terminal_symbols = ',.;?!()[] "\'\n\t'; // From composebox_typeahead
|
||||
const symbols_except_space = terminal_symbols.replace(' ', '');
|
||||
|
||||
const emoticon_replacer = function (match, g1, offset, str) {
|
||||
const prev_char = str[offset - 1];
|
||||
const next_char = str[offset + match.length];
|
||||
|
||||
const symbol_at_start = terminal_symbols.includes(prev_char);
|
||||
const symbol_at_end = terminal_symbols.includes(next_char);
|
||||
const non_space_at_start = symbols_except_space.includes(prev_char);
|
||||
const non_space_at_end = symbols_except_space.includes(next_char);
|
||||
const valid_start = symbol_at_start || offset === 0;
|
||||
const valid_end = symbol_at_end || offset === str.length - match.length;
|
||||
|
||||
if (non_space_at_start && non_space_at_end) { // Hello!:)?
|
||||
return match;
|
||||
}
|
||||
if (valid_start && valid_end) {
|
||||
return replacement_text;
|
||||
}
|
||||
return match;
|
||||
};
|
||||
|
||||
for (const translation of emoji.get_emoticon_translations()) {
|
||||
// We can't pass replacement_text directly into
|
||||
// emoticon_replacer, because emoticon_replacer is
|
||||
// a callback for `replace()`. Instead we just mutate
|
||||
// the `replacement_text` that the function closes on.
|
||||
replacement_text = translation.replacement_text;
|
||||
translated = translated.replace(translation.regex, emoticon_replacer);
|
||||
}
|
||||
|
||||
return translated;
|
||||
};
|
||||
|
||||
exports.contains_backend_only_syntax = function (content) {
|
||||
// Try to guess whether or not a message has bugdown in it
|
||||
// If it doesn't, we can immediately render it client-side
|
||||
|
@ -376,7 +415,7 @@ exports.initialize = function () {
|
|||
|
||||
// In this scenario, the message has to be from the user, so the only
|
||||
// requirement should be that they have the setting on.
|
||||
return emoji.translate_emoticons_to_names(src);
|
||||
return exports.translate_emoticons_to_names(src);
|
||||
}
|
||||
|
||||
// Disable lheadings
|
||||
|
|
Loading…
Reference in New Issue