Commit d5472a8d authored by JC Brand's avatar JC Brand

muc-views: Use native methods instead of lodash

parent 17ef50f6
...@@ -8,6 +8,7 @@ import "converse-modal"; ...@@ -8,6 +8,7 @@ import "converse-modal";
import "backbone.vdomview"; import "backbone.vdomview";
import "formdata-polyfill"; import "formdata-polyfill";
import "@converse/headless/utils/muc"; import "@converse/headless/utils/muc";
import { get, head, isString, isUndefined, pick } from "lodash";
import { OrderedListView } from "backbone.overview"; import { OrderedListView } from "backbone.overview";
import converse from "@converse/headless/converse-core"; import converse from "@converse/headless/converse-core";
import log from "@converse/headless/log"; import log from "@converse/headless/log";
...@@ -36,7 +37,7 @@ import tpl_rooms_results from "templates/rooms_results.html"; ...@@ -36,7 +37,7 @@ import tpl_rooms_results from "templates/rooms_results.html";
import tpl_spinner from "templates/spinner.html"; import tpl_spinner from "templates/spinner.html";
import xss from "xss/dist/xss"; import xss from "xss/dist/xss";
const { Backbone, Strophe, sizzle, _, $iq, $pres } = converse.env; const { Backbone, Strophe, sizzle, $iq, $pres } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
const ROLES = ['moderator', 'participant', 'visitor']; const ROLES = ['moderator', 'participant', 'visitor'];
...@@ -171,8 +172,8 @@ converse.plugins.add('converse-muc-views', { ...@@ -171,8 +172,8 @@ converse.plugins.add('converse-muc-views', {
'beforeEnd', 'beforeEnd',
tpl_room_description({ tpl_room_description({
'jid': stanza.getAttribute('from'), 'jid': stanza.getAttribute('from'),
'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'), 'desc': get(head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'),
'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'), 'occ': get(head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'),
'hidden': sizzle('feature[var="muc_hidden"]', stanza).length, 'hidden': sizzle('feature[var="muc_hidden"]', stanza).length,
'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length, 'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length,
'moderated': sizzle('feature[var="muc_moderated"]', stanza).length, 'moderated': sizzle('feature[var="muc_moderated"]', stanza).length,
...@@ -259,18 +260,19 @@ converse.plugins.add('converse-muc-views', { ...@@ -259,18 +260,19 @@ converse.plugins.add('converse-muc-views', {
toHTML () { toHTML () {
const allowed_commands = this.chatroomview.getAllowedCommands(); const allowed_commands = this.chatroomview.getAllowedCommands();
const allowed_affiliations = allowed_commands.map(c => COMMAND_TO_AFFILIATION[c]).filter(c => c); const allowed_affiliations = allowed_commands.map(c => COMMAND_TO_AFFILIATION[c]).filter(c => c);
const allowed_roles = _.uniq(allowed_commands const allowed_roles = allowed_commands
.filter((value, i, list) => list.indexOf(value) == i)
.map(c => COMMAND_TO_ROLE[c]) .map(c => COMMAND_TO_ROLE[c])
.filter(c => c)); .filter(c => c);
allowed_affiliations.sort(); allowed_affiliations.sort();
allowed_roles.sort(); allowed_roles.sort();
return tpl_moderator_tools_modal(Object.assign(this.model.toJSON(), { return tpl_moderator_tools_modal(Object.assign(this.model.toJSON(), {
'__': __, __,
allowed_affiliations,
allowed_roles,
'affiliations': [...AFFILIATIONS, 'none'], 'affiliations': [...AFFILIATIONS, 'none'],
'allowed_affiliations': allowed_affiliations,
'allowed_roles': allowed_roles,
'loading_users_with_affiliation': this.loading_users_with_affiliation, 'loading_users_with_affiliation': this.loading_users_with_affiliation,
'roles': ROLES, 'roles': ROLES,
'users_with_affiliation': this.users_with_affiliation, 'users_with_affiliation': this.users_with_affiliation,
...@@ -623,13 +625,12 @@ converse.plugins.add('converse-muc-views', { ...@@ -623,13 +625,12 @@ converse.plugins.add('converse-muc-views', {
toHTML () { toHTML () {
return tpl_chatroom_details_modal(Object.assign( return tpl_chatroom_details_modal(Object.assign(
this.model.toJSON(), { this.model.toJSON(), {
'_': _,
'__': __, '__': __,
'config': this.model.config.toJSON(), 'config': this.model.config.toJSON(),
'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()), 'display_name': __('Groupchat info for %1$s', this.model.getDisplayName()),
'features': this.model.features.toJSON(), 'features': this.model.features.toJSON(),
'num_occupants': this.model.occupants.length, 'num_occupants': this.model.occupants.length,
'topic': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})) 'topic': u.addHyperlinks(xss.filterXSS(get(this.model.get('subject'), 'text'), {'whiteList': {}}))
}) })
); );
} }
...@@ -732,11 +733,16 @@ converse.plugins.add('converse-muc-views', { ...@@ -732,11 +733,16 @@ converse.plugins.add('converse-muc-views', {
return this; return this;
}, },
/**
* Renders the MUC heading if any relevant attributes have changed.
* @private
* @method _converse.ChatRoomView#renderHeading
* @param { _converse.ChatRoom } [item]
*/
renderHeading (item=null) { renderHeading (item=null) {
/* Render the heading UI of the groupchat. */ const changed = item === null ? [] : Object.keys(item.changed);
const changed = _.get(item, 'changed', {});
const keys = ['affiliation', 'bookmarked', 'jid', 'name', 'description', 'subject']; const keys = ['affiliation', 'bookmarked', 'jid', 'name', 'description', 'subject'];
if (item === null || _.intersection(Object.keys(changed), keys).length) { if (item === null || changed.filter(v => keys.includes(v)).length) {
this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML(); this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML();
} }
}, },
...@@ -1025,7 +1031,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1025,7 +1031,7 @@ converse.plugins.add('converse-muc-views', {
if (!this.verifyRoles(['moderator'])) { if (!this.verifyRoles(['moderator'])) {
return; return;
} }
if (_.isUndefined(this.model.modtools_modal)) { if (isUndefined(this.model.modtools_modal)) {
const model = new Backbone.Model({'affiliation': affiliation}); const model = new Backbone.Model({'affiliation': affiliation});
this.modtools_modal = new _converse.ModeratorToolsModal({'model': model, 'chatroomview': this}); this.modtools_modal = new _converse.ModeratorToolsModal({'model': model, 'chatroomview': this});
} else { } else {
...@@ -1127,7 +1133,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1127,7 +1133,7 @@ converse.plugins.add('converse-muc-views', {
'info_close': __('Close and leave this groupchat'), 'info_close': __('Close and leave this groupchat'),
'info_configure': __('Configure this groupchat'), 'info_configure': __('Configure this groupchat'),
'info_details': __('Show more details about this groupchat'), 'info_details': __('Show more details about this groupchat'),
'subject': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), 'subject': u.addHyperlinks(xss.filterXSS(get(this.model.get('subject'), 'text'), {'whiteList': {}})),
})); }));
}, },
...@@ -1663,7 +1669,6 @@ converse.plugins.add('converse-muc-views', { ...@@ -1663,7 +1669,6 @@ converse.plugins.add('converse-muc-views', {
}); });
const container = this.el.querySelector('.disconnect-container'); const container = this.el.querySelector('.disconnect-container');
container.innerHTML = tpl_chatroom_destroyed({ container.innerHTML = tpl_chatroom_destroyed({
'_': _,
'__':__, '__':__,
'jid': moved_jid, 'jid': moved_jid,
'reason': reason ? `"${reason}"` : null 'reason': reason ? `"${reason}"` : null
...@@ -1704,17 +1709,14 @@ converse.plugins.add('converse-muc-views', { ...@@ -1704,17 +1709,14 @@ converse.plugins.add('converse-muc-views', {
'disconnection_actor': undefined 'disconnection_actor': undefined
}); });
const container = this.el.querySelector('.disconnect-container'); const container = this.el.querySelector('.disconnect-container');
container.innerHTML = tpl_chatroom_disconnect({ container.innerHTML = tpl_chatroom_disconnect({messages})
'_': _,
'disconnect_messages': messages
})
u.showElement(container); u.showElement(container);
}, },
getNotificationWithMessage (message) { getNotificationWithMessage (message) {
let el = this.content.lastElementChild; let el = this.content.lastElementChild;
while (el) { while (el) {
if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { if (!u.hasClass(el, 'chat-info')) {
return; return;
} }
if (el.textContent === message) { if (el.textContent === message) {
...@@ -1771,7 +1773,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1771,7 +1773,7 @@ converse.plugins.add('converse-muc-views', {
}, },
showJoinOrLeaveNotification (occupant) { showJoinOrLeaveNotification (occupant) {
if (_.includes(occupant.get('states'), '303')) { if (occupant.get('states').includes('303')) {
return; return;
} }
if (occupant.get('show') === 'offline') { if (occupant.get('show') === 'offline') {
...@@ -1801,7 +1803,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1801,7 +1803,7 @@ converse.plugins.add('converse-muc-views', {
if (date && date.split('T')[0] !== today) { if (date && date.split('T')[0] !== today) {
return; return;
} }
const data = _.get(el, 'dataset', {}); const data = get(el, 'dataset', {});
if (data.join === nick || if (data.join === nick ||
data.leave === nick || data.leave === nick ||
data.leavejoin === nick || data.leavejoin === nick ||
...@@ -1820,7 +1822,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1820,7 +1822,7 @@ converse.plugins.add('converse-muc-views', {
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
data = _.get(prev_info_el, 'dataset', {}); data = get(prev_info_el, 'dataset', {});
if (data.leave === nick) { if (data.leave === nick) {
let message; let message;
...@@ -1868,14 +1870,14 @@ converse.plugins.add('converse-muc-views', { ...@@ -1868,14 +1870,14 @@ converse.plugins.add('converse-muc-views', {
showLeaveNotification (occupant) { showLeaveNotification (occupant) {
if (!_converse.muc_show_join_leave || if (!_converse.muc_show_join_leave ||
_.includes(occupant.get('states'), '303') || occupant.get('states').includes('303') ||
_.includes(occupant.get('states'), '307')) { occupant.get('states').includes('307')) {
return; return;
} }
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null,
prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick),
dataset = _.get(prev_info_el, 'dataset', {}); dataset = get(prev_info_el, 'dataset', {});
if (dataset.join === nick) { if (dataset.join === nick) {
let message; let message;
...@@ -1991,7 +1993,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -1991,7 +1993,7 @@ converse.plugins.add('converse-muc-views', {
tpl_info({ tpl_info({
'isodate': date, 'isodate': date,
'extra_classes': 'chat-topic', 'extra_classes': 'chat-topic',
'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), 'message': u.addHyperlinks(xss.filterXSS(get(this.model.get('subject'), 'text'), {'whiteList': {}})),
'render_message': true 'render_message': true
})); }));
} }
...@@ -2059,7 +2061,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -2059,7 +2061,7 @@ converse.plugins.add('converse-muc-views', {
const whitelist = _converse.roomconfig_whitelist; const whitelist = _converse.roomconfig_whitelist;
let fields = sizzle('field', stanza); let fields = sizzle('field', stanza);
if (whitelist.length) { if (whitelist.length) {
fields = fields.filter(f => _.includes(whitelist, f.getAttribute('var'))); fields = fields.filter(f => whitelist.includes(f.getAttribute('var')));
} }
const password_protected = this.model.features.get('passwordprotected'); const password_protected = this.model.features.get('passwordprotected');
const options = { const options = {
...@@ -2068,8 +2070,8 @@ converse.plugins.add('converse-muc-views', { ...@@ -2068,8 +2070,8 @@ converse.plugins.add('converse-muc-views', {
}; };
return tpl_chatroom_form({ return tpl_chatroom_form({
'__': __, '__': __,
'title': _.get(stanza.querySelector('title'), 'textContent'), 'title': get(stanza.querySelector('title'), 'textContent'),
'instructions': _.get(stanza.querySelector('instructions'), 'textContent'), 'instructions': get(stanza.querySelector('instructions'), 'textContent'),
'fields': fields.map(f => u.xForm2webForm(f, stanza, options)) 'fields': fields.map(f => u.xForm2webForm(f, stanza, options))
}); });
}, },
...@@ -2140,20 +2142,12 @@ converse.plugins.add('converse-muc-views', { ...@@ -2140,20 +2142,12 @@ converse.plugins.add('converse-muc-views', {
toHTML () { toHTML () {
const show = this.model.get('show'); const show = this.model.get('show');
return tpl_occupant( return tpl_occupant(
Object.assign( Object.assign({
{ '_': _, __,
show,
'jid': '', 'jid': '',
'show': show,
'hint_show': _converse.PRETTY_CHAT_STATUS[show], 'hint_show': _converse.PRETTY_CHAT_STATUS[show],
'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')), 'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick'))
'desc_moderator': __('This user is a moderator.'),
'desc_participant': __('This user can send messages in this groupchat.'),
'desc_visitor': __('This user can NOT send messages in this groupchat.'),
'label_moderator': __('Moderator'),
'label_visitor': __('Visitor'),
'label_owner': __('Owner'),
'label_member': __('Member'),
'label_admin': __('Admin')
}, this.model.toJSON()) }, this.model.toJSON())
); );
}, },
...@@ -2239,10 +2233,10 @@ converse.plugins.add('converse-muc-views', { ...@@ -2239,10 +2233,10 @@ converse.plugins.add('converse-muc-views', {
renderRoomFeatures () { renderRoomFeatures () {
const features = this.chatroomview.model.features, const features = this.chatroomview.model.features,
picks = _.pick(features.attributes, converse.ROOM_FEATURES), picks = pick(features.attributes, converse.ROOM_FEATURES),
iteratee = (a, v) => a || v; iteratee = (a, v) => a || v;
if (_.reduce(Object.values(picks), iteratee)) { if (Object.values(picks).reduce(iteratee)) {
const el = this.el.querySelector('.chatroom-features'); const el = this.el.querySelector('.chatroom-features');
el.innerHTML = tpl_chatroom_features(Object.assign(features.toJSON(), {__})); el.innerHTML = tpl_chatroom_features(Object.assign(features.toJSON(), {__}));
this.setOccupantsHeight(); this.setOccupantsHeight();
...@@ -2280,22 +2274,20 @@ converse.plugins.add('converse-muc-views', { ...@@ -2280,22 +2274,20 @@ converse.plugins.add('converse-muc-views', {
inviteFormSubmitted (evt) { inviteFormSubmitted (evt) {
evt.preventDefault(); evt.preventDefault();
const el = evt.target.querySelector('input.invited-contact'), const el = evt.target.querySelector('input.invited-contact');
jid = el.value; const jid = el.value;
if (!jid || _.compact(jid.split('@')).length < 2) { if (u.isValid(jid)) {
this.promptForInvite({
'target': el,
'text': {'label': jid, 'value': jid}}
);
} else {
evt.target.outerHTML = tpl_chatroom_invite({ evt.target.outerHTML = tpl_chatroom_invite({
'error_message': __('Please enter a valid XMPP address'), 'error_message': __('Please enter a valid XMPP address'),
'label_invitation': __('Invite'), 'label_invitation': __('Invite'),
}); });
this.initInviteWidget(); this.initInviteWidget();
return;
} }
this.promptForInvite({
'target': el,
'text': {
'label': jid,
'value': jid
}});
}, },
shouldInviteWidgetBeShown () { shouldInviteWidgetBeShown () {
...@@ -2470,7 +2462,7 @@ converse.plugins.add('converse-muc-views', { ...@@ -2470,7 +2462,7 @@ converse.plugins.add('converse-muc-views', {
let views; let views;
if (jids === undefined) { if (jids === undefined) {
views = _converse.chatboxviews; views = _converse.chatboxviews;
} else if (_.isString(jids)) { } else if (isString(jids)) {
views = [_converse.chatboxviews.get(jids)].filter(v => v); views = [_converse.chatboxviews.get(jids)].filter(v => v);
} else if (Array.isArray(jids)) { } else if (Array.isArray(jids)) {
views = jids.map(jid => _converse.chatboxviews.get(jid)); views = jids.map(jid => _converse.chatboxviews.get(jid));
......
<div class="alert alert-danger"> <div class="alert alert-danger">
<h3 class="alert-heading disconnect-msg">{{{o.__('This groupchat no longer exists')}}}</h3> <h3 class="alert-heading disconnect-msg">{{{o.__('This groupchat no longer exists')}}}</h3>
<p class="destroyed-reason">{{{o.reason}}}</p> <p class="destroyed-reason">{{{o.reason}}}</p>
{[ if (o.jid) { ]} {[ if (o.jid) { ]}
<p class="moved-label"> <p class="moved-label">{{{o.__('The conversation has moved. Click below to enter.') }}}</p>
{{{o.__('The conversation has moved. Click below to enter.') }}}
</p>
<p class="moved-link"><a class="switch-chat" href="#">{{{o.jid}}}</a></p> <p class="moved-link"><a class="switch-chat" href="#">{{{o.jid}}}</a></p>
{[ } ]} {[ } ]}
</div> </div>
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<p class="room-info"><strong>{{{o.__('Description')}}}</strong>: {{{o.config.description}}}</p> <p class="room-info"><strong>{{{o.__('Description')}}}</strong>: {{{o.config.description}}}</p>
{[ if (o.subject) { ]} {[ if (o.subject) { ]}
<p class="room-info"><strong>{{{o.__('Topic')}}}</strong>: {{o.topic}}</p> <!-- Sanitized in converse-muc-views. We want to render links. --> <p class="room-info"><strong>{{{o.__('Topic')}}}</strong>: {{o.topic}}</p> <!-- Sanitized in converse-muc-views. We want to render links. -->
<p class="room-info"><strong>{{{o.__('Topic author')}}}</strong>: {{{o._.get(o.subject, 'author')}}}</p> <p class="room-info"><strong>{{{o.__('Topic author')}}}</strong>: {{{o.subject && o.subject.author}}}</p>
{[ } ]} {[ } ]}
<p class="room-info"><strong>{{{o.__('Online users')}}}</strong>: {{{o.num_occupants}}}</p> <p class="room-info"><strong>{{{o.__('Online users')}}}</strong>: {{{o.num_occupants}}}</p>
<p class="room-info"><strong>{{{o.__('Features')}}}</strong>: <p class="room-info"><strong>{{{o.__('Features')}}}</strong>:
......
<div class="alert alert-danger"> <div class="alert alert-danger">
<h3 class="alert-heading disconnect-msg">{{{o.disconnect_messages[0]}}}</h3> <h3 class="alert-heading disconnect-msg">{{{o.messages[0]}}}</h3>
{[ o.messages.slice(1).forEach(function (msg) { ]}
{[ o._.forEach(o.disconnect_messages.slice(1), function (msg) { ]}
<p class="disconnect-msg">{{{msg}}}</p> <p class="disconnect-msg">{{{msg}}}</p>
{[ }); ]} {[ }); ]}
</div> </div>
<li class="occupant" id="{{{ o.id }}}" <li class="occupant" id="{{{ o.id }}}"
{[ if (o.role === "moderator") { ]} {[ if (o.role === "moderator") { ]}
title="{{{ o.jid }}} {{{ o.desc_moderator }}} {{{ o.hint_occupant }}}" title="{{{ o.jid }}} {{{ o.__('This user is a moderator.') }}} {{{ o.hint_occupant }}}"
{[ } ]} {[ } ]}
{[ if (o.role === "participant") { ]} {[ if (o.role === "participant") { ]}
title="{{{ o.jid }}} {{{ o.desc_participant }}} {{{ o.hint_occupant }}}" title="{{{ o.jid }}} {{{ o.__('This user can send messages in this groupchat.') }}} {{{ o.hint_occupant }}}"
{[ } ]} {[ } ]}
{[ if (o.role === "visitor") { ]} {[ if (o.role === "visitor") { ]}
title="{{{ o.jid }}} {{{ o.desc_visitor }}} {{{ o.hint_occupant }}}" title="{{{ o.jid }}} {{{ o.__('This user can NOT send messages in this groupchat.') }}} {{{ o.hint_occupant }}}"
{[ } ]} {[ } ]}
{[ if (!o._.includes(["visitor", "participant", "moderator"], o.role)) { ]} {[ if (!["visitor", "participant", "moderator"].includes(o.role)) { ]}
title="{{{ o.jid }}} {{{ o.hint_occupant }}}" title="{{{ o.jid }}} {{{ o.hint_occupant }}}"
{[ } ]}> {[ } ]}>
<div class="row no-gutters"> <div class="row no-gutters">
...@@ -19,20 +19,20 @@ ...@@ -19,20 +19,20 @@
<span class="occupant-nick">{{{o.nick || o.jid}}}</span> <span class="occupant-nick">{{{o.nick || o.jid}}}</span>
<span class="occupant-badges"> <span class="occupant-badges">
{[ if (o.affiliation === "owner") { ]} {[ if (o.affiliation === "owner") { ]}
<span class="badge badge-groupchat">{{{o.label_owner}}}</span> <span class="badge badge-groupchat">{{{o.__('Owner')}}}</span>
{[ } ]} {[ } ]}
{[ if (o.affiliation === "admin") { ]} {[ if (o.affiliation === "admin") { ]}
<span class="badge badge-info">{{{o.label_admin}}}</span> <span class="badge badge-info">{{{o.__('Admin')}}}</span>
{[ } ]} {[ } ]}
{[ if (o.affiliation === "member") { ]} {[ if (o.affiliation === "member") { ]}
<span class="badge badge-info">{{{o.label_member}}}</span> <span class="badge badge-info">{{{o.__('Member')}}}</span>
{[ } ]} {[ } ]}
{[ if (o.role === "moderator") { ]} {[ if (o.role === "moderator") { ]}
<span class="badge badge-info">{{{o.label_moderator}}}</span> <span class="badge badge-info">{{{o.__('Moderator')}}}</span>
{[ } ]} {[ } ]}
{[ if (o.role === "visitor") { ]} {[ if (o.role === "visitor") { ]}
<span class="badge badge-secondary">{{{o.label_visitor}}}</span> <span class="badge badge-secondary">{{{o.__('Visitor')}}}</span>
{[ } ]} {[ } ]}
</span> </span>
</div> </div>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment