custom profile fields: Markdown rendering for custom profile field values.

This makes it possible it include our standard markdown formatting in
one's custom profile fields, allowing for links, emphasis, emoji, etc.

Fixes #10131.
This commit is contained in:
Hemanth V. Alluri 2018-12-31 12:15:33 +05:30 committed by Tim Abbott
parent 28d344b4b5
commit e3aed0f7bc
9 changed files with 56 additions and 19 deletions

View File

@ -240,11 +240,12 @@ initialize();
run_test('set_custom_profile_field_data', () => { run_test('set_custom_profile_field_data', () => {
var person = people.get_by_email(me.email); var person = people.get_by_email(me.email);
me.profile_data = {}; me.profile_data = {};
var field = {id: 3, name: 'Custom long field', type: 'text', value: 'Field value'}; var field = {id: 3, name: 'Custom long field', type: 'text', value: 'Field value', rendered_value: '<p>Field value</p>'};
people.set_custom_profile_field_data(person.user_id, {}); people.set_custom_profile_field_data(person.user_id, {});
assert.deepEqual(person.profile_data, {}); assert.deepEqual(person.profile_data, {});
people.set_custom_profile_field_data(person.user_id, field); people.set_custom_profile_field_data(person.user_id, field);
assert.equal(person.profile_data[field.id].value, 'Field value'); assert.equal(person.profile_data[field.id].value, 'Field value');
assert.equal(person.profile_data[field.id].rendered_value, '<p>Field value</p>');
}); });
run_test('get_rest_of_realm', () => { run_test('get_rest_of_realm', () => {

View File

@ -591,7 +591,7 @@ run_test('compose_private_stream_alert', () => {
run_test('custom_user_profile_field', () => { run_test('custom_user_profile_field', () => {
var field = {name: "GitHub user name", id: 2, hint: "Or link to profile"}; var field = {name: "GitHub user name", id: 2, hint: "Or link to profile"};
var args = {field: field, field_value: {value: "@GitHub"}, field_type: "text"}; var args = {field: field, field_value: {value: "@GitHub", rendered_value: "<p>@GitHub</p>"}, field_type: "text"};
var html = render('custom-user-profile-field', args); var html = render('custom-user-profile-field', args);
assert.equal($(html).attr('data-field-id'), 2); assert.equal($(html).attr('data-field-id'), 2);
assert.equal($(html).find('.custom_user_field_value').val(), "@GitHub"); assert.equal($(html).find('.custom_user_field_value').val(), "@GitHub");

View File

@ -157,9 +157,10 @@ run_test('updates', () => {
blueslip.clear_test_data(); blueslip.clear_test_data();
me.profile_data = {}; me.profile_data = {};
user_events.update_person({user_id: me.user_id, custom_profile_field: {id: 3, value: 'Value'}}); user_events.update_person({user_id: me.user_id, custom_profile_field: {id: 3, value: 'Value', rendered_value: '<p>Value</p>'}});
person = people.get_by_email(me.email); person = people.get_by_email(me.email);
assert.equal(person.profile_data[3].value, 'Value'); assert.equal(person.profile_data[3].value, 'Value');
assert.equal(person.profile_data[3].rendered_value, '<p>Value</p>');
var updated = false; var updated = false;
settings_account.update_email = (email) => { settings_account.update_email = (email) => {

View File

@ -983,6 +983,7 @@ exports.set_custom_profile_field_data = function (user_id, field) {
} }
people_by_user_id_dict.get(user_id).profile_data[field.id] = { people_by_user_id_dict.get(user_id).profile_data[field.id] = {
value: field.value, value: field.value,
rendered_value: field.rendered_value,
}; };
}; };

View File

@ -256,9 +256,13 @@ exports.show_user_profile = function (user) {
var field_choice_dict = JSON.parse(field.field_data); var field_choice_dict = JSON.parse(field.field_data);
profile_field.value = field_choice_dict[field_value.value].text; profile_field.value = field_choice_dict[field_value.value].text;
break; break;
case field_types.SHORT_TEXT.id:
case field_types.LONG_TEXT.id:
profile_field.value = field_value.value;
profile_field.rendered_value = field_value.rendered_value;
break;
default: default:
profile_field.value = field_value.value; profile_field.value = field_value.value;
break;
} }
profile_data.push(profile_field); profile_data.push(profile_field);
}); });

View File

@ -164,6 +164,7 @@ exports.intialize_custom_user_type_fields = function (element_id, user_id, is_ed
}); });
} }
} }
if (is_editable) { if (is_editable) {
var input = pill_container.children('.input'); var input = pill_container.children('.input');
if (set_handler_on_update) { if (set_handler_on_update) {

View File

@ -44,13 +44,17 @@
<div data-type="{{this.type}}" class="field-section custom_user_field" data-field-id="{{this.id}}"> <div data-type="{{this.type}}" class="field-section custom_user_field" data-field-id="{{this.id}}">
<div class="name">{{this.name}}</div> <div class="name">{{this.name}}</div>
{{#if this.is_user_field}} {{#if this.is_user_field}}
<div class="pill-container not-editable" data-field-id="{{this.id}}"> <div class="pill-container not-editable" data-field-id="{{this.id}}">
<div class="input" contenteditable="false" style="display: none;"></div> <div class="input" contenteditable="false" style="display: none;"></div>
</div> </div>
{{else if this.is_link}} {{else if this.is_link}}
<a href={{this.value}} target="_blank" class="value">{{this.value}}</a> <a href={{this.value}} target="_blank" class="value">{{this.value}}</a>
{{else}} {{else}}
<div class="value">{{this.value}}</div> {{#if this.rendered_value}}
<div class="value">{{{this.rendered_value}}}</div>
{{else}}
<div class="value">{{this.value}}</div>
{{/if}}
{{/if}} {{/if}}
</div> </div>
{{/each}} {{/each}}

View File

@ -63,9 +63,15 @@ def get_raw_user_data(realm_id: int, client_gravatar: bool) -> Dict[int, Dict[st
profiles_by_user_id = defaultdict(dict) # type: Dict[int, Dict[str, Any]] profiles_by_user_id = defaultdict(dict) # type: Dict[int, Dict[str, Any]]
for profile_field in custom_profile_field_values: for profile_field in custom_profile_field_values:
user_id = profile_field.user_profile_id user_id = profile_field.user_profile_id
profiles_by_user_id[user_id][profile_field.field_id] = { if profile_field.field.is_renderable():
"value": profile_field.value profiles_by_user_id[user_id][profile_field.field_id] = {
} "value": profile_field.value,
"rendered_value": profile_field.rendered_value
}
else:
profiles_by_user_id[user_id][profile_field.field_id] = {
"value": profile_field.value
}
def user_data(row: Dict[str, Any]) -> Dict[str, Any]: def user_data(row: Dict[str, Any]) -> Dict[str, Any]:
avatar_url = get_avatar_field( avatar_url = get_avatar_field(
@ -407,9 +413,15 @@ def apply_event(state: Dict[str, Any],
if 'custom_profile_field' in person: if 'custom_profile_field' in person:
custom_field_id = person['custom_profile_field']['id'] custom_field_id = person['custom_profile_field']['id']
custom_field_new_value = person['custom_profile_field']['value'] custom_field_new_value = person['custom_profile_field']['value']
p['profile_data'][custom_field_id] = { if 'rendered_value' in person['custom_profile_field']:
'value': custom_field_new_value p['profile_data'][custom_field_id] = {
} 'value': custom_field_new_value,
'rendered_value': person['custom_profile_field']['rendered_value']
}
else:
p['profile_data'][custom_field_id] = {
'value': custom_field_new_value
}
elif event['type'] == 'realm_bot': elif event['type'] == 'realm_bot':
if event['op'] == 'add': if event['op'] == 'add':

View File

@ -1067,7 +1067,7 @@ class EventsRegisterTest(ZulipTestCase):
self.assert_on_error(error) self.assert_on_error(error)
def test_custom_profile_field_data_events(self) -> None: def test_custom_profile_field_data_events(self) -> None:
schema_checker = self.check_events_dict([ schema_checker_basic = self.check_events_dict([
('type', equals('realm_user')), ('type', equals('realm_user')),
('op', equals('update')), ('op', equals('update')),
('person', check_dict_only([ ('person', check_dict_only([
@ -1079,6 +1079,19 @@ class EventsRegisterTest(ZulipTestCase):
])), ])),
]) ])
schema_checker_with_rendered_value = self.check_events_dict([
('type', equals('realm_user')),
('op', equals('update')),
('person', check_dict_only([
('user_id', check_int),
('custom_profile_field', check_dict([
('id', check_int),
('value', check_none_or(check_string)),
('rendered_value', check_none_or(check_string)),
], _allow_only_listed_keys=False)),
])),
])
realm = get_realm("zulip") realm = get_realm("zulip")
field_id = realm.customprofilefield_set.get(realm=realm, name='Biography').id field_id = realm.customprofilefield_set.get(realm=realm, name='Biography').id
field = { field = {
@ -1086,7 +1099,7 @@ class EventsRegisterTest(ZulipTestCase):
"value": "New value", "value": "New value",
} }
events = self.do_test(lambda: do_update_user_custom_profile_data(self.user_profile, [field])) events = self.do_test(lambda: do_update_user_custom_profile_data(self.user_profile, [field]))
error = schema_checker('events[0]', events[0]) error = schema_checker_with_rendered_value('events[0]', events[0])
self.assert_on_error(error) self.assert_on_error(error)
# Test we pass correct stringify value in custom-user-field data event # Test we pass correct stringify value in custom-user-field data event
@ -1096,7 +1109,7 @@ class EventsRegisterTest(ZulipTestCase):
"value": [self.example_user("ZOE").id], "value": [self.example_user("ZOE").id],
} }
events = self.do_test(lambda: do_update_user_custom_profile_data(self.user_profile, [field])) events = self.do_test(lambda: do_update_user_custom_profile_data(self.user_profile, [field]))
error = schema_checker('events[0]', events[0]) error = schema_checker_basic('events[0]', events[0])
self.assert_on_error(error) self.assert_on_error(error)
def test_presence_events(self) -> None: def test_presence_events(self) -> None: