moodle/comment/comment.js
Paul Holden c8d32c279d
MDL-82309 comment: preserve link text property during AJAX requests.
We really only need to update the count value that follows the link
text content, so do that rather than overwriting the entire thing.
2024-07-09 09:17:10 +01:00

461 lines
20 KiB
JavaScript

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Comment Helper
* @author Dongsheng Cai <dongsheng@moodle.com>
*/
M.core_comment = {
/**
* Initialize commenting system
*/
init: function(Y, options) {
var CommentHelper = function(args) {
CommentHelper.superclass.constructor.apply(this, arguments);
};
CommentHelper.NAME = "COMMENT";
CommentHelper.ATTRS = {
options: {},
lang: {}
};
Y.extend(CommentHelper, Y.Base, {
api: M.cfg.wwwroot+'/comment/comment_ajax.php',
initializer: function(args) {
var scope = this;
this.client_id = args.client_id;
this.itemid = args.itemid;
this.commentarea = args.commentarea;
this.component = args.component;
this.courseid = args.courseid;
this.contextid = args.contextid;
this.autostart = (args.autostart);
// Fail fast if the comments element cannot be found, such as in embedded-type views where blocks may be loaded
// then discarded.
if (!Y.one('#comment-ctrl-'+this.client_id)) {
return;
}
// expand comments?
if (this.autostart) {
this.view(args.page);
}
// load comments
var handle = Y.one('#comment-link-'+this.client_id);
// hide toggle link
if (handle) {
if (args.notoggle) {
handle.setStyle('display', 'none');
}
handle.on('click', function(e) {
e.preventDefault();
this.view(0);
return false;
}, this);
// Also handle space/enter key.
handle.on('key', function(e) {
e.preventDefault();
this.view(0);
return false;
}, '13,32', this);
}
scope.toggle_textarea(false);
},
post: function() {
var ta = Y.one('#dlg-content-'+this.client_id);
var scope = this;
var value = ta.get('value');
if (value && value != M.util.get_string('addcomment', 'moodle')) {
ta.set('disabled', true);
ta.setStyles({
'backgroundImage': 'url(' + M.util.image_url('i/loading_small', 'core') + ')',
'backgroundRepeat': 'no-repeat',
'backgroundPosition': 'center center'
});
var params = {'content': value};
this.request({
action: 'add',
scope: scope,
params: params,
callback: async function(id, obj, args) {
var scope = args.scope;
var cid = scope.client_id;
var ta = Y.one('#dlg-content-'+cid);
ta.set('value', '');
ta.set('disabled', false);
ta.setStyle('backgroundImage', 'none');
scope.toggle_textarea(false);
var container = Y.one('#comment-list-'+cid);
var result = await scope.render([obj], true);
var newcomment = Y.Node.create(result.html);
container.appendChild(newcomment);
var ids = result.ids;
var linkTextCount = Y.one('#comment-link-text-' + cid + ' .comment-link-count');
if (linkTextCount) {
linkTextCount.set('innerHTML', obj.count);
}
for(var i in ids) {
var attributes = {
color: { to: '#06e' },
backgroundColor: { to: '#FFE390' }
};
var anim = new Y.YUI2.util.ColorAnim(ids[i], attributes);
anim.animate();
}
scope.register_pagination();
scope.register_delete_buttons();
}
}, true);
} else {
var attributes = {
backgroundColor: { from: '#FFE390', to:'#FFFFFF' }
};
var anim = new Y.YUI2.util.ColorAnim('dlg-content-'+cid, attributes);
anim.animate();
}
},
request: function(args, noloading) {
var params = {};
var scope = this;
if (args['scope']) {
scope = args['scope'];
}
//params['page'] = args.page?args.page:'';
// the form element only accept certain file types
params['sesskey'] = M.cfg.sesskey;
params['action'] = args.action?args.action:'';
params['client_id'] = this.client_id;
params['itemid'] = this.itemid;
params['area'] = this.commentarea;
params['courseid'] = this.courseid;
params['contextid'] = this.contextid;
params['component'] = this.component;
if (args['params']) {
for (i in args['params']) {
params[i] = args['params'][i];
}
}
var cfg = {
method: 'POST',
on: {
complete: function(id,o,p) {
if (!o) {
alert('IO FATAL');
return false;
}
var data = Y.JSON.parse(o.responseText);
if (data.error) {
if (data.error == 'require_login') {
args.callback(id,data,p);
return true;
}
alert(data.error);
return false;
} else {
args.callback(id,data,p);
return true;
}
}
},
arguments: {
scope: scope
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: build_querystring(params)
};
if (args.form) {
cfg.form = args.form;
}
Y.io(this.api, cfg);
if (!noloading) {
this.wait();
}
},
render: async function(list, newcmt) {
var ret = {};
ret.ids = [];
var template = Y.one('#cmt-tmpl');
var html = '';
for(var i in list) {
var htmlid = 'comment-'+list[i].id+'-'+this.client_id;
var val = template.get('innerHTML');
if (list[i].profileurl) {
val = val.replace('___name___', '<a href="'+list[i].profileurl+'">'+list[i].fullname+'</a>');
} else {
val = val.replace('___name___', list[i].fullname);
}
if (list[i].delete || newcmt) {
list[i].clientid = this.client_id;
list[i].content += await this.renderDeleteIcon(list[i]);
}
val = val.replace('___time___', list[i].time);
val = val.replace('___picture___', list[i].avatar);
val = val.replace('___content___', list[i].content);
val = '<li id="'+htmlid+'">'+val+'</li>';
ret.ids.push(htmlid);
html = (val+html);
}
ret.html = html;
return ret;
},
renderDeleteIcon: async function(list) {
return new Promise(function(resolve) {
require(['core/templates', 'core/str'], (Templates, Str) => {
return Str.get_string('deletecommentbyon', 'moodle', {
user: list.fullname,
time: list.time
}).then(function(deleteStr) {
return Templates.renderPix('t/delete', 'core', deleteStr).then(function(deleteIcon) {
var deleteDiv = document.createElement('div');
deleteDiv.className = 'comment-delete';
var deleteLink = document.createElement('a');
deleteLink.href = '#';
deleteLink.role = 'button';
deleteLink.title = deleteStr;
deleteLink.id = `comment-delete-${list.clientid}-${list.id}`;
deleteLink.innerHTML = deleteIcon;
deleteDiv.appendChild(deleteLink);
resolve(deleteDiv.outerHTML);
return true;
});
});
});
});
},
load: function(page) {
var scope = this;
var container = Y.one('#comment-ctrl-'+this.client_id);
var params = {
'action': 'get',
'page': page
};
this.request({
scope: scope,
params: params,
callback: async function(id, ret, args) {
var linkTextCount = Y.one('#comment-link-text-' + scope.client_id + ' .comment-link-count');
if (linkTextCount) {
linkTextCount.set('innerHTML', ret.count);
}
var container = Y.one('#comment-list-'+scope.client_id);
var pagination = Y.one('#comment-pagination-'+scope.client_id);
if (ret.pagination) {
pagination.set('innerHTML', ret.pagination);
} else {
//empty paging bar
pagination.set('innerHTML', '');
}
if (ret.error == 'require_login') {
var result = {};
result.html = M.util.get_string('commentsrequirelogin', 'moodle');
} else {
var result = await scope.render(ret.list);
}
container.set('innerHTML', result.html);
var img = Y.one('#comment-img-'+scope.client_id);
if (img) {
img.set('src', M.util.image_url('t/expanded', 'core'));
}
args.scope.register_pagination();
args.scope.register_delete_buttons();
}
});
},
dodelete: function(id) { // note: delete is a reserved word in javascript, chrome and safary do not like it at all here!
var scope = this,
cid = scope.client_id,
params = {'commentid': id};
function remove_dom(type, anim, cmt) {
cmt.remove();
var linkTextCount = Y.one('#comment-link-text-' + cid + ' .comment-link-count'),
comments = Y.all('#comment-list-' + cid + ' li');
if (linkTextCount) {
linkTextCount.set('innerHTML', comments.size());
}
}
this.request({
action: 'delete',
scope: scope,
params: params,
callback: function(id, resp, args) {
var htmlid= 'comment-'+resp.commentid+'-'+resp.client_id;
var attributes = {
width:{to:0},
height:{to:0}
};
var cmt = Y.one('#'+htmlid);
cmt.setStyle('overflow', 'hidden');
var anim = new Y.YUI2.util.Anim(htmlid, attributes, 1, Y.YUI2.util.Easing.easeOut);
anim.onComplete.subscribe(remove_dom, cmt, this);
anim.animate();
}
}, true);
},
register_actions: function() {
// add new comment
var action_btn = Y.one('#comment-action-post-'+this.client_id);
if (action_btn) {
action_btn.on('click', function(e) {
e.preventDefault();
this.post();
return false;
}, this);
}
// cancel comment box
var cancel = Y.one('#comment-action-cancel-'+this.client_id);
if (cancel) {
cancel.on('click', function(e) {
e.preventDefault();
this.view(0);
return false;
}, this);
}
},
register_delete_buttons: function() {
var scope = this;
// page buttons
Y.all('div.comment-delete a').each(
function(node, id) {
var theid = node.get('id');
var parseid = new RegExp("comment-delete-"+scope.client_id+"-(\\d+)", "i");
var commentid = theid.match(parseid);
if (!commentid) {
return;
}
if (commentid[1]) {
Y.Event.purgeElement('#'+theid, false, 'click');
}
node.on('click', function(e) {
e.preventDefault();
if (commentid[1]) {
scope.dodelete(commentid[1]);
}
});
// Also handle space/enter key.
node.on('key', function(e) {
e.preventDefault();
if (commentid[1]) {
scope.dodelete(commentid[1]);
}
}, '13,32');
// 13 and 32 are the keycodes for space and enter.
}
);
},
register_pagination: function() {
var scope = this;
// page buttons
Y.all('#comment-pagination-'+this.client_id+' a').each(
function(node, id) {
node.on('click', function(e, node) {
e.preventDefault();
var id = node.get('id');
var re = new RegExp("comment-page-"+this.client_id+"-(\\d+)", "i");
var result = id.match(re);
this.load(result[1]);
}, scope, node);
}
);
},
view: function(page) {
var commenttoggler = Y.one('#comment-link-' + this.client_id);
var container = Y.one('#comment-ctrl-'+this.client_id);
var ta = Y.one('#dlg-content-'+this.client_id);
var img = Y.one('#comment-img-'+this.client_id);
var d = container.getStyle('display');
if (d=='none'||d=='') {
// show
if (!this.autostart) {
this.load(page);
} else {
this.register_delete_buttons();
this.register_pagination();
}
container.setStyle('display', 'block');
if (img) {
img.set('src', M.util.image_url('t/expanded', 'core'));
}
if (commenttoggler) {
commenttoggler.setAttribute('aria-expanded', 'true');
}
} else {
// hide
container.setStyle('display', 'none');
var collapsedimage = 't/collapsed'; // ltr mode
if ( Y.one(document.body).hasClass('dir-rtl') ) {
collapsedimage = 't/collapsed_rtl';
} else {
collapsedimage = 't/collapsed';
}
if (img) {
img.set('src', M.util.image_url(collapsedimage, 'core'));
}
if (ta) {
ta.set('value','');
}
if (commenttoggler) {
commenttoggler.setAttribute('aria-expanded', 'false');
}
}
if (ta) {
//toggle_textarea.apply(ta, [false]);
//// reset textarea size
ta.on('focus', function() {
this.toggle_textarea(true);
}, this);
//ta.onkeypress = function() {
//if (this.scrollHeight > this.clientHeight && !window.opera)
//this.rows += 1;
//}
ta.on('blur', function() {
this.toggle_textarea(false);
}, this);
}
this.register_actions();
return false;
},
toggle_textarea: function(focus) {
var t = Y.one('#dlg-content-'+this.client_id);
if (!t) {
return false;
}
if (focus) {
if (t.get('value') == M.util.get_string('addcomment', 'moodle')) {
t.set('value', '');
t.setStyle('color', 'black');
}
}else{
if (t.get('value') == '') {
t.set('value', M.util.get_string('addcomment', 'moodle'));
t.setStyle('color','grey');
t.set('rows', 2);
}
}
},
wait: function() {
var container = Y.one('#comment-list-'+this.client_id);
container.set('innerHTML', '<div class="mdl-align"><img src="'+M.util.image_url('i/loading_small', 'core')+'" /></div>');
}
});
new CommentHelper(options);
}
};