forked from GitHub/gf-core
GF cloud: work on syntax editor integration
+ The syntax editor is now accessible from the Simple Translaton Tool. + The minibar now automatically provides access to the syntax editor (provided the necessary JavaScript files and style sheets have been loaded). + Preparations for making the syntax editor accessible from the grammar editor.
This commit is contained in:
@@ -129,7 +129,7 @@ textarea.text_mode {
|
|||||||
width: 99%;
|
width: 99%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#minibar {
|
div#minibar, div#syntax_editor {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #ccc url("../minibar/brushed-metal.png");
|
background: #ccc url("../minibar/brushed-metal.png");
|
||||||
|
|||||||
@@ -370,6 +370,7 @@ function compile_button(g,err_ind) {
|
|||||||
function minibar_button(g,files,err_ind,comp_btn) {
|
function minibar_button(g,files,err_ind,comp_btn) {
|
||||||
var b2;
|
var b2;
|
||||||
var minibar_div=div_id("minibar");
|
var minibar_div=div_id("minibar");
|
||||||
|
var editor_div= div_id("syntax_editor");
|
||||||
|
|
||||||
function page_overlay(inner) {
|
function page_overlay(inner) {
|
||||||
return wrap_class("table","page_overlay",tr(td(inner)))
|
return wrap_class("table","page_overlay",tr(td(inner)))
|
||||||
@@ -530,7 +531,7 @@ function minibar_button(g,files,err_ind,comp_btn) {
|
|||||||
|
|
||||||
function goto_minibar() {
|
function goto_minibar() {
|
||||||
clear(files);
|
clear(files);
|
||||||
files.appendChild(minibar_div);
|
appendChildren(files,[minibar_div,editor_div]);
|
||||||
var online_options={grammars_url: local.get("dir")+"/",
|
var online_options={grammars_url: local.get("dir")+"/",
|
||||||
grammar_list: [g.basename+".pgf"]}
|
grammar_list: [g.basename+".pgf"]}
|
||||||
var pgf_server=pgf_online(online_options)
|
var pgf_server=pgf_online(online_options)
|
||||||
|
|||||||
@@ -33,23 +33,25 @@ This page does not work without JavaScript.
|
|||||||
<hr>
|
<hr>
|
||||||
<div class=modtime><small>
|
<div class=modtime><small>
|
||||||
HTML
|
HTML
|
||||||
<!-- hhmts start -->Last modified: Wed Apr 3 20:30:24 CEST 2013 <!-- hhmts end -->
|
<!-- hhmts start -->Last modified: Fri Apr 12 20:22:01 CEST 2013 <!-- hhmts end -->
|
||||||
</small></div>
|
</small></div>
|
||||||
<a href="about.html">About</a>
|
<a href="about.html">About</a>
|
||||||
<pre id=debug></pre>
|
<pre id=debug></pre>
|
||||||
<script type="text/javascript" src="config.js"></script> <!-- optional -->
|
<script type="text/javascript" src="config.js"></script> <!-- optional -->
|
||||||
<script type="text/javascript" src="../js/support.js"></script>
|
<script type="text/javascript" src="../js/support.js"></script>
|
||||||
|
<script type="text/JavaScript" src="../js/pgf_online.js"></script>
|
||||||
<script type="text/javascript" src="../js/localstorage.js"></script>
|
<script type="text/javascript" src="../js/localstorage.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="localstorage.js"></script>
|
<script type="text/javascript" src="localstorage.js"></script>
|
||||||
<script type="text/javascript" src="gf_abs.js"></script>
|
<script type="text/javascript" src="gf_abs.js"></script>
|
||||||
<script type="text/javascript" src="example_based.js"></script>
|
<script type="text/javascript" src="example_based.js"></script>
|
||||||
<script type="text/javascript" src="sort.js"></script>
|
<script type="text/javascript" src="sort.js"></script>
|
||||||
<script type="text/javascript" src="cloud2.js"></script>
|
<script type="text/javascript" src="cloud2.js"></script>
|
||||||
<script type="text/javascript" src="editor.js"></script>
|
<script type="text/javascript" src="editor.js"></script>
|
||||||
|
|
||||||
<script type="text/JavaScript" src="../minibar/minibar.js"></script>
|
<script type="text/JavaScript" src="../minibar/minibar.js"></script>
|
||||||
<script type="text/JavaScript" src="../minibar/minibar_input.js"></script>
|
<script type="text/JavaScript" src="../minibar/minibar_input.js"></script>
|
||||||
<script type="text/JavaScript" src="../minibar/minibar_translations.js"></script>
|
<script type="text/JavaScript" src="../minibar/minibar_translations.js"></script>
|
||||||
<script type="text/JavaScript" src="../minibar/minibar_support.js"></script>
|
<script type="text/JavaScript" src="../minibar/minibar_support.js"></script>
|
||||||
<script type="text/JavaScript" src="../js/pgf_online.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<body class=minibar>
|
<body class=minibar>
|
||||||
<h2>Minibar online</h2>
|
<h2>Minibar online</h2>
|
||||||
<div id=minibar></div>
|
<div id=minibar></div>
|
||||||
<div id=editor></div>
|
<div id=syntax_editor></div>
|
||||||
|
|
||||||
<noscript>This page doesn't works unless JavaScript is enabled.</noscript>
|
<noscript>This page doesn't works unless JavaScript is enabled.</noscript>
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
& <a href="http://www.grammaticalframework.org:41296/translate/">Translator</a>]
|
& <a href="http://www.grammaticalframework.org:41296/translate/">Translator</a>]
|
||||||
</small>
|
</small>
|
||||||
<small class=modtime>
|
<small class=modtime>
|
||||||
HTML <!-- hhmts start -->Last modified: Thu Apr 4 16:50:57 CEST 2013 <!-- hhmts end -->
|
HTML <!-- hhmts start -->Last modified: Fri Apr 12 20:06:19 CEST 2013 <!-- hhmts end -->
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<div id="debug" class="hidden"></div>
|
<div id="debug" class="hidden"></div>
|
||||||
|
|||||||
@@ -37,8 +37,12 @@ function Minibar(server,opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply supplied options
|
// Apply supplied options
|
||||||
|
this.server=server;
|
||||||
if(opts) for(var o in opts) this.options[o]=opts[o];
|
if(opts) for(var o in opts) this.options[o]=opts[o];
|
||||||
|
|
||||||
|
/* --- Syntax editor integration ---------------------------------------- */
|
||||||
|
if(!this.options.abstract_action) this.integrate_syntax_editor()
|
||||||
|
|
||||||
/* --- Creating the components of the minibar --------------------------- */
|
/* --- Creating the components of the minibar --------------------------- */
|
||||||
this.translations=new Translations(server,this.options)
|
this.translations=new Translations(server,this.options)
|
||||||
this.input=new Input(server,this.translations,this.options)
|
this.input=new Input(server,this.translations,this.options)
|
||||||
@@ -57,24 +61,84 @@ function Minibar(server,opts) {
|
|||||||
menubar.appendChild(button("Help",bind(open_help,this)));
|
menubar.appendChild(button("Help",bind(open_help,this)));
|
||||||
append_extra_buttons(extra,options);
|
append_extra_buttons(extra,options);
|
||||||
}
|
}
|
||||||
this.hide = function() {
|
this.set_hidden = function(b) {
|
||||||
this.minibar.style.display="none";
|
this.hidden=b
|
||||||
}
|
this.minibar.style.display= b ? "none" : ""
|
||||||
this.show = function() {
|
|
||||||
this.minibar.style.display="block";
|
|
||||||
}
|
}
|
||||||
|
this.hide = function() { this.set_hidden(true); }
|
||||||
|
this.show = function() { this.set_hidden(false); }
|
||||||
|
|
||||||
/* --- Minibar client state initialisation ------------------------------ */
|
/* --- Minibar client state initialisation ------------------------------ */
|
||||||
this.grammar=null;
|
this.grammar=null;
|
||||||
|
|
||||||
this.server=server;
|
|
||||||
|
|
||||||
/* --- Main program, this gets things going ----------------------------- */
|
/* --- Main program, this gets things going ----------------------------- */
|
||||||
with(this) {
|
with(this) {
|
||||||
server.get_grammarlists(bind(show_grammarlist,this));
|
server.get_grammarlists(bind(show_grammarlist,this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Minibar.prototype.integrate_syntax_editor=function() {
|
||||||
|
var minibar=this
|
||||||
|
var editor_target="syntax_editor"
|
||||||
|
var e=element(editor_target)
|
||||||
|
if(!e || !window.Editor) return
|
||||||
|
|
||||||
|
e.style.display="none"
|
||||||
|
minibar.options.abstract_action=function(tree) {
|
||||||
|
var editor_options = {
|
||||||
|
target: editor_target,
|
||||||
|
show_startcat_menu: minibar.input.options.startcat_menu,
|
||||||
|
initial: { grammar: minibar.grammar_menu.value, // hmm
|
||||||
|
startcat: minibar.input.startcat_menu.value, // hmm
|
||||||
|
languages: minibar.translations.toLangs, // hmm
|
||||||
|
abstr: tree
|
||||||
|
},
|
||||||
|
lin_action: function(new_input,langFrom) {
|
||||||
|
console.log(editor.menu.ui.grammar_menu.value)
|
||||||
|
var grammar_url=editor.menu.ui.grammar_menu.value // hmm
|
||||||
|
|| minibar.server.current_grammar_url // hmm
|
||||||
|
var startcat=editor.get_startcat()
|
||||||
|
|| minibar.input.startcat_menu.value // hmm
|
||||||
|
var toLangs=gm.languages // hmm
|
||||||
|
minibar.input.set_input_for(grammar_url,
|
||||||
|
{from:langFrom,
|
||||||
|
startcat:startcat,
|
||||||
|
input:gf_lex(new_input)})
|
||||||
|
minibar.translations.set_toLangs_for(grammar_url,toLangs)
|
||||||
|
|
||||||
|
//Better: keep editor around and reactivate it next time:
|
||||||
|
editor.hide()
|
||||||
|
// ...
|
||||||
|
|
||||||
|
//Easier: delete the editor and create a new one next time:
|
||||||
|
clear(editor.container)
|
||||||
|
editor=minibar.editor=null;
|
||||||
|
|
||||||
|
// Even if the grammar is the same as before, this call is
|
||||||
|
// what eventually triggers the new_input to be loaded:
|
||||||
|
minibar.select_grammar(grammar_url)
|
||||||
|
|
||||||
|
// Make the minibar visible again
|
||||||
|
minibar.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
minibar.hide()
|
||||||
|
var gm = new GrammarManager(minibar.server,editor_options);
|
||||||
|
var editor=minibar.editor=new Editor(gm,editor_options)
|
||||||
|
editor.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Minibar.prototype.get_current_input=function(cont) {
|
||||||
|
var t=this
|
||||||
|
if(!t.hidden) cont(gf_unlex(t.input.current.input))
|
||||||
|
else {
|
||||||
|
var tree=t.editor.get_ast()
|
||||||
|
function pick(lins) { cont(lins[0].text) }
|
||||||
|
t.server.linearize({tree:tree,to:t.input.current.from},pick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Minibar.prototype.show_grammarlist=function(dir,grammar_names,dir_count) {
|
Minibar.prototype.show_grammarlist=function(dir,grammar_names,dir_count) {
|
||||||
var t=this;
|
var t=this;
|
||||||
var first_time= !t.grammar_menu
|
var first_time= !t.grammar_menu
|
||||||
|
|||||||
@@ -19,46 +19,6 @@ var minibar_options= {
|
|||||||
try_google: true
|
try_google: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(window.Editor) // Syntax editor loaded?
|
|
||||||
minibar_options.abstract_action=function(tree) {
|
|
||||||
var editor_options = {
|
|
||||||
target: "editor",
|
|
||||||
initial: { grammar: minibar.grammar_menu.value, // hmm
|
|
||||||
startcat: minibar.input.startcat_menu.value, // hmm
|
|
||||||
languages: minibar.translations.toLangs, // hmm
|
|
||||||
abstr: tree
|
|
||||||
},
|
|
||||||
lin_action: function(new_input,langFrom) {
|
|
||||||
var grammar_url=editor.menu.ui.grammar_menu.value // hmm
|
|
||||||
var startcat=editor.menu.ui.startcat_menu.value // hmm
|
|
||||||
var toLangs=gm.languages // hmm
|
|
||||||
minibar.input.set_input_for(grammar_url,
|
|
||||||
{from:langFrom,
|
|
||||||
startcat:startcat,
|
|
||||||
input:gf_lex(new_input)})
|
|
||||||
minibar.translations.set_toLangs_for(grammar_url,toLangs)
|
|
||||||
|
|
||||||
//Easier: delete the editor and create a new one next time:
|
|
||||||
clear(editor.container)
|
|
||||||
editor=null;
|
|
||||||
|
|
||||||
//Better: keep editor around and reactivate it next time:
|
|
||||||
//editor.container.style.display="none"
|
|
||||||
|
|
||||||
// Even if the grammar is the same as before, this call is
|
|
||||||
// what eventually triggers the new_input to be loaded:
|
|
||||||
minibar.select_grammar(grammar_url)
|
|
||||||
|
|
||||||
// Make the minibar visible again
|
|
||||||
minibar.minibar.style.display=""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
minibar.minibar.style.display="none" // Hide the minibar
|
|
||||||
var gm = new GrammarManager(server,editor_options);
|
|
||||||
var editor=new Editor(gm,editor_options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(/^\?\/tmp\//.test(location.search)) {
|
if(/^\?\/tmp\//.test(location.search)) {
|
||||||
var args=decodeURIComponent(location.search.substr(1)).split(" ")
|
var args=decodeURIComponent(location.search.substr(1)).split(" ")
|
||||||
if(args[0]) online_options.grammars_url=args[0];
|
if(args[0]) online_options.grammars_url=args[0];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
body {
|
body.syntax-editor {
|
||||||
background: #ccc url("../minibar/brushed-metal.png");
|
background: #ccc url("../minibar/brushed-metal.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ body {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#debug
|
div#debug
|
||||||
{
|
{
|
||||||
font: 10px monospace;
|
font: 10px monospace;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
|||||||
@@ -35,11 +35,18 @@ The tool supports two machine translation services:
|
|||||||
translation from English to a few other languages.
|
translation from English to a few other languages.
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>If an unsatisfactory automatic translation is
|
<p>
|
||||||
|
If an unsatisfactory automatic translation is
|
||||||
obtained, the user can click on it and replace it with a manual translation.
|
obtained, the user can click on it and replace it with a manual translation.
|
||||||
If multiple translations are obtained, one of them is shown by default and
|
If multiple translations are obtained, one of them is shown by default and
|
||||||
the other ones are available in a popup menu.
|
the other ones are available in a popup menu.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Source segments can also be edited. If a GF grammar is used for translation,
|
||||||
|
the <a href="../minibar/about.html">Minibar</a> and the
|
||||||
|
<a href="../syntax-editor/about.html">Syntax Editor</a> can be used.
|
||||||
|
A plain text box is also available, regardless of translation method.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The tool handles a set of documents. Documents can be named, saved,
|
The tool handles a set of documents. Documents can be named, saved,
|
||||||
closed and reopened later. Documents can be saved locally or in the cloud.
|
closed and reopened later. Documents can be saved locally or in the cloud.
|
||||||
@@ -56,7 +63,6 @@ closed and reopened later. Documents can be saved locally or in the cloud.
|
|||||||
to be capitalized, e.g. "I am ready." and "Spanish wine is good."
|
to be capitalized, e.g. "I am ready." and "Spanish wine is good."
|
||||||
<li>Document sharing in the cloud.
|
<li>Document sharing in the cloud.
|
||||||
<li>Interface to other translation services.
|
<li>Interface to other translation services.
|
||||||
<li>Guided text entry, using the Minibar or some variant of it.
|
|
||||||
<li>Interface to the grammar editor for grammar extension.
|
<li>Interface to the grammar editor for grammar extension.
|
||||||
<li>More browser compatibility testing (Chrome, Firefox, Safari &
|
<li>More browser compatibility testing (Chrome, Firefox, Safari &
|
||||||
Opera Mobile tested so far).
|
Opera Mobile tested so far).
|
||||||
@@ -67,7 +73,7 @@ closed and reopened later. Documents can be saved locally or in the cloud.
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class=modtime><small>
|
<div class=modtime><small>
|
||||||
<!-- hhmts start -->Last modified: Fri Apr 5 15:24:59 CEST 2013 <!-- hhmts end -->
|
<!-- hhmts start -->Last modified: Fri Apr 12 19:39:40 CEST 2013 <!-- hhmts end -->
|
||||||
</small></div>
|
</small></div>
|
||||||
<address>
|
<address>
|
||||||
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
|
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="../gfse/editor.css" title="Cloud">
|
<link rel="stylesheet" type="text/css" href="../gfse/editor.css" title="Cloud">
|
||||||
<link rel="stylesheet" type="text/css" href="translator.css" title="Cloud">
|
<link rel="stylesheet" type="text/css" href="translator.css" title="Cloud">
|
||||||
<link rel="stylesheet" type="text/css" href="../minibar/minibar.css">
|
<link rel="stylesheet" type="text/css" href="../minibar/minibar.css">
|
||||||
|
<link rel=stylesheet type="text/css" href="../syntax-editor/editor.css">
|
||||||
<meta name = "viewport" content = "width = device-width">
|
<meta name = "viewport" content = "width = device-width">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
</head>
|
</head>
|
||||||
@@ -77,10 +78,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class=modtime><small>HMTL
|
<div class=modtime><small>HMTL
|
||||||
<!-- hhmts start -->Last modified: Fri Apr 12 14:18:13 CEST 2013 <!-- hhmts end -->
|
<!-- hhmts start -->Last modified: Fri Apr 12 17:03:43 CEST 2013 <!-- hhmts end -->
|
||||||
</small></div>
|
</small></div>
|
||||||
<a href="about.html">About</a>
|
<a href="about.html">About</a>
|
||||||
|
|
||||||
|
<script type="text/JavaScript" src="../js/grammar_manager.js"></script>
|
||||||
<script type="text/javascript" src="../js/support.js"></script>
|
<script type="text/javascript" src="../js/support.js"></script>
|
||||||
<script type="text/javascript" src="../js/pgf_online.js"></script>
|
<script type="text/javascript" src="../js/pgf_online.js"></script>
|
||||||
<script type="text/javascript" src="../js/gfrobust.js"></script>
|
<script type="text/javascript" src="../js/gfrobust.js"></script>
|
||||||
@@ -92,6 +94,9 @@
|
|||||||
<script type="text/javascript" src="../minibar/minibar_input.js"></script>
|
<script type="text/javascript" src="../minibar/minibar_input.js"></script>
|
||||||
<script type="text/javascript" src="../minibar/minibar_translations.js"></script>
|
<script type="text/javascript" src="../minibar/minibar_translations.js"></script>
|
||||||
<script type="text/javascript" src="../minibar/minibar_support.js"></script>
|
<script type="text/javascript" src="../minibar/minibar_support.js"></script>
|
||||||
|
<script type="text/javascript" src="../syntax-editor/ast.js"></script>
|
||||||
|
<script type="text/javascript" src="../syntax-editor/editor_menu.js"></script>
|
||||||
|
<script type="text/javascript" src="../syntax-editor/editor.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="translator.js"></script>
|
<script type="text/javascript" src="translator.js"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ div.overlay > div {
|
|||||||
/*border-radius: 5px;*/
|
/*border-radius: 5px;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
div#minibar {
|
div#minibar, div#syntax_editor {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #ccc url("../minibar/brushed-metal.png");
|
background: #ccc url("../minibar/brushed-metal.png");
|
||||||
|
|||||||
@@ -305,11 +305,7 @@ function uses_gf(doc,segment) {
|
|||||||
var m= segment.options.method || doc.options.method
|
var m= segment.options.method || doc.options.method
|
||||||
var d=segment.use_default
|
var d=segment.use_default
|
||||||
if(d || d==null) m=doc.options.method
|
if(d || d==null) m=doc.options.method
|
||||||
switch(m) {
|
return /\.pgf$/.test(m) ? m : null
|
||||||
case "Manual": return null
|
|
||||||
case "Apertium": return null
|
|
||||||
default: return m
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Translator.prototype.add_apertium=function() {
|
Translator.prototype.add_apertium=function() {
|
||||||
@@ -769,7 +765,8 @@ Translator.prototype.edit_source=function(source,i) {
|
|||||||
var pgf_server=t.servers[grammarname]
|
var pgf_server=t.servers[grammarname]
|
||||||
function cont2(source) {
|
function cont2(source) {
|
||||||
function ok() {
|
function ok() {
|
||||||
unlextext(gf_unlex(minibar.input.current.input),change)
|
function cont(input) { unlextext(input,change) }
|
||||||
|
minibar.get_current_input(cont)
|
||||||
t.hide_filebox()
|
t.hide_filebox()
|
||||||
}
|
}
|
||||||
function cancel() {
|
function cancel() {
|
||||||
@@ -789,9 +786,11 @@ Translator.prototype.edit_source=function(source,i) {
|
|||||||
initial:{from:gfrom,
|
initial:{from:gfrom,
|
||||||
startcat:grammar_info.startcat,
|
startcat:grammar_info.startcat,
|
||||||
input:source.split(" ")},
|
input:source.split(" ")},
|
||||||
initial_toLangs: [gto]
|
initial_toLangs: [gfrom,gto]
|
||||||
}
|
}
|
||||||
replaceChildren(t.filebox,empty_id("div","minibar"))
|
clear(t.filebox)
|
||||||
|
appendChildren(t.filebox,[empty_id("div","minibar"),
|
||||||
|
empty_id("div","syntax_editor")])
|
||||||
var minibar=new Minibar(pgf_server,minibar_options)
|
var minibar=new Minibar(pgf_server,minibar_options)
|
||||||
appendChildren(t.filebox,[button("OK",ok),
|
appendChildren(t.filebox,[button("OK",ok),
|
||||||
button("Cancel",cancel)])
|
button("Cancel",cancel)])
|
||||||
|
|||||||
Reference in New Issue
Block a user