mirror of
https://github.com/moodle/moodle.git
synced 2025-03-18 22:50:19 +01:00
Merge branch 'MDL-43267-master-3' of git://github.com/andrewnicols/moodle
This commit is contained in:
commit
52b7e8aa58
@ -3238,11 +3238,15 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
|
||||
'clicktochangeinbrackets',
|
||||
'markthistopic',
|
||||
'markedthistopic',
|
||||
'move',
|
||||
'movesection',
|
||||
'movecoursemodule',
|
||||
'movecoursesection',
|
||||
'movecontent',
|
||||
'tocontent',
|
||||
'emptydragdropregion'
|
||||
'emptydragdropregion',
|
||||
'afterresource',
|
||||
'aftersection',
|
||||
'totopofsection',
|
||||
), 'moodle');
|
||||
|
||||
// Include section-specific strings for formats which support sections.
|
||||
|
@ -303,7 +303,16 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
this.groups = ['resource'];
|
||||
this.samenodeclass = CSS.ACTIVITY;
|
||||
this.parentnodeclass = CSS.SECTION;
|
||||
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
|
||||
this.samenodelabel = {
|
||||
identifier: 'afterresource',
|
||||
component: 'moodle'
|
||||
};
|
||||
this.parentnodelabel = {
|
||||
identifier: 'totopofsection',
|
||||
component: 'moodle'
|
||||
};
|
||||
|
||||
// Go through all sections
|
||||
var sectionlistselector = M.course.format.get_section_selector(Y);
|
||||
|
File diff suppressed because one or more lines are too long
@ -299,7 +299,16 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
this.groups = ['resource'];
|
||||
this.samenodeclass = CSS.ACTIVITY;
|
||||
this.parentnodeclass = CSS.SECTION;
|
||||
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
|
||||
this.samenodelabel = {
|
||||
identifier: 'afterresource',
|
||||
component: 'moodle'
|
||||
};
|
||||
this.parentnodelabel = {
|
||||
identifier: 'totopofsection',
|
||||
component: 'moodle'
|
||||
};
|
||||
|
||||
// Go through all sections
|
||||
var sectionlistselector = M.course.format.get_section_selector(Y);
|
||||
|
11
course/yui/src/dragdrop/js/resource.js
vendored
11
course/yui/src/dragdrop/js/resource.js
vendored
@ -14,7 +14,16 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
this.groups = ['resource'];
|
||||
this.samenodeclass = CSS.ACTIVITY;
|
||||
this.parentnodeclass = CSS.SECTION;
|
||||
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
|
||||
|
||||
this.samenodelabel = {
|
||||
identifier: 'afterresource',
|
||||
component: 'moodle'
|
||||
};
|
||||
this.parentnodelabel = {
|
||||
identifier: 'totopofsection',
|
||||
component: 'moodle'
|
||||
};
|
||||
|
||||
// Go through all sections
|
||||
var sectionlistselector = M.course.format.get_section_selector(Y);
|
||||
|
@ -115,6 +115,8 @@ $string['administratorsandteachers'] = 'Administrators and teachers';
|
||||
$string['advanced'] = 'Advanced';
|
||||
$string['advancedfilter'] = 'Advanced search';
|
||||
$string['advancedsettings'] = 'Advanced settings';
|
||||
$string['afterresource'] = 'After resource "{$a}"';
|
||||
$string['aftersection'] = 'After section "{$a}"';
|
||||
$string['again'] = 'again';
|
||||
$string['aimid'] = 'AIM ID';
|
||||
$string['ajaxuse'] = 'AJAX and Javascript';
|
||||
@ -1165,6 +1167,8 @@ $string['moreinformation'] = 'More information about this error';
|
||||
$string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
|
||||
$string['mostrecently'] = 'most recently';
|
||||
$string['move'] = 'Move';
|
||||
$string['movecoursemodule'] = 'Move resource';
|
||||
$string['movecoursesection'] = 'Move section';
|
||||
$string['movecontent'] = 'Move {$a}';
|
||||
$string['movecategorycontentto'] = 'Move into';
|
||||
$string['movecategorysuccess'] = 'Successfully moved category \'{$a->moved}\' into category \'{$a->to}\'';
|
||||
@ -1820,6 +1824,7 @@ $string['topicoutline'] = 'Topic outline';
|
||||
$string['topicshow'] = 'Show this topic to {$a}';
|
||||
$string['topichide'] = 'Hide this topic from {$a}';
|
||||
$string['total'] = 'Total';
|
||||
$string['totopofsection'] = 'To the top of section "{$a}"';
|
||||
$string['trackforums'] = 'Forum tracking';
|
||||
$string['trackforumsno'] = 'No: don\'t keep track of posts I have seen';
|
||||
$string['trackforumsyes'] = 'Yes: highlight new posts for me';
|
||||
|
@ -64,6 +64,24 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
parentnodeclass: null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the same Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
samenodelabel : null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the parent Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
parentnodelabel : null,
|
||||
|
||||
/**
|
||||
* The groups for this instance.
|
||||
*
|
||||
@ -148,8 +166,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
.setAttribute('title', title)
|
||||
.setAttribute('tabIndex', 0)
|
||||
.setAttribute('data-draggroups', this.groups)
|
||||
.setAttribute('role', 'button')
|
||||
.setAttribute('aria-grabbed', 'false');
|
||||
.setAttribute('role', 'button');
|
||||
dragelement.appendChild(dragicon);
|
||||
dragelement.addClass(MOVEICON.cssclass);
|
||||
|
||||
@ -162,6 +179,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
|
||||
unlock_drag_handle: function(drag, classname) {
|
||||
drag.addHandle('.'+classname);
|
||||
drag.get('activeHandle').focus();
|
||||
},
|
||||
|
||||
ajax_failure: function(response) {
|
||||
@ -352,8 +370,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
M.core.dragdrop.keydragcontainer = dragcontainer;
|
||||
M.core.dragdrop.keydraghandle = draghandle;
|
||||
|
||||
// Indicate to a screenreader the node that is selected for drag and drop.
|
||||
dragcontainer.setAttribute('aria-grabbed', 'true');
|
||||
// Get the name of the thing to move.
|
||||
var nodetitle = this.find_element_text(dragcontainer);
|
||||
var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
|
||||
@ -397,13 +413,17 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
listlink = Y.Node.create('<a></a>');
|
||||
nodetitle = this.find_element_text(labelroot);
|
||||
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
if (this.samenodelabel && node.hasClass(this.samenodeclass)) {
|
||||
listitemtext = M.util.get_string(this.samenodelabel.identifier, this.samenodelabel.component, nodetitle);
|
||||
} else if (this.parentnodelabel && node.hasClass(this.parentnodeclass)) {
|
||||
listitemtext = M.util.get_string(this.parentnodelabel.identifier, this.parentnodelabel.component, nodetitle);
|
||||
} else {
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
}
|
||||
listlink.setContent(listitemtext);
|
||||
|
||||
// Add a data attribute so we can get the real drop target.
|
||||
listlink.setAttribute('data-drop-target', node.get('id'));
|
||||
// Notify the screen reader this is a valid drop target.
|
||||
listlink.setAttribute('aria-dropeffect', 'move');
|
||||
// Allow tabbing to the link.
|
||||
listlink.setAttribute('tabindex', '0');
|
||||
|
||||
@ -423,9 +443,18 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
bodyContent: droplist,
|
||||
draggable: true,
|
||||
visible: true,
|
||||
centered: true
|
||||
center: true,
|
||||
modal: true
|
||||
});
|
||||
|
||||
M.core.dragdrop.dropui.after('visibleChange', function(e) {
|
||||
// After the dialogue has been closed, we call the cancel function. This will
|
||||
// ensure that tidying up happens (e.g. focusing on the start Node).
|
||||
if (e.prevVal && !e.newVal) {
|
||||
this.global_cancel_keyboard_drag();
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Focus the first drop target.
|
||||
if (droplist.one('a')) {
|
||||
droplist.one('a').focus();
|
||||
@ -465,6 +494,9 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
|
||||
return this.node;
|
||||
}
|
||||
if (param === 'activeHandle') {
|
||||
return this.node.one('.editing_move');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -493,7 +525,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
global_keyboard_drop: function(e) {
|
||||
// The drag node was saved.
|
||||
var dragcontainer = M.core.dragdrop.keydragcontainer;
|
||||
dragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// The real drop node is stored in an attribute of the proxy.
|
||||
var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
|
||||
|
||||
@ -508,7 +539,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
this.drag_start(dragevent);
|
||||
this.global_drop_over(dropevent);
|
||||
this.global_drop_hit(dropevent);
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -518,7 +548,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
global_cancel_keyboard_drag: function() {
|
||||
if (M.core.dragdrop.keydragcontainer) {
|
||||
M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// Focus on the node which was being dragged.
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
M.core.dragdrop.keydragcontainer = null;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -64,6 +64,24 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
parentnodeclass: null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the same Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
samenodelabel : null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the parent Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
parentnodelabel : null,
|
||||
|
||||
/**
|
||||
* The groups for this instance.
|
||||
*
|
||||
@ -148,8 +166,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
.setAttribute('title', title)
|
||||
.setAttribute('tabIndex', 0)
|
||||
.setAttribute('data-draggroups', this.groups)
|
||||
.setAttribute('role', 'button')
|
||||
.setAttribute('aria-grabbed', 'false');
|
||||
.setAttribute('role', 'button');
|
||||
dragelement.appendChild(dragicon);
|
||||
dragelement.addClass(MOVEICON.cssclass);
|
||||
|
||||
@ -162,6 +179,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
|
||||
unlock_drag_handle: function(drag, classname) {
|
||||
drag.addHandle('.'+classname);
|
||||
drag.get('activeHandle').focus();
|
||||
},
|
||||
|
||||
ajax_failure: function(response) {
|
||||
@ -352,8 +370,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
M.core.dragdrop.keydragcontainer = dragcontainer;
|
||||
M.core.dragdrop.keydraghandle = draghandle;
|
||||
|
||||
// Indicate to a screenreader the node that is selected for drag and drop.
|
||||
dragcontainer.setAttribute('aria-grabbed', 'true');
|
||||
// Get the name of the thing to move.
|
||||
var nodetitle = this.find_element_text(dragcontainer);
|
||||
var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
|
||||
@ -397,13 +413,17 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
listlink = Y.Node.create('<a></a>');
|
||||
nodetitle = this.find_element_text(labelroot);
|
||||
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
if (this.samenodelabel && node.hasClass(this.samenodeclass)) {
|
||||
listitemtext = M.util.get_string(this.samenodelabel.identifier, this.samenodelabel.component, nodetitle);
|
||||
} else if (this.parentnodelabel && node.hasClass(this.parentnodeclass)) {
|
||||
listitemtext = M.util.get_string(this.parentnodelabel.identifier, this.parentnodelabel.component, nodetitle);
|
||||
} else {
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
}
|
||||
listlink.setContent(listitemtext);
|
||||
|
||||
// Add a data attribute so we can get the real drop target.
|
||||
listlink.setAttribute('data-drop-target', node.get('id'));
|
||||
// Notify the screen reader this is a valid drop target.
|
||||
listlink.setAttribute('aria-dropeffect', 'move');
|
||||
// Allow tabbing to the link.
|
||||
listlink.setAttribute('tabindex', '0');
|
||||
|
||||
@ -423,9 +443,18 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
bodyContent: droplist,
|
||||
draggable: true,
|
||||
visible: true,
|
||||
centered: true
|
||||
center: true,
|
||||
modal: true
|
||||
});
|
||||
|
||||
M.core.dragdrop.dropui.after('visibleChange', function(e) {
|
||||
// After the dialogue has been closed, we call the cancel function. This will
|
||||
// ensure that tidying up happens (e.g. focusing on the start Node).
|
||||
if (e.prevVal && !e.newVal) {
|
||||
this.global_cancel_keyboard_drag();
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Focus the first drop target.
|
||||
if (droplist.one('a')) {
|
||||
droplist.one('a').focus();
|
||||
@ -465,6 +494,9 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
|
||||
return this.node;
|
||||
}
|
||||
if (param === 'activeHandle') {
|
||||
return this.node.one('.editing_move');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -493,7 +525,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
global_keyboard_drop: function(e) {
|
||||
// The drag node was saved.
|
||||
var dragcontainer = M.core.dragdrop.keydragcontainer;
|
||||
dragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// The real drop node is stored in an attribute of the proxy.
|
||||
var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
|
||||
|
||||
@ -508,7 +539,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
this.drag_start(dragevent);
|
||||
this.global_drop_over(dropevent);
|
||||
this.global_drop_hit(dropevent);
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -518,7 +548,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
global_cancel_keyboard_drag: function() {
|
||||
if (M.core.dragdrop.keydragcontainer) {
|
||||
M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// Focus on the node which was being dragged.
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
M.core.dragdrop.keydragcontainer = null;
|
||||
}
|
||||
|
52
lib/yui/src/dragdrop/js/dragdrop.js
vendored
52
lib/yui/src/dragdrop/js/dragdrop.js
vendored
@ -62,6 +62,24 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
parentnodeclass: null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the same Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
samenodelabel : null,
|
||||
|
||||
/**
|
||||
* The label to use with keyboard drag/drop to describe items of the parent Node.
|
||||
*
|
||||
* @property samenodelabel
|
||||
* @type Object
|
||||
* @default null
|
||||
*/
|
||||
parentnodelabel : null,
|
||||
|
||||
/**
|
||||
* The groups for this instance.
|
||||
*
|
||||
@ -146,8 +164,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
.setAttribute('title', title)
|
||||
.setAttribute('tabIndex', 0)
|
||||
.setAttribute('data-draggroups', this.groups)
|
||||
.setAttribute('role', 'button')
|
||||
.setAttribute('aria-grabbed', 'false');
|
||||
.setAttribute('role', 'button');
|
||||
dragelement.appendChild(dragicon);
|
||||
dragelement.addClass(MOVEICON.cssclass);
|
||||
|
||||
@ -160,6 +177,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
|
||||
unlock_drag_handle: function(drag, classname) {
|
||||
drag.addHandle('.'+classname);
|
||||
drag.get('activeHandle').focus();
|
||||
},
|
||||
|
||||
ajax_failure: function(response) {
|
||||
@ -350,8 +368,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
M.core.dragdrop.keydragcontainer = dragcontainer;
|
||||
M.core.dragdrop.keydraghandle = draghandle;
|
||||
|
||||
// Indicate to a screenreader the node that is selected for drag and drop.
|
||||
dragcontainer.setAttribute('aria-grabbed', 'true');
|
||||
// Get the name of the thing to move.
|
||||
var nodetitle = this.find_element_text(dragcontainer);
|
||||
var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
|
||||
@ -395,13 +411,17 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
listlink = Y.Node.create('<a></a>');
|
||||
nodetitle = this.find_element_text(labelroot);
|
||||
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
if (this.samenodelabel && node.hasClass(this.samenodeclass)) {
|
||||
listitemtext = M.util.get_string(this.samenodelabel.identifier, this.samenodelabel.component, nodetitle);
|
||||
} else if (this.parentnodelabel && node.hasClass(this.parentnodeclass)) {
|
||||
listitemtext = M.util.get_string(this.parentnodelabel.identifier, this.parentnodelabel.component, nodetitle);
|
||||
} else {
|
||||
listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
|
||||
}
|
||||
listlink.setContent(listitemtext);
|
||||
|
||||
// Add a data attribute so we can get the real drop target.
|
||||
listlink.setAttribute('data-drop-target', node.get('id'));
|
||||
// Notify the screen reader this is a valid drop target.
|
||||
listlink.setAttribute('aria-dropeffect', 'move');
|
||||
// Allow tabbing to the link.
|
||||
listlink.setAttribute('tabindex', '0');
|
||||
|
||||
@ -421,9 +441,18 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
bodyContent: droplist,
|
||||
draggable: true,
|
||||
visible: true,
|
||||
centered: true
|
||||
center: true,
|
||||
modal: true
|
||||
});
|
||||
|
||||
M.core.dragdrop.dropui.after('visibleChange', function(e) {
|
||||
// After the dialogue has been closed, we call the cancel function. This will
|
||||
// ensure that tidying up happens (e.g. focusing on the start Node).
|
||||
if (e.prevVal && !e.newVal) {
|
||||
this.global_cancel_keyboard_drag();
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Focus the first drop target.
|
||||
if (droplist.one('a')) {
|
||||
droplist.one('a').focus();
|
||||
@ -463,6 +492,9 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
|
||||
return this.node;
|
||||
}
|
||||
if (param === 'activeHandle') {
|
||||
return this.node.one('.editing_move');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -491,7 +523,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
global_keyboard_drop: function(e) {
|
||||
// The drag node was saved.
|
||||
var dragcontainer = M.core.dragdrop.keydragcontainer;
|
||||
dragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// The real drop node is stored in an attribute of the proxy.
|
||||
var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
|
||||
|
||||
@ -506,7 +537,6 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
this.drag_start(dragevent);
|
||||
this.global_drop_over(dropevent);
|
||||
this.global_drop_hit(dropevent);
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -516,7 +546,7 @@ Y.extend(DRAGDROP, Y.Base, {
|
||||
*/
|
||||
global_cancel_keyboard_drag: function() {
|
||||
if (M.core.dragdrop.keydragcontainer) {
|
||||
M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
|
||||
// Focus on the node which was being dragged.
|
||||
M.core.dragdrop.keydraghandle.focus();
|
||||
M.core.dragdrop.keydragcontainer = null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user