mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-06-02 15:46:39 +08:00
Update release
This commit is contained in:
+26
-17
@@ -18396,12 +18396,7 @@ GuiTextBoxRegexColorizer
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
regex::RegexProc proc;
|
||||
proc.colorizeProc = &GuiTextBoxRegexColorizer::ColorizerProc;
|
||||
proc.argument = colorizerArgument;
|
||||
lexer = new regex::RegexLexer(tokenRegexes, proc);
|
||||
}
|
||||
lexer = new regex::RegexLexer(tokenRegexes);
|
||||
colors.Resize(1 + tokenRegexes.Count() + extraTokenColors.Count());
|
||||
colors[0] = defaultColor;
|
||||
for (vint i = 0; i < tokenColors.Count(); i++)
|
||||
@@ -18412,7 +18407,12 @@ GuiTextBoxRegexColorizer
|
||||
{
|
||||
colors[i + 1 + tokenColors.Count()] = extraTokenColors[i];
|
||||
}
|
||||
colorizer = new regex::RegexLexerColorizer(lexer->Colorize());
|
||||
{
|
||||
regex::RegexProc proc;
|
||||
proc.colorizeProc = &GuiTextBoxRegexColorizer::ColorizerProc;
|
||||
proc.argument = colorizerArgument;
|
||||
colorizer = new regex::RegexLexerColorizer(lexer->Colorize(proc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20357,16 +20357,17 @@ RepeatingParsingExecutor
|
||||
}
|
||||
md.hasContextColor=tokenContextColorAtts.Keys().Contains(tokenIndex);
|
||||
md.hasAutoComplete=tokenAutoCompleteAtts.Keys().Contains(tokenIndex);
|
||||
if((md.isCandidate=tokenCandidateAtts.Keys().Contains(tokenIndex)))
|
||||
if ((md.isCandidate = tokenCandidateAtts.Keys().Contains(tokenIndex)))
|
||||
{
|
||||
const ParsingTable::TokenInfo& tokenInfo=table->GetTokenInfo(md.tableTokenIndex);
|
||||
if(IsRegexEscapedLiteralString(tokenInfo.regex))
|
||||
const ParsingTable::TokenInfo& tokenInfo = table->GetTokenInfo(md.tableTokenIndex);
|
||||
auto regex = wtou32(tokenInfo.regex);
|
||||
if (IsRegexEscapedLiteralString(regex))
|
||||
{
|
||||
md.unescapedRegexText=UnescapeTextForRegex(tokenInfo.regex);
|
||||
md.unescapedRegexText = u32tow(UnescapeTextForRegex(regex));
|
||||
}
|
||||
else
|
||||
{
|
||||
md.isCandidate=false;
|
||||
md.isCandidate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23592,9 +23593,17 @@ GuiToolstripCommand::ShortcutBuilder Parser
|
||||
typedef GuiToolstripCommand::ShortcutBuilder ShortcutBuilder;
|
||||
public:
|
||||
Regex regexShortcut;
|
||||
const vint _ctrl;
|
||||
const vint _shift;
|
||||
const vint _alt;
|
||||
const vint _key;
|
||||
|
||||
GuiToolstripCommandShortcutParser()
|
||||
:regexShortcut(L"((<ctrl>Ctrl)/+|(<shift>Shift)/+|(<alt>Alt)/+)*(<key>/.+)")
|
||||
: regexShortcut(L"((<ctrl>Ctrl)/+|(<shift>Shift)/+|(<alt>Alt)/+)*(<key>/.+)")
|
||||
, _ctrl(regexShortcut.CaptureNames().IndexOf(L"ctrl"))
|
||||
, _shift(regexShortcut.CaptureNames().IndexOf(L"shift"))
|
||||
, _alt(regexShortcut.CaptureNames().IndexOf(L"alt"))
|
||||
, _key(regexShortcut.CaptureNames().IndexOf(L"key"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -23609,11 +23618,11 @@ GuiToolstripCommand::ShortcutBuilder Parser
|
||||
|
||||
Ptr<ShortcutBuilder> builder = new ShortcutBuilder;
|
||||
builder->text = text;
|
||||
builder->ctrl = match->Groups().Contains(L"ctrl");
|
||||
builder->shift = match->Groups().Contains(L"shift");
|
||||
builder->alt = match->Groups().Contains(L"alt");
|
||||
builder->ctrl = match->Groups().Contains(_ctrl);
|
||||
builder->shift = match->Groups().Contains(_shift);
|
||||
builder->alt = match->Groups().Contains(_alt);
|
||||
|
||||
WString name = match->Groups()[L"key"][0].Value();
|
||||
WString name = match->Groups()[_key][0].Value();
|
||||
builder->key = GetCurrentController()->InputService()->GetKey(name);
|
||||
|
||||
return builder->key == VKEY::KEY_UNKNOWN ? nullptr : builder;
|
||||
|
||||
@@ -1686,9 +1686,17 @@ GuiInstanceContext::ElementName Parser
|
||||
typedef GuiInstanceContext::ElementName ElementName;
|
||||
public:
|
||||
Regex regexElementName;
|
||||
const vint _namespaceName;
|
||||
const vint _category;
|
||||
const vint _name;
|
||||
const vint _binding;
|
||||
|
||||
GuiInstanceContextElementNameParser()
|
||||
:regexElementName(L"((<namespaceName>[a-zA-Z_]/w*):)?((<category>[a-zA-Z_]/w*).)?(<name>[a-zA-Z_]/w*)(-(<binding>[a-zA-Z_]/w*))?")
|
||||
: regexElementName(L"((<namespaceName>[a-zA-Z_]/w*):)?((<category>[a-zA-Z_]/w*).)?(<name>[a-zA-Z_]/w*)(-(<binding>[a-zA-Z_]/w*))?")
|
||||
, _namespaceName(regexElementName.CaptureNames().IndexOf(L"namespaceName"))
|
||||
, _category(regexElementName.CaptureNames().IndexOf(L"category"))
|
||||
, _name(regexElementName.CaptureNames().IndexOf(L"name"))
|
||||
, _binding(regexElementName.CaptureNames().IndexOf(L"binding"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1702,21 +1710,21 @@ GuiInstanceContext::ElementName Parser
|
||||
}
|
||||
|
||||
Ptr<ElementName> elementName = new ElementName;
|
||||
if (match->Groups().Keys().Contains(L"namespaceName"))
|
||||
if (match->Groups().Keys().Contains(_namespaceName))
|
||||
{
|
||||
elementName->namespaceName = match->Groups()[L"namespaceName"][0].Value();
|
||||
elementName->namespaceName = match->Groups()[_namespaceName][0].Value();
|
||||
}
|
||||
if (match->Groups().Keys().Contains(L"category"))
|
||||
if (match->Groups().Keys().Contains(_category))
|
||||
{
|
||||
elementName->category = match->Groups()[L"category"][0].Value();
|
||||
elementName->category = match->Groups()[_category][0].Value();
|
||||
}
|
||||
if (match->Groups().Keys().Contains(L"name"))
|
||||
if (match->Groups().Keys().Contains(_name))
|
||||
{
|
||||
elementName->name = match->Groups()[L"name"][0].Value();
|
||||
elementName->name = match->Groups()[_name][0].Value();
|
||||
}
|
||||
if (match->Groups().Keys().Contains(L"binding"))
|
||||
if (match->Groups().Keys().Contains(_binding))
|
||||
{
|
||||
elementName->binding = match->Groups()[L"binding"][0].Value();
|
||||
elementName->binding = match->Groups()[_binding][0].Value();
|
||||
}
|
||||
return elementName;
|
||||
}
|
||||
|
||||
@@ -424,27 +424,6 @@ ArgumentException
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
ParsingException
|
||||
***********************************************************************/
|
||||
|
||||
ParsingException::ParsingException(const WString& _message, const WString& _expression, vint _position)
|
||||
:Exception(_message)
|
||||
,expression(_expression)
|
||||
,position(_position)
|
||||
{
|
||||
}
|
||||
|
||||
const WString& ParsingException::GetExpression()const
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
vint ParsingException::GetPosition()const
|
||||
{
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
|
||||
+46
-30
@@ -7758,19 +7758,6 @@ namespace vl
|
||||
const WString& GetFunction()const;
|
||||
const WString& GetName()const;
|
||||
};
|
||||
|
||||
class ParsingException : public Exception
|
||||
{
|
||||
protected:
|
||||
vint position;
|
||||
WString expression;
|
||||
|
||||
public:
|
||||
ParsingException(const WString& _message, const WString& _expression, vint _position);
|
||||
|
||||
const WString& GetExpression()const;
|
||||
vint GetPosition()const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -7933,6 +7920,12 @@ UtfConversion<T>
|
||||
Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
***********************************************************************/
|
||||
|
||||
struct UtfCharCluster
|
||||
{
|
||||
vint index;
|
||||
vint size;
|
||||
};
|
||||
|
||||
template<typename T, typename TBase>
|
||||
class UtfFrom32ReaderBase : public Object
|
||||
{
|
||||
@@ -7940,6 +7933,9 @@ Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
vint read = 0;
|
||||
vint available = 0;
|
||||
T buffer[BufferLength];
|
||||
|
||||
UtfCharCluster sourceCluster = { 0,0 };
|
||||
vint readCounter = -1;
|
||||
bool error = false;
|
||||
public:
|
||||
T Read()
|
||||
@@ -7952,17 +7948,33 @@ Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
{
|
||||
available = UtfConversion<T>::From32(c, buffer);
|
||||
if (available == -1) return 0;
|
||||
sourceCluster.index += sourceCluster.size;
|
||||
sourceCluster.size = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
available = -1;
|
||||
readCounter++;
|
||||
sourceCluster.index += sourceCluster.size;
|
||||
sourceCluster.size = 0;
|
||||
return 0;
|
||||
}
|
||||
read = 0;
|
||||
}
|
||||
readCounter++;
|
||||
return buffer[read++];
|
||||
}
|
||||
|
||||
vint ReadingIndex() const
|
||||
{
|
||||
return readCounter;
|
||||
}
|
||||
|
||||
UtfCharCluster SourceCluster() const
|
||||
{
|
||||
return sourceCluster;
|
||||
}
|
||||
|
||||
bool HasIllegalChar() const
|
||||
{
|
||||
return error;
|
||||
@@ -7975,6 +7987,9 @@ Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
static const vint BufferLength = UtfConversion<T>::BufferLength;
|
||||
vint available = 0;
|
||||
T buffer[BufferLength];
|
||||
|
||||
UtfCharCluster sourceCluster = { 0,0 };
|
||||
vint readCounter = -1;
|
||||
bool error = false;
|
||||
public:
|
||||
char32_t Read()
|
||||
@@ -7992,6 +8007,9 @@ Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
if (available == 0)
|
||||
{
|
||||
available = -1;
|
||||
readCounter++;
|
||||
sourceCluster.index += sourceCluster.size;
|
||||
sourceCluster.size = 0;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
@@ -8010,9 +8028,22 @@ Utfto32ReaderBase<T> and UtfFrom32ReaerBase<T>
|
||||
{
|
||||
buffer[i] = buffer[i + result];
|
||||
}
|
||||
readCounter++;
|
||||
sourceCluster.index += sourceCluster.size;
|
||||
sourceCluster.size = result;
|
||||
return dest;
|
||||
}
|
||||
|
||||
vint ReadingIndex() const
|
||||
{
|
||||
return readCounter;
|
||||
}
|
||||
|
||||
UtfCharCluster SourceCluster() const
|
||||
{
|
||||
return sourceCluster;
|
||||
}
|
||||
|
||||
bool HasIllegalChar() const
|
||||
{
|
||||
return error;
|
||||
@@ -8042,16 +8073,6 @@ UtfStringTo32Reader<T> and UtfStringFrom32Reader<T>
|
||||
, consuming(_starting)
|
||||
{
|
||||
}
|
||||
|
||||
const T* Starting() const
|
||||
{
|
||||
return starting;
|
||||
}
|
||||
|
||||
const T* Current() const
|
||||
{
|
||||
return consuming;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -8096,14 +8117,9 @@ UtfStringTo32Reader<T> and UtfStringFrom32Reader<T>
|
||||
{
|
||||
}
|
||||
|
||||
const TFrom* Starting() const
|
||||
UtfCharCluster SourceCluster() const
|
||||
{
|
||||
return internalReader.Starting();
|
||||
}
|
||||
|
||||
const TFrom* Current() const
|
||||
{
|
||||
return internalReader.Current();
|
||||
return internalReader.SourceCluster();
|
||||
}
|
||||
|
||||
bool HasIllegalChar() const
|
||||
|
||||
+15
-15
@@ -1753,9 +1753,9 @@ PrepareSymbols
|
||||
manager->AddTokenDefinition(token->name, token->regex);
|
||||
try
|
||||
{
|
||||
regex_internal::ParseRegexExpression(token->regex);
|
||||
regex_internal::ParseRegexExpression(wtou32(token->regex));
|
||||
}
|
||||
catch(const ParsingException& ex)
|
||||
catch(const regex_internal::RegexException& ex)
|
||||
{
|
||||
errors.Add(new ParsingError(token.Obj(), L"Wrong token definition for \""+token->name+L"\": "+ex.Message()));
|
||||
}
|
||||
@@ -1866,14 +1866,14 @@ ValidateRuleStructure
|
||||
|
||||
void Visit(ParsingDefinitionTextGrammar* node)override
|
||||
{
|
||||
WString regex=regex_internal::EscapeTextForRegex(node->text);
|
||||
for(vint i=0;i<manager->GetGlobal()->GetSubSymbolCount();i++)
|
||||
auto regex = regex_internal::EscapeTextForRegex(wtou32(node->text));
|
||||
for (vint i = 0; i < manager->GetGlobal()->GetSubSymbolCount(); i++)
|
||||
{
|
||||
ParsingSymbol* symbol=manager->GetGlobal()->GetSubSymbol(i);
|
||||
if(symbol->GetType()==ParsingSymbol::TokenDef)
|
||||
ParsingSymbol* symbol = manager->GetGlobal()->GetSubSymbol(i);
|
||||
if (symbol->GetType() == ParsingSymbol::TokenDef)
|
||||
{
|
||||
WString normalizedRegex=regex_internal::NormalizeEscapedTextForRegex(symbol->GetDescriptorString());
|
||||
if(normalizedRegex==regex)
|
||||
auto normalizedRegex = regex_internal::NormalizeEscapedTextForRegex(wtou32(symbol->GetDescriptorString()));
|
||||
if (normalizedRegex == regex)
|
||||
{
|
||||
manager->CacheSetSymbol(node, symbol);
|
||||
manager->CacheSetType(node, manager->GetTokenType());
|
||||
@@ -1881,7 +1881,7 @@ ValidateRuleStructure
|
||||
}
|
||||
}
|
||||
}
|
||||
errors.Add(new ParsingError(node, L"Cannot find a token whose definition is exactly \""+regex+L"\"."));
|
||||
errors.Add(new ParsingError(node, L"Cannot find a token whose definition is exactly \"" + u32tow(regex) + L"\"."));
|
||||
}
|
||||
|
||||
void Visit(ParsingDefinitionSequenceGrammar* node)override
|
||||
@@ -6361,16 +6361,16 @@ Logger (Automaton)
|
||||
|
||||
void LogTransitionSymbol(ParsingSymbol* symbol, stream::TextWriter& writer)
|
||||
{
|
||||
if(symbol->GetType()==ParsingSymbol::TokenDef)
|
||||
if (symbol->GetType() == ParsingSymbol::TokenDef)
|
||||
{
|
||||
writer.WriteString(L"[");
|
||||
writer.WriteString(symbol->GetName());
|
||||
|
||||
WString regex=symbol->GetDescriptorString();
|
||||
if(regex_internal::IsRegexEscapedLiteralString(regex))
|
||||
U32String regex = wtou32(symbol->GetDescriptorString());
|
||||
if (regex_internal::IsRegexEscapedLiteralString(regex))
|
||||
{
|
||||
writer.WriteString(L" ");
|
||||
definitions::LogString(regex_internal::UnescapeTextForRegex(regex), writer);
|
||||
definitions::LogString(u32tow(regex_internal::UnescapeTextForRegex(regex)), writer);
|
||||
}
|
||||
writer.WriteString(L"]");
|
||||
}
|
||||
@@ -7090,7 +7090,7 @@ ParsingState
|
||||
,table(_table)
|
||||
,parsingRuleStartState(-1)
|
||||
{
|
||||
CopyFrom(tokens, table->GetLexer().Parse(input, codeIndex));
|
||||
CopyFrom(tokens, table->GetLexer().Parse(input, {}, codeIndex));
|
||||
walker=new ParsingTokenWalker(tokens, table);
|
||||
}
|
||||
|
||||
@@ -8470,7 +8470,7 @@ ParsingTable
|
||||
{
|
||||
discardTokenInfos[i].regexTokenIndex = regexTokenIndex++;
|
||||
}
|
||||
lexer = new RegexLexer(tokens, {});
|
||||
lexer = new RegexLexer(tokens);
|
||||
|
||||
ruleMap.Clear();
|
||||
for (auto [rule, index] : indexed(ruleInfos))
|
||||
|
||||
@@ -4701,9 +4701,17 @@ DateTimeValueSerializer
|
||||
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(DateTimeSerializerStorage)
|
||||
Regex* regexDateTime = nullptr;
|
||||
vint _Y, _M, _D, _h, _m, _s, _ms;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
regexDateTime = new Regex(L"(<Y>/d/d/d/d)-(<M>/d/d)-(<D>/d/d) (<h>/d/d):(<m>/d/d):(<s>/d/d).(<ms>/d/d/d)");
|
||||
_Y = regexDateTime->CaptureNames().IndexOf(L"Y");
|
||||
_M = regexDateTime->CaptureNames().IndexOf(L"M");
|
||||
_D = regexDateTime->CaptureNames().IndexOf(L"D");
|
||||
_h = regexDateTime->CaptureNames().IndexOf(L"H");
|
||||
_m = regexDateTime->CaptureNames().IndexOf(L"M");
|
||||
_s = regexDateTime->CaptureNames().IndexOf(L"S");
|
||||
_ms = regexDateTime->CaptureNames().IndexOf(L"MS");
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
delete regexDateTime;
|
||||
@@ -4737,19 +4745,20 @@ DateTimeValueSerializer
|
||||
|
||||
bool TypedValueSerializerProvider<DateTime>::Deserialize(const WString& input, DateTime& output)
|
||||
{
|
||||
Ptr<RegexMatch> match = GetDateTimeSerializerStorage().regexDateTime->Match(input);
|
||||
auto& dts = GetDateTimeSerializerStorage();
|
||||
Ptr<RegexMatch> match = dts.regexDateTime->Match(input);
|
||||
if (!match) return false;
|
||||
if (!match->Success()) return false;
|
||||
if (match->Result().Start() != 0) return false;
|
||||
if (match->Result().Length() != input.Length()) return false;
|
||||
|
||||
vint year = wtoi(match->Groups()[L"Y"].Get(0).Value());
|
||||
vint month = wtoi(match->Groups()[L"M"].Get(0).Value());
|
||||
vint day = wtoi(match->Groups()[L"D"].Get(0).Value());
|
||||
vint hour = wtoi(match->Groups()[L"h"].Get(0).Value());
|
||||
vint minute = wtoi(match->Groups()[L"m"].Get(0).Value());
|
||||
vint second = wtoi(match->Groups()[L"s"].Get(0).Value());
|
||||
vint milliseconds = wtoi(match->Groups()[L"ms"].Get(0).Value());
|
||||
vint year = wtoi(match->Groups()[dts._Y].Get(0).Value());
|
||||
vint month = wtoi(match->Groups()[dts._M].Get(0).Value());
|
||||
vint day = wtoi(match->Groups()[dts._D].Get(0).Value());
|
||||
vint hour = wtoi(match->Groups()[dts._h].Get(0).Value());
|
||||
vint minute = wtoi(match->Groups()[dts._m].Get(0).Value());
|
||||
vint second = wtoi(match->Groups()[dts._s].Get(0).Value());
|
||||
vint milliseconds = wtoi(match->Groups()[dts._ms].Get(0).Value());
|
||||
|
||||
output = DateTime::FromDateTime(year, month, day, hour, minute, second, milliseconds);
|
||||
return true;
|
||||
|
||||
+2645
-2477
File diff suppressed because it is too large
Load Diff
+652
-270
File diff suppressed because it is too large
Load Diff
@@ -16708,6 +16708,8 @@ WfCppConfig
|
||||
, regexSplitName(L"::")
|
||||
, regexSpecialName(L"/<(<category>/w+)(-(<category>/w+))*/>(<name>/w*)")
|
||||
, regexTemplate(L", /$Arguments|/$Arguments, |/$/l+")
|
||||
, specialName_category(regexSpecialName.CaptureNames().IndexOf(L"category"))
|
||||
, specialName_name(regexSpecialName.CaptureNames().IndexOf(L"name"))
|
||||
, assemblyName(_assemblyName)
|
||||
, assemblyNamespace(_assemblyNamespace)
|
||||
{
|
||||
@@ -16764,7 +16766,7 @@ WfCppConfig
|
||||
if (match)
|
||||
{
|
||||
return specialNameCategory
|
||||
+ From(match->Groups()[L"category"])
|
||||
+ From(match->Groups()[specialName_category])
|
||||
.Select([](const RegexString& rs)
|
||||
{
|
||||
return rs.Value();
|
||||
@@ -16773,7 +16775,7 @@ WfCppConfig
|
||||
{
|
||||
return a + L"_" + b;
|
||||
})
|
||||
+ L"_" + match->Groups()[L"name"][0].Value();
|
||||
+ L"_" + match->Groups()[specialName_name][0].Value();
|
||||
}
|
||||
else if (alwaysUseCategory)
|
||||
{
|
||||
@@ -23567,6 +23569,7 @@ MergeCpp
|
||||
void ProcessCppContent(const WString& code, const TCallback& callback)
|
||||
{
|
||||
Regex regexUserContentBegin(L"/.*?(?/{)?///* USER_CONTENT_BEGIN/((<name>[^)]*?)/) /*//");
|
||||
vint _name = regexUserContentBegin.CaptureNames().IndexOf(L"name");
|
||||
|
||||
vint state = NORMAL;
|
||||
vint counter = 0;
|
||||
@@ -23599,7 +23602,7 @@ MergeCpp
|
||||
case NORMAL:
|
||||
if (auto match = regexUserContentBegin.MatchHead(content))
|
||||
{
|
||||
content = L"USERIMPL(/* " + match->Groups()[L"name"][0].Value() + L" */)";
|
||||
content = L"USERIMPL(/* " + match->Groups()[_name][0].Value() + L" */)";
|
||||
if (match->Captures().Count() > 0)
|
||||
{
|
||||
content += previousContent;
|
||||
|
||||
@@ -4811,6 +4811,8 @@ namespace vl
|
||||
regex::Regex regexSplitName;
|
||||
regex::Regex regexSpecialName;
|
||||
regex::Regex regexTemplate;
|
||||
const vint specialName_category;
|
||||
const vint specialName_name;
|
||||
|
||||
protected:
|
||||
Ptr<ClosureInfo> CollectClosureInfo(Ptr<WfExpression> closure);
|
||||
|
||||
Reference in New Issue
Block a user