mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-14 06:57:01 +00:00
Improve roundtrip of tex2lyx test documents:
- Make test-insets.tex and test-structure.tex compilable - Avoid duplicate definition of \lyxarrow in test-insets.lyx - Prevent subscript package from being ignored in test-insets.lyx - Prevent commands listed with optional arg in syntax.default from being concatenated with the next word if no optional arg is given - Handle spaces and comments inbetween a command an "{}" consistently git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@36943 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
parent
61eeab7405
commit
93232c3ad6
@ -215,20 +215,24 @@ bool Parser::isParagraph()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Parser::skip_spaces(bool skip_comments)
|
bool Parser::skip_spaces(bool skip_comments)
|
||||||
{
|
{
|
||||||
// We just silently return if we have no more tokens.
|
// We just silently return if we have no more tokens.
|
||||||
// skip_spaces() should be callable at any time,
|
// skip_spaces() should be callable at any time,
|
||||||
// the caller must check p::good() anyway.
|
// the caller must check p::good() anyway.
|
||||||
|
bool skipped = false;
|
||||||
while (good()) {
|
while (good()) {
|
||||||
get_token();
|
get_token();
|
||||||
if (isParagraph()) {
|
if (isParagraph()) {
|
||||||
putback();
|
putback();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ( curr_token().cat() == catSpace ||
|
if (curr_token().cat() == catSpace ||
|
||||||
curr_token().cat() == catNewline ||
|
curr_token().cat() == catNewline) {
|
||||||
(curr_token().cat() == catComment && curr_token().cs().empty()))
|
skipped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((curr_token().cat() == catComment && curr_token().cs().empty()))
|
||||||
continue;
|
continue;
|
||||||
if (skip_comments && curr_token().cat() == catComment)
|
if (skip_comments && curr_token().cat() == catComment)
|
||||||
cerr << " Ignoring comment: " << curr_token().asInput();
|
cerr << " Ignoring comment: " << curr_token().asInput();
|
||||||
@ -237,6 +241,7 @@ void Parser::skip_spaces(bool skip_comments)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -325,10 +330,15 @@ string Parser::getFullOpt()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string Parser::getOpt()
|
string Parser::getOpt(bool keepws)
|
||||||
{
|
{
|
||||||
string const res = getArg('[', ']');
|
string const res = getArg('[', ']');
|
||||||
return res.empty() ? string() : '[' + res + ']';
|
if (res.empty()) {
|
||||||
|
if (keepws)
|
||||||
|
unskip_spaces(true);
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
return '[' + res + ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,15 +153,21 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
* \returns getArg('[', ']') including the brackets or the
|
* \returns getArg('[', ']') including the brackets or the
|
||||||
* empty string if there is no such argument.
|
* empty string if there is no such argument.
|
||||||
|
* No whitespace is eaten if \p keepws is true and no optional
|
||||||
|
* argument exists. This is important if an optional argument is
|
||||||
|
* parsed that would go after a command in ERT: In this case the
|
||||||
|
* whitespace is needed to separate the ERT from the subsequent
|
||||||
|
* word. Without it, the ERT and the next word would be concatenated
|
||||||
|
* during .tex export, thus creating an invalid command.
|
||||||
*/
|
*/
|
||||||
std::string getOpt();
|
std::string getOpt(bool keepws = false);
|
||||||
/*!
|
/*!
|
||||||
* \returns getFullArg('[', ']') including the parentheses or the
|
* the same as getOpt but without the brackets
|
||||||
* empty string if there is no such argument.
|
|
||||||
*/
|
*/
|
||||||
std::string getOptContent();
|
std::string getOptContent();
|
||||||
/*!
|
/*!
|
||||||
* the same as getOpt but without the brackets
|
* \returns getFullArg('(', ')') including the parentheses or the
|
||||||
|
* empty string if there is no such argument.
|
||||||
*/
|
*/
|
||||||
std::string getFullParentheseArg();
|
std::string getFullParentheseArg();
|
||||||
/*!
|
/*!
|
||||||
@ -192,7 +198,8 @@ public:
|
|||||||
/// \return whether the current token starts a new paragraph
|
/// \return whether the current token starts a new paragraph
|
||||||
bool isParagraph();
|
bool isParagraph();
|
||||||
/// skips spaces (and comments if \p skip_comments is true)
|
/// skips spaces (and comments if \p skip_comments is true)
|
||||||
void skip_spaces(bool skip_comments = false);
|
/// \return whether whitespace was skipped (not comments)
|
||||||
|
bool skip_spaces(bool skip_comments = false);
|
||||||
/// puts back spaces (and comments if \p skip_comments is true)
|
/// puts back spaces (and comments if \p skip_comments is true)
|
||||||
void unskip_spaces(bool skip_comments = false);
|
void unskip_spaces(bool skip_comments = false);
|
||||||
///
|
///
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
%% LyX trick_preamble_code_into_believing_that_this_was_created_by_lyx created this file. For more info, see http://www.lyx.org/.
|
||||||
|
%% Do not edit unless you really know what you are doing.
|
||||||
\documentclass[a4paper,12pt]{article}
|
\documentclass[a4paper,12pt]{article}
|
||||||
\usepackage[T1]{fontenc}
|
\usepackage[T1]{fontenc}
|
||||||
\usepackage[latin9]{inputenc}
|
\usepackage[latin9]{inputenc}
|
||||||
@ -16,7 +18,7 @@
|
|||||||
\providecommand{\makenomenclature}{\makeglossary}
|
\providecommand{\makenomenclature}{\makeglossary}
|
||||||
\usepackage{varioref}
|
\usepackage{varioref}
|
||||||
\usepackage{prettyref}
|
\usepackage{prettyref}
|
||||||
\usepackage{subscript}
|
\usepackage{makeidx}
|
||||||
|
|
||||||
\usepackage{graphicx}
|
\usepackage{graphicx}
|
||||||
|
|
||||||
@ -27,6 +29,12 @@
|
|||||||
|
|
||||||
\newcommand{\lyxarrow}{\leavevmode\,$\triangleright$\,\allowbreak}
|
\newcommand{\lyxarrow}{\leavevmode\,$\triangleright$\,\allowbreak}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% User specified LaTeX commands.
|
||||||
|
\usepackage{subscript} % user specified as long as tex2lyx
|
||||||
|
% produces a format less than 408
|
||||||
|
|
||||||
|
\def\mycommand{\textquestiondown}
|
||||||
|
|
||||||
\begin{document}
|
\begin{document}
|
||||||
|
|
||||||
\tableofcontents
|
\tableofcontents
|
||||||
@ -88,7 +96,8 @@ Now the natbib things:
|
|||||||
\section{Input files\index{Input files}}
|
\section{Input files\index{Input files}}
|
||||||
|
|
||||||
We can input files too, like this \input{DummyDocument}, or with the include
|
We can input files too, like this \input{DummyDocument}, or with the include
|
||||||
variant \include{DummyDocument}
|
variant \include{DummyDocument} % unfortunately, including the doc twice
|
||||||
|
% generates a multiply defined label
|
||||||
|
|
||||||
If you prefer verbatim input, you can choose
|
If you prefer verbatim input, you can choose
|
||||||
between~\verbatiminput{foo} or~\verbatiminput*{foo}.
|
between~\verbatiminput{foo} or~\verbatiminput*{foo}.
|
||||||
@ -155,6 +164,16 @@ And what about special characters like hyphe\-nation mark,
|
|||||||
ellipsis\ldots, and end-of-sentence\@. LyX also supports a menu
|
ellipsis\ldots, and end-of-sentence\@. LyX also supports a menu
|
||||||
separator\lyxarrow{}and a spif\textcompwordmark{}fy ligature break.
|
separator\lyxarrow{}and a spif\textcompwordmark{}fy ligature break.
|
||||||
|
|
||||||
|
Test for whitespace handling of commands: The following lines should
|
||||||
|
result in identical output:
|
||||||
|
|
||||||
|
builtin \textasciicircum{} unicodesymbols \j{} user \mycommand{} xx\par
|
||||||
|
builtin \textasciicircum {} unicodesymbols \j {} user \mycommand{} xx\par
|
||||||
|
builtin \textasciicircum % with a comment
|
||||||
|
{} unicodesymbols \j % and a second one
|
||||||
|
{} user \mycommand % and another
|
||||||
|
{} xx
|
||||||
|
|
||||||
A sub\textsubscript{sc\emph{ript}} and super\textsuperscript{script
|
A sub\textsubscript{sc\emph{ript}} and super\textsuperscript{script
|
||||||
with $a^2+b^2=c^2$ math}.
|
with $a^2+b^2=c^2$ math}.
|
||||||
|
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
|
%% LyX trick_preamble_code_into_believing_that_this_was_created_by_lyx created this file. For more info, see http://www.lyx.org/.
|
||||||
|
%% Do not edit unless you really know what you are doing.
|
||||||
\documentclass[legalpaper]{article}
|
\documentclass[legalpaper]{article}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands.
|
||||||
|
|
||||||
|
\newcommand{\noun}[1]{\textsc{#1}}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands.
|
||||||
|
|
||||||
|
\newenvironment{lyxlist}[1]
|
||||||
|
{\begin{list}{}
|
||||||
|
{\settowidth{\labelwidth}{#1}
|
||||||
|
\setlength{\leftmargin}{\labelwidth}
|
||||||
|
\addtolength{\leftmargin}{\labelsep}
|
||||||
|
\renewcommand{\makelabel}[1]{##1\hfil}}}
|
||||||
|
{\end{list}}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% User specified LaTeX commands.
|
||||||
|
|
||||||
\newenvironment{foo}{==[}{]==}
|
\newenvironment{foo}{==[}{]==}
|
||||||
|
|
||||||
\begin{document}
|
\begin{document}
|
||||||
|
@ -371,16 +371,17 @@ void end_inset(ostream & os)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void skip_braces(Parser & p)
|
bool skip_braces(Parser & p)
|
||||||
{
|
{
|
||||||
if (p.next_token().cat() != catBegin)
|
if (p.next_token().cat() != catBegin)
|
||||||
return;
|
return false;
|
||||||
p.get_token();
|
p.get_token();
|
||||||
if (p.next_token().cat() == catEnd) {
|
if (p.next_token().cat() == catEnd) {
|
||||||
p.get_token();
|
p.get_token();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
p.putback();
|
p.putback();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -441,6 +442,33 @@ Layout const * findLayout(TextClass const & textclass, string const & name)
|
|||||||
void eat_whitespace(Parser &, ostream &, Context &, bool);
|
void eat_whitespace(Parser &, ostream &, Context &, bool);
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Skips whitespace and braces.
|
||||||
|
* This should be called after a command has been parsed that is not put into
|
||||||
|
* ERT, and where LyX adds "{}" if needed.
|
||||||
|
*/
|
||||||
|
void skip_spaces_braces(Parser & p)
|
||||||
|
{
|
||||||
|
/* The following four examples produce the same typeset output and
|
||||||
|
should be handled by this function:
|
||||||
|
- abc \j{} xyz
|
||||||
|
- abc \j {} xyz
|
||||||
|
- abc \j
|
||||||
|
{} xyz
|
||||||
|
- abc \j %comment
|
||||||
|
{} xyz
|
||||||
|
*/
|
||||||
|
// Unfortunately we need to skip comments, too.
|
||||||
|
// We can't use eat_whitespace since writing them after the {}
|
||||||
|
// results in different output in some cases.
|
||||||
|
bool const skipped_spaces = p.skip_spaces(true);
|
||||||
|
bool const skipped_braces = skip_braces(p);
|
||||||
|
if (skipped_spaces && !skipped_braces)
|
||||||
|
// put back the space (it is better handled by check_space)
|
||||||
|
p.unskip_spaces(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void output_command_layout(ostream & os, Parser & p, bool outer,
|
void output_command_layout(ostream & os, Parser & p, bool outer,
|
||||||
Context & parent_context,
|
Context & parent_context,
|
||||||
Layout const * newlayout)
|
Layout const * newlayout)
|
||||||
@ -560,7 +588,8 @@ void parse_arguments(string const & command,
|
|||||||
ert += '{' + p.verbatim_item() + '}';
|
ert += '{' + p.verbatim_item() + '}';
|
||||||
break;
|
break;
|
||||||
case optional:
|
case optional:
|
||||||
ert += p.getOpt();
|
// true because we must not eat whitespace
|
||||||
|
ert += p.getOpt(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1850,32 +1879,29 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
|
else if (t.cs() == "makeindex" || t.cs() == "maketitle") {
|
||||||
// FIXME: Somehow prevent title layouts if
|
// FIXME: Somehow prevent title layouts if
|
||||||
// "maketitle" was not found
|
// "maketitle" was not found
|
||||||
p.skip_spaces();
|
// swallow this
|
||||||
skip_braces(p); // swallow this
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "tableofcontents") {
|
else if (t.cs() == "tableofcontents") {
|
||||||
p.skip_spaces();
|
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
begin_command_inset(os, "toc", "tableofcontents");
|
begin_command_inset(os, "toc", "tableofcontents");
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p); // swallow this
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "listoffigures") {
|
else if (t.cs() == "listoffigures") {
|
||||||
p.skip_spaces();
|
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
begin_inset(os, "FloatList figure\n");
|
begin_inset(os, "FloatList figure\n");
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p); // swallow this
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "listoftables") {
|
else if (t.cs() == "listoftables") {
|
||||||
p.skip_spaces();
|
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
begin_inset(os, "FloatList table\n");
|
begin_inset(os, "FloatList table\n");
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p); // swallow this
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "listof") {
|
else if (t.cs() == "listof") {
|
||||||
@ -2153,14 +2179,14 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
begin_command_inset(os, "index_print", "printindex");
|
begin_command_inset(os, "index_print", "printindex");
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "printnomenclature") {
|
else if (t.cs() == "printnomenclature") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
begin_command_inset(os, "nomencl_print", "printnomenclature");
|
begin_command_inset(os, "nomencl_print", "printnomenclature");
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "url") {
|
else if (t.cs() == "url") {
|
||||||
@ -2312,37 +2338,37 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
|| t.cs() == "LaTeX") {
|
|| t.cs() == "LaTeX") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << t.cs();
|
os << t.cs();
|
||||||
skip_braces(p); // eat {}
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "LaTeXe") {
|
else if (t.cs() == "LaTeXe") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "LaTeX2e";
|
os << "LaTeX2e";
|
||||||
skip_braces(p); // eat {}
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "ldots") {
|
else if (t.cs() == "ldots") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
skip_braces(p);
|
|
||||||
os << "\\SpecialChar \\ldots{}\n";
|
os << "\\SpecialChar \\ldots{}\n";
|
||||||
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "lyxarrow") {
|
else if (t.cs() == "lyxarrow") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\\SpecialChar \\menuseparator\n";
|
os << "\\SpecialChar \\menuseparator\n";
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "textcompwordmark") {
|
else if (t.cs() == "textcompwordmark") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\\SpecialChar \\textcompwordmark{}\n";
|
os << "\\SpecialChar \\textcompwordmark{}\n";
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (LYX_FORMAT >= 307 && t.cs() == "slash") {
|
else if (LYX_FORMAT >= 307 && t.cs() == "slash") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\\SpecialChar \\slash{}\n";
|
os << "\\SpecialChar \\slash{}\n";
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (LYX_FORMAT >= 307 && t.cs() == "nobreakdash") {
|
else if (LYX_FORMAT >= 307 && t.cs() == "nobreakdash") {
|
||||||
@ -2370,19 +2396,19 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
else if (t.cs() == "textasciitilde") {
|
else if (t.cs() == "textasciitilde") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << '~';
|
os << '~';
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "textasciicircum") {
|
else if (t.cs() == "textasciicircum") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << '^';
|
os << '^';
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "textbackslash") {
|
else if (t.cs() == "textbackslash") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\n\\backslash\n";
|
os << "\n\\backslash\n";
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
|
else if (t.cs() == "_" || t.cs() == "&" || t.cs() == "#"
|
||||||
@ -2461,7 +2487,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
else if (t.cs() == "newline") {
|
else if (t.cs() == "newline") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\n\\" << t.cs() << "\n";
|
os << "\n\\" << t.cs() << "\n";
|
||||||
skip_braces(p); // eat {}
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "input" || t.cs() == "include"
|
else if (t.cs() == "input" || t.cs() == "include"
|
||||||
@ -2577,7 +2603,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
begin_inset(os, "VSpace ");
|
begin_inset(os, "VSpace ");
|
||||||
os << t.cs();
|
os << t.cs();
|
||||||
end_inset(os);
|
end_inset(os);
|
||||||
skip_braces(p);
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (is_known(t.cs(), known_spaces)) {
|
else if (is_known(t.cs(), known_spaces)) {
|
||||||
@ -2604,7 +2630,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
t.cs() == "cleardoublepage") {
|
t.cs() == "cleardoublepage") {
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << "\n\\" << t.cs() << "\n";
|
os << "\n\\" << t.cs() << "\n";
|
||||||
skip_braces(p); // eat {}
|
skip_spaces_braces(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (t.cs() == "newcommand" ||
|
else if (t.cs() == "newcommand" ||
|
||||||
@ -2726,8 +2752,7 @@ void parse_text(Parser & p, ostream & os, unsigned flags, bool outer,
|
|||||||
<< "+" << to_utf8(rem) << endl;
|
<< "+" << to_utf8(rem) << endl;
|
||||||
context.check_layout(os);
|
context.check_layout(os);
|
||||||
os << to_utf8(s);
|
os << to_utf8(s);
|
||||||
p.skip_spaces();
|
skip_spaces_braces(p);
|
||||||
skip_braces(p); // eat {}
|
|
||||||
}
|
}
|
||||||
//cerr << "#: " << t << " mode: " << mode << endl;
|
//cerr << "#: " << t << " mode: " << mode << endl;
|
||||||
// heuristic: read up to next non-nested space
|
// heuristic: read up to next non-nested space
|
||||||
|
Loading…
Reference in New Issue
Block a user