mirror of
https://github.com/jupyter/notebook.git
synced 2025-02-05 12:19:58 +08:00
Update to codemirror 4
Update to codemirror 4.6 and update most notebook to work wiht it this include keyevent that where triggerd twice and the re-writing of ipython-gfm anf ipython mode to work with require.
This commit is contained in:
parent
262bf7d2c9
commit
5b355936df
@ -4,7 +4,8 @@
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
], function(IPython, $) {
|
||||
'codemirror/lib/codemirror',
|
||||
], function(IPython, $, CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var modal = function (options) {
|
||||
|
@ -1,5 +1,12 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module keyboard
|
||||
* @namespace keyboard
|
||||
* @class ShortcutManager
|
||||
*/
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
@ -126,6 +133,12 @@ define([
|
||||
// Shortcut manager class
|
||||
|
||||
var ShortcutManager = function (delay, events) {
|
||||
/**
|
||||
* A class to deal with keyboard event and shortcut
|
||||
*
|
||||
* @class ShortcutManager
|
||||
* @constructor
|
||||
*/
|
||||
this._shortcuts = {};
|
||||
this._counts = {};
|
||||
this._timers = {};
|
||||
@ -201,6 +214,16 @@ define([
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
|
||||
/**
|
||||
* Seem to allow to call an handler only after several key press.
|
||||
* like, I suppose `dd` that delete the current cell only after
|
||||
* `d` has been pressed twice..
|
||||
* @method count_handler
|
||||
* @return {Boolean} `true|false`, whether or not the event has been handled.
|
||||
* @param shortcut {shortcut}
|
||||
* @param event {event}
|
||||
* @param data {data}
|
||||
*/
|
||||
var that = this;
|
||||
var c = this._counts;
|
||||
var t = this._timers;
|
||||
@ -221,6 +244,12 @@ define([
|
||||
};
|
||||
|
||||
ShortcutManager.prototype.call_handler = function (event) {
|
||||
/**
|
||||
* Call the corresponding shortcut handler for a keyboard event
|
||||
* @method call_handler
|
||||
* @return {Boolean} `true|false`, `false` if no handler was found, otherwise the value return by the handler.
|
||||
* @param event {event}
|
||||
*/
|
||||
var shortcut = event_to_shortcut(event);
|
||||
var data = this._shortcuts[shortcut];
|
||||
if (data) {
|
||||
@ -252,7 +281,7 @@ define([
|
||||
event_to_shortcut : event_to_shortcut
|
||||
};
|
||||
|
||||
// For backwards compatability.
|
||||
// For backwards compatibility.
|
||||
IPython.keyboard = keyboard;
|
||||
|
||||
return keyboard;
|
||||
|
@ -4,7 +4,8 @@
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
], function(IPython, $){
|
||||
'codemirror/lib/codemirror',
|
||||
], function(IPython, $, CodeMirror){
|
||||
"use strict";
|
||||
|
||||
IPython.load_extensions = function () {
|
||||
|
@ -1,11 +1,24 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module cell
|
||||
* @namespace cell
|
||||
* @class Cell
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
'jquery',
|
||||
'base/js/utils',
|
||||
], function(IPython, $, utils) {
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/addon/edit/matchbrackets',
|
||||
'codemirror/addon/edit/closebrackets',
|
||||
'codemirror/addon/comment/comment'
|
||||
], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
|
||||
// TODO: remove IPython dependency here
|
||||
"use strict";
|
||||
|
||||
@ -29,16 +42,17 @@ define([
|
||||
// end monkey patching CodeMirror
|
||||
|
||||
var Cell = function (options) {
|
||||
// Constructor
|
||||
//
|
||||
// The Base `Cell` class from which to inherit.
|
||||
//
|
||||
// Parameters:
|
||||
// options: dictionary
|
||||
// Dictionary of keyword arguments.
|
||||
// events: $(Events) instance
|
||||
// config: dictionary
|
||||
// keyboard_manager: KeyboardManager instance
|
||||
/* Constructor
|
||||
*
|
||||
* The Base `Cell` class from which to inherit.
|
||||
* @constructor
|
||||
* @param:
|
||||
* options: dictionary
|
||||
* Dictionary of keyword arguments.
|
||||
* events: $(Events) instance
|
||||
* config: dictionary
|
||||
* keyboard_manager: KeyboardManager instance
|
||||
*/
|
||||
options = options || {};
|
||||
this.keyboard_manager = options.keyboard_manager;
|
||||
this.events = options.events;
|
||||
@ -278,8 +292,6 @@ define([
|
||||
*/
|
||||
Cell.prototype.handle_keyevent = function (editor, event) {
|
||||
|
||||
// console.log('CM', this.mode, event.which, event.type)
|
||||
|
||||
if (this.mode === 'command') {
|
||||
return true;
|
||||
} else if (this.mode === 'edit') {
|
||||
|
@ -1,5 +1,13 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module codecell
|
||||
* @namespace codecell
|
||||
* @class CodeCell
|
||||
*/
|
||||
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
@ -10,7 +18,10 @@ define([
|
||||
'notebook/js/outputarea',
|
||||
'notebook/js/completer',
|
||||
'notebook/js/celltoolbar',
|
||||
], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/python/python',
|
||||
'notebook/js/codemirror-ipython'
|
||||
], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
|
||||
"use strict";
|
||||
var Cell = cell.Cell;
|
||||
|
||||
@ -102,9 +113,7 @@ define([
|
||||
},
|
||||
mode: 'ipython',
|
||||
theme: 'ipython',
|
||||
matchBrackets: true,
|
||||
// don't auto-close strings because of CodeMirror #2385
|
||||
autoCloseBrackets: "()[]{}"
|
||||
matchBrackets: true
|
||||
}
|
||||
};
|
||||
|
||||
@ -135,6 +144,7 @@ define([
|
||||
inner_cell.append(this.celltoolbar.element);
|
||||
var input_area = $('<div/>').addClass('input_area');
|
||||
this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
|
||||
this.code_mirror.on('keydown', $.proxy(this.handle_codemirror_keyevent,this))
|
||||
$(this.code_mirror.getInputField()).attr("spellcheck", "false");
|
||||
inner_cell.append(input_area);
|
||||
input.append(prompt).append(inner_cell);
|
||||
@ -220,7 +230,8 @@ define([
|
||||
}
|
||||
// If we closed the tooltip, don't let CM or the global handlers
|
||||
// handle this event.
|
||||
event.stop();
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
return true;
|
||||
} else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
|
||||
if (editor.somethingSelected()){
|
||||
@ -231,7 +242,8 @@ define([
|
||||
}
|
||||
}
|
||||
this.tooltip.request(that);
|
||||
event.stop();
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
return true;
|
||||
} else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
|
||||
// Tab completion.
|
||||
@ -245,7 +257,8 @@ define([
|
||||
// is empty. In this case, let CodeMirror handle indentation.
|
||||
return false;
|
||||
} else {
|
||||
event.stop();
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
this.completer.startCompletion();
|
||||
return true;
|
||||
}
|
||||
|
@ -3,9 +3,20 @@
|
||||
// callback to auto-load python mode, which is more likely not the best things
|
||||
// to do, but at least the simple one for now.
|
||||
|
||||
CodeMirror.requireMode('python',function(){
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object"){ // CommonJS
|
||||
mod(require("codemirror/lib/codemirror"),
|
||||
require("codemirror/mode/python/python")
|
||||
);
|
||||
} else if (typeof define == "function" && define.amd){ // AMD
|
||||
define(["codemirror/lib/codemirror",
|
||||
"codemirror/mode/python/python"], mod);
|
||||
} else {// Plain browser env
|
||||
mod(CodeMirror);
|
||||
}
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
|
||||
CodeMirror.defineMode("ipython", function(conf, parserConf) {
|
||||
var pythonConf = {};
|
||||
for (var prop in parserConf) {
|
||||
@ -25,3 +36,4 @@ CodeMirror.requireMode('python',function(){
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipython", "ipython");
|
||||
})
|
||||
|
||||
|
@ -6,39 +6,59 @@
|
||||
// But was later removed in
|
||||
// https://github.com/codemirror/CodeMirror/commit/d9c9f1b1ffe984aee41307f3e927f80d1f23590c
|
||||
|
||||
CodeMirror.requireMode('gfm', function(){
|
||||
CodeMirror.requireMode('stex', function(){
|
||||
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
|
||||
|
||||
var gfm_mode = CodeMirror.getMode(config, "gfm");
|
||||
var tex_mode = CodeMirror.getMode(config, "stex");
|
||||
|
||||
return CodeMirror.multiplexingMode(
|
||||
gfm_mode,
|
||||
{
|
||||
open: "$", close: "$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "$$", close: "$$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\(", close: "\\)",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\[", close: "\\]",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
}
|
||||
// .. more multiplexed styles can follow here
|
||||
);
|
||||
}, 'gfm');
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object"){ // CommonJS
|
||||
mod(require("codemirror/lib/codemirror")
|
||||
,require("codemirror/addon/mode/multiplex")
|
||||
,require("codemirror/mode/gfm/gfm")
|
||||
,require("codemirror/mode/stex/stex")
|
||||
);
|
||||
} else if (typeof define == "function" && define.amd){ // AMD
|
||||
define(["codemirror/lib/codemirror"
|
||||
,"codemirror/addon/mode/multiplex"
|
||||
,"codemirror/mode/python/python"
|
||||
,"codemirror/mode/stex/stex"
|
||||
], mod);
|
||||
} else {// Plain browser env
|
||||
mod(CodeMirror);
|
||||
}
|
||||
})( function(CodeMirror){
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm");
|
||||
});
|
||||
});
|
||||
var gfm_mode = CodeMirror.getMode(config, "gfm");
|
||||
var tex_mode = CodeMirror.getMode(config, "stex");
|
||||
|
||||
return CodeMirror.multiplexingMode(
|
||||
gfm_mode,
|
||||
{
|
||||
open: "$", close: "$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
// not sure this works as $$ is interpreted at (opening $, closing $, as defined just above)
|
||||
open: "$$", close: "$$",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\(", close: "\\)",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
},
|
||||
{
|
||||
open: "\\[", close: "\\]",
|
||||
mode: tex_mode,
|
||||
delimStyle: "delimit"
|
||||
}
|
||||
// .. more multiplexed styles can follow here
|
||||
);
|
||||
}, 'gfm');
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm");
|
||||
|
||||
|
||||
})
|
||||
|
@ -7,7 +7,8 @@ define([
|
||||
'base/js/utils',
|
||||
'base/js/keyboard',
|
||||
'notebook/js/contexthint',
|
||||
], function(IPython, $, utils, keyboard) {
|
||||
'codemirror/lib/codemirror',
|
||||
], function(IPython, $, utils, keyboard, CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
// easier key mapping
|
||||
@ -316,11 +317,13 @@ define([
|
||||
|
||||
// Enter
|
||||
if (code == keycodes.enter) {
|
||||
CodeMirror.e_stop(event);
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
this.pick();
|
||||
// Escape or backspace
|
||||
} else if (code == keycodes.esc || code == keycodes.backspace) {
|
||||
CodeMirror.e_stop(event);
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
this.close();
|
||||
} else if (code == keycodes.tab) {
|
||||
//all the fastforwarding operation,
|
||||
@ -339,7 +342,8 @@ define([
|
||||
} else if (code == keycodes.up || code == keycodes.down) {
|
||||
// need to do that to be able to move the arrow
|
||||
// when on the first or last line ofo a code cell
|
||||
CodeMirror.e_stop(event);
|
||||
event.codemirrorIgnore = true;
|
||||
event.preventDefault();
|
||||
|
||||
var options = this.sel.find('option');
|
||||
var index = this.sel[0].selectedIndex;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// highly adapted for codemiror jshint
|
||||
define([], function() {
|
||||
define(['codemirror/lib/codemirror'], function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var forEach = function(arr, f) {
|
||||
|
@ -1,5 +1,12 @@
|
||||
// Copyright (c) IPython Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @module keyboardmanager
|
||||
* @namespace keyboardmanager
|
||||
* @class KeyboardManager
|
||||
*/
|
||||
|
||||
define([
|
||||
'base/js/namespace',
|
||||
@ -16,13 +23,15 @@ define([
|
||||
var keycodes = keyboard.keycodes;
|
||||
|
||||
var KeyboardManager = function (options) {
|
||||
// Constructor
|
||||
//
|
||||
// Parameters:
|
||||
// options: dictionary
|
||||
// Dictionary of keyword arguments.
|
||||
// events: $(Events) instance
|
||||
// pager: Pager instance
|
||||
/**
|
||||
* A class to deal with keyboard event and shortcut
|
||||
*
|
||||
* @class KeyboardManager
|
||||
* @constructor
|
||||
* @param options {dict} Dictionary of keyword arguments :
|
||||
* @param options.events {$(Events)} instance
|
||||
* @param options.pager: {Pager} pager instance
|
||||
*/
|
||||
this.mode = 'command';
|
||||
this.enabled = true;
|
||||
this.pager = options.pager;
|
||||
@ -37,6 +46,22 @@ define([
|
||||
this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a dict of common shortcut
|
||||
* @method get_default_common_shortcuts
|
||||
*
|
||||
* @example Example of returned shortcut
|
||||
* ```
|
||||
* 'shortcut-key': // a string representing the shortcut as dash separated value.
|
||||
* // e.g. 'shift' , 'shift-enter', 'cmd-t'
|
||||
* {
|
||||
* help: String // user facing help string
|
||||
* help_index: String // string used internally to order the shortcut on the quickhelp
|
||||
* handler: function(event){return true|false} // function that takes an even as first and only parameter
|
||||
* // and return a boolean indicating whether or not the event should been handled further.
|
||||
* }
|
||||
*```
|
||||
*/
|
||||
KeyboardManager.prototype.get_default_common_shortcuts = function() {
|
||||
var that = this;
|
||||
var shortcuts = {
|
||||
@ -125,19 +150,17 @@ define([
|
||||
handler : function (event) {
|
||||
var index = that.notebook.get_selected_index();
|
||||
var cell = that.notebook.get_cell(index);
|
||||
if (cell && cell.at_top() && index !== 0) {
|
||||
var cm = that.notebook.get_selected_cell().code_mirror;
|
||||
var cur = cm.getCursor()
|
||||
if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
|
||||
event.preventDefault();
|
||||
that.notebook.command_mode();
|
||||
that.notebook.select_prev();
|
||||
that.notebook.edit_mode();
|
||||
var cm = that.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(cm.lastLine(), 0);
|
||||
return false;
|
||||
} else if (cell) {
|
||||
var cm = cell.code_mirror;
|
||||
cm.execCommand('goLineUp');
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'down' : {
|
||||
@ -154,11 +177,8 @@ define([
|
||||
var cm = that.notebook.get_selected_cell().code_mirror;
|
||||
cm.setCursor(0, 0);
|
||||
return false;
|
||||
} else {
|
||||
var cm = cell.code_mirror;
|
||||
cm.execCommand('goLineDown');
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'ctrl-shift--' : {
|
||||
|
@ -19,8 +19,10 @@ require([
|
||||
'notebook/js/keyboardmanager',
|
||||
'notebook/js/config',
|
||||
'notebook/js/kernelselector',
|
||||
// only loaded, not used:
|
||||
'custom/custom',
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/addon/mode/loadmode',
|
||||
// only loaded, not used, please keep sure this is loaded last
|
||||
'custom/custom'
|
||||
], function(
|
||||
IPython,
|
||||
$,
|
||||
@ -38,10 +40,18 @@ require([
|
||||
savewidget,
|
||||
keyboardmanager,
|
||||
config,
|
||||
kernelselector
|
||||
kernelselector,
|
||||
CodeMirror,
|
||||
cm_loadmode,
|
||||
// please keep sure that even if not used, this is loaded last
|
||||
custom
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
window.CodeMirror = CodeMirror;
|
||||
$('#ipython-main-app').addClass('border-box-sizing');
|
||||
$('div#notebook_panel').addClass('border-box-sizing');
|
||||
|
||||
var common_options = {
|
||||
base_url : utils.get_body_data("baseUrl"),
|
||||
ws_url : IPython.utils.get_body_data("wsUrl"),
|
||||
|
@ -10,7 +10,10 @@ define([
|
||||
'notebook/js/mathjaxutils',
|
||||
'notebook/js/celltoolbar',
|
||||
'components/marked/lib/marked',
|
||||
], function(IPython, utils, $, cell, security, mathjaxutils, celltoolbar, marked) {
|
||||
'codemirror/lib/codemirror',
|
||||
'codemirror/mode/gfm/gfm',
|
||||
'notebook/js/codemirror-ipythongfm'
|
||||
], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
|
||||
"use strict";
|
||||
var Cell = cell.Cell;
|
||||
|
||||
|
@ -311,25 +311,17 @@ class="notebook_app"
|
||||
{% block script %}
|
||||
{{super()}}
|
||||
|
||||
<script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
|
||||
require(['codemirror/lib/codemirror',
|
||||
], function(CodeMirror){
|
||||
CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
|
||||
// require([
|
||||
// 'notebook/js/codemirror-ipython',
|
||||
// 'notebook/js/codemirror-ipythongfm'
|
||||
//], function(){})
|
||||
});
|
||||
</script>
|
||||
<script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
|
||||
<script src="{{ static_url("notebook/js/codemirror-ipythongfm.js") }}" charset="utf-8"></script>
|
||||
|
||||
<script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
|
||||
highlight: 'components/highlight.js/build/highlight.pack',
|
||||
moment: "components/moment/moment",
|
||||
codemirror: 'components/codemirror',
|
||||
},
|
||||
shim: {
|
||||
underscore: {
|
||||
|
Loading…
Reference in New Issue
Block a user