from retail
This commit is contained in:
@@ -514,8 +514,20 @@ local exec_env_custom = setmetatable({},
|
||||
return {}
|
||||
elseif overridden[k] then
|
||||
return overridden[k]
|
||||
else
|
||||
elseif _G[k] then
|
||||
return _G[k]
|
||||
elseif k:find(".", 1, true) then
|
||||
local f
|
||||
for i, n in ipairs{strsplit(".", k)} do
|
||||
if i == 1 then
|
||||
f = _G[n]
|
||||
elseif f then
|
||||
f = f[n]
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
end,
|
||||
__newindex = function(table, key, value)
|
||||
|
||||
@@ -70,6 +70,24 @@ function MergeTable(t1, t2)
|
||||
return merged
|
||||
end
|
||||
|
||||
function tCompare(t1, t2)
|
||||
for k, v in pairs(t1) do
|
||||
if type(v) == "table" and type(t2[k]) == "table" then
|
||||
if not tCompare(v, t2[k]) then
|
||||
return false
|
||||
end
|
||||
elseif t2[k] ~= v then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for k in pairs(t2) do
|
||||
if t1[k] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function IsInGroup()
|
||||
return GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0
|
||||
end
|
||||
|
||||
+99
-15
@@ -647,6 +647,68 @@ Private.tinySecondFormat = function(value)
|
||||
end
|
||||
end
|
||||
|
||||
function Private.ExecEnv.ParseStringCheck(input)
|
||||
if not input then return end
|
||||
local matcher = {
|
||||
entries = {},
|
||||
negativeEntries = {},
|
||||
Check = function(self, e)
|
||||
return false
|
||||
end,
|
||||
CheckBoth = function(self, e)
|
||||
return self.entries[e] and not self.negativeEntries[e]
|
||||
end,
|
||||
CheckPositive = function(self, e)
|
||||
return self.entries[e]
|
||||
end,
|
||||
CheckNegative = function(self, e)
|
||||
return not self.negativeEntries[e]
|
||||
end,
|
||||
Add = function(self, e, negate)
|
||||
if negate then
|
||||
self.negativeEntries[e] = true
|
||||
else
|
||||
self.entries[e] = true
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local start = 1
|
||||
local escaped = false
|
||||
local partial = ""
|
||||
local negate = false
|
||||
for i = 1, #input do
|
||||
local c = input:sub(i, i)
|
||||
if escaped then
|
||||
escaped = false
|
||||
elseif c == '\\' then
|
||||
partial = partial .. input:sub(start, i - 1)
|
||||
start = i + 1
|
||||
escaped = true
|
||||
elseif c == "," then
|
||||
matcher:Add(partial .. input:sub(start, i - 1):trim(), negate)
|
||||
start = i + 1
|
||||
partial = ""
|
||||
negate = false
|
||||
elseif c == "-" and partial:trim() == "" and input:sub(start, i - 1):trim() == "" then
|
||||
start = i + 1
|
||||
negate = true
|
||||
end
|
||||
end
|
||||
matcher:Add(partial .. input:sub(start, #input):trim(), negate)
|
||||
|
||||
-- Update check function
|
||||
if next(matcher.entries) and next(matcher.negativeEntries) then
|
||||
matcher.Check = matcher.CheckBoth
|
||||
elseif next(matcher.entries) then
|
||||
matcher.Check = matcher.CheckPositive
|
||||
elseif next(matcher.negativeEntries) then
|
||||
matcher.Check = matcher.CheckNegative
|
||||
end
|
||||
|
||||
return matcher
|
||||
end
|
||||
|
||||
function WeakAuras.ValidateNumericOrPercent(info, val)
|
||||
if val ~= nil and val ~= "" then
|
||||
local percent = string.match(val, "(%d+)%%")
|
||||
@@ -1077,7 +1139,7 @@ Private.load_prototype = {
|
||||
test = "zoneChecker:Check(zone)",
|
||||
events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
|
||||
desc = function()
|
||||
return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), L["Supports multiple entries, separated by commas"])
|
||||
return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."])
|
||||
end,
|
||||
optional = true,
|
||||
},
|
||||
@@ -1090,7 +1152,7 @@ Private.load_prototype = {
|
||||
test = "WeakAuras.CheckNumericIds(%q, zoneId)",
|
||||
events = {"ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
|
||||
desc = function()
|
||||
return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas"])
|
||||
return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."])
|
||||
end,
|
||||
optional = true,
|
||||
},
|
||||
@@ -1104,7 +1166,7 @@ Private.load_prototype = {
|
||||
test = "subzoneChecker:Check(subzone)",
|
||||
events = { "ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA", "VEHICLE_UPDATE", "WA_DELAYED_PLAYER_ENTERING_WORLD" },
|
||||
desc = function()
|
||||
return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetMinimapZoneText(), L["Supports multiple entries, separated by commas"])
|
||||
return ("\n|cffffd200%s|r%s\n\n%s"):format(L["Current Zone\n"], GetMinimapZoneText(), L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."])
|
||||
end,
|
||||
optional = true,
|
||||
},
|
||||
@@ -1608,7 +1670,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.npcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not trigger.use_inverse
|
||||
end,
|
||||
@@ -1977,7 +2039,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.npcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not trigger.use_inverse
|
||||
end,
|
||||
@@ -2332,7 +2394,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.npcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not trigger.use_inverse
|
||||
end,
|
||||
@@ -2504,7 +2566,7 @@ Private.event_prototypes = {
|
||||
conditionType = "string",
|
||||
preamble = "local sourceNameChecker = Private.ExecEnv.ParseStringCheck(%q)",
|
||||
test = "sourceNameChecker:Check(sourceName)",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
},
|
||||
{
|
||||
name = "sourceNpcId",
|
||||
@@ -2523,7 +2585,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.sourceNpcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not (trigger.subeventPrefix == "ENVIRONMENTAL")
|
||||
end,
|
||||
@@ -2641,7 +2703,7 @@ Private.event_prototypes = {
|
||||
conditionType = "string",
|
||||
preamble = "local destNameChecker = Private.ExecEnv.ParseStringCheck(%q)",
|
||||
test = "destNameChecker:Check(destName)",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
|
||||
end,
|
||||
@@ -2663,7 +2725,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.destNpcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not (trigger.subeventPrefix == "SPELL" and trigger.subeventSuffix == "_CAST_START");
|
||||
end,
|
||||
@@ -5098,10 +5160,20 @@ Private.event_prototypes = {
|
||||
{
|
||||
name = "enchant",
|
||||
display = L["Weapon Enchant"],
|
||||
desc = L["Enchant Name"],
|
||||
desc = L["Enchant Name or ID"],
|
||||
type = "string",
|
||||
test = "true"
|
||||
},
|
||||
{
|
||||
name = "enchantID",
|
||||
hidden = true,
|
||||
test = "true",
|
||||
display = L["Enchant ID"],
|
||||
store = true,
|
||||
conditionType = "number",
|
||||
operator_types = "only_equal",
|
||||
noProgressSource = true
|
||||
},
|
||||
{
|
||||
name = "stacks",
|
||||
display = L["Stack Count"],
|
||||
@@ -5145,6 +5217,18 @@ Private.event_prototypes = {
|
||||
test = "true",
|
||||
store = true
|
||||
},
|
||||
{
|
||||
name = "enchanted",
|
||||
display = L["Enchanted"],
|
||||
hidden = true,
|
||||
init = "found == true",
|
||||
test = "true",
|
||||
store = true,
|
||||
conditionType = "bool",
|
||||
conditionTest = function(state, needle)
|
||||
return state and state.show and state.enchanted == (needle == 1)
|
||||
end,
|
||||
},
|
||||
{
|
||||
name = "remaining",
|
||||
display = L["Remaining Time"],
|
||||
@@ -5903,7 +5987,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.name)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"]
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."]
|
||||
},
|
||||
{
|
||||
name = "npcId",
|
||||
@@ -5922,7 +6006,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.npcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"]
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."]
|
||||
},
|
||||
{
|
||||
name = "value",
|
||||
@@ -6218,7 +6302,7 @@ Private.event_prototypes = {
|
||||
return preamble:Check(state.npcId)
|
||||
end,
|
||||
operator_types = "none",
|
||||
desc = L["Supports multiple entries, separated by commas"],
|
||||
desc = L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."],
|
||||
enable = function(trigger)
|
||||
return not trigger.use_inverse
|
||||
end,
|
||||
@@ -7585,7 +7669,7 @@ Private.event_prototypes = {
|
||||
name = "zoneIds",
|
||||
display = L["Player Location ID(s)"],
|
||||
desc = function()
|
||||
return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas"])
|
||||
return ("\n|cffffd200%s|r%s: %d\n\n%s"):format(L["Current Zone\n"], GetRealZoneText(), GetCurrentMapAreaID(), L["Supports multiple entries, separated by commas. Escape ',' with \\. Prefix with '-' for negation."])
|
||||
end,
|
||||
type = "string",
|
||||
multiline = true,
|
||||
|
||||
@@ -493,6 +493,7 @@ local function modify(parent, region, data)
|
||||
region.FrameTick = FrameTickFunctions.timed
|
||||
region.subRegionEvents:AddSubscriber("FrameTick", region, true)
|
||||
function region:Update()
|
||||
region:UpdateProgress()
|
||||
end
|
||||
|
||||
elseif data.animationType == "progress" then
|
||||
|
||||
+5
-1
@@ -876,7 +876,8 @@ Private.faction_group = {
|
||||
|
||||
Private.form_types = {};
|
||||
local function update_forms()
|
||||
wipe(Private.form_types);
|
||||
local oldForms = Private.form_types
|
||||
Private.form_types = {}
|
||||
Private.form_types[0] = "0 - " .. L["Humanoid"]
|
||||
for i = 1, GetNumShapeshiftForms() do
|
||||
local _, name = GetShapeshiftFormInfo(i);
|
||||
@@ -885,6 +886,9 @@ local function update_forms()
|
||||
end
|
||||
end
|
||||
end
|
||||
if Private.OptionsFrame and not tCompare(oldForms, Private.form_types) then
|
||||
Private.OptionsFrame():ReloadOptions()
|
||||
end
|
||||
local form_frame = CreateFrame("Frame");
|
||||
form_frame:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
|
||||
form_frame:RegisterEvent("PLAYER_LOGIN")
|
||||
|
||||
+11
-41
@@ -81,7 +81,7 @@ do
|
||||
local currentErrorHandlerUid
|
||||
local currentErrorHandlerContext
|
||||
local function waErrorHandler(errorMessage)
|
||||
local prefix = ""
|
||||
local juicedMessage = {}
|
||||
local data
|
||||
if currentErrorHandlerId then
|
||||
data = WeakAuras.GetData(currentErrorHandlerId)
|
||||
@@ -91,23 +91,27 @@ do
|
||||
if data then
|
||||
Private.AuraWarnings.UpdateWarning(data.uid, "LuaError", "error",
|
||||
L["This aura has caused a Lua error."] .. "\n" .. L["Install the addons BugSack and BugGrabber for detailed error logs."], true)
|
||||
prefix = L["Lua error in aura '%s': %s"]:format(data.id, currentErrorHandlerContext or L["unknown location"]) .. "\n"
|
||||
table.insert(juicedMessage, L["Lua error in aura '%s': %s"]:format(data.id, currentErrorHandlerContext or L["unknown location"]))
|
||||
else
|
||||
prefix = L["Lua error"] .. "\n"
|
||||
table.insert(juicedMessage, L["Lua error"])
|
||||
end
|
||||
prefix = prefix .. L["WeakAuras Version: %s"]:format(WeakAuras.versionString) .. "\n"
|
||||
table.insert(juicedMessage, L["WeakAuras Version: %s"]:format(WeakAuras.versionString))
|
||||
local version = data and (data.semver or data.version)
|
||||
if version then
|
||||
prefix = prefix .. L["Aura Version: %s"]:format(version) .. "\n"
|
||||
table.insert(juicedMessage, L["Aura Version: %s"]:format(version))
|
||||
end
|
||||
geterrorhandler()(prefix .. errorMessage)
|
||||
table.insert(juicedMessage, L["Stack trace:"])
|
||||
table.insert(juicedMessage, errorMessage)
|
||||
geterrorhandler()(table.concat(juicedMessage, "\n"))
|
||||
end
|
||||
|
||||
function Private.GetErrorHandlerId(id, context)
|
||||
currentErrorHandlerUid = nil
|
||||
currentErrorHandlerId = id
|
||||
currentErrorHandlerContext = context
|
||||
return waErrorHandler
|
||||
end
|
||||
|
||||
function Private.GetErrorHandlerUid(uid, context)
|
||||
currentErrorHandlerUid = uid
|
||||
currentErrorHandlerId = nil
|
||||
@@ -5350,7 +5354,7 @@ local textSymbols = {
|
||||
["{rt13}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcons.blp:0:0:0:0:64:64:0:16:48:64|t",
|
||||
["{rt14}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcons.blp:0:0:0:0:64:64:16:32:48:64|t",
|
||||
["{rt15}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcons.blp:0:0:0:0:64:64:32:48:48:64|t",
|
||||
["{rt16}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcons.blp:0:0:0:0:64:64:48:64:48:64|t"
|
||||
["{rt16}"] = "|TInterface\\TargetingFrame\\UI-RaidTargetingIcons.blp:0:0:0:0:64:64:48:64:48:64|t",
|
||||
}
|
||||
|
||||
function WeakAuras.ReplaceRaidMarkerSymbols(txt)
|
||||
@@ -5549,40 +5553,6 @@ function Private.ExecEnv.ParseZoneCheck(input)
|
||||
return matcher
|
||||
end
|
||||
|
||||
function Private.ExecEnv.ParseStringCheck(input)
|
||||
if not input then return end
|
||||
local matcher = {
|
||||
zones = {},
|
||||
Check = function(self, zone)
|
||||
return self.zones[zone]
|
||||
end,
|
||||
Add = function(self, z)
|
||||
self.zones[z] = true
|
||||
end
|
||||
}
|
||||
|
||||
local start = 1
|
||||
local escaped = false
|
||||
local partial = ""
|
||||
for i = 1, #input do
|
||||
local c = input:sub(i, i)
|
||||
if escaped then
|
||||
escaped = false
|
||||
elseif c == '\\' then
|
||||
partial = partial .. input:sub(start, i - 1)
|
||||
start = i + 1
|
||||
escaped = true
|
||||
elseif c == "," then
|
||||
matcher:Add(partial .. input:sub(start, i - 1):trim())
|
||||
start = i + 1
|
||||
partial = ""
|
||||
end
|
||||
end
|
||||
matcher:Add(partial .. input:sub(start, #input):trim())
|
||||
|
||||
return matcher
|
||||
end
|
||||
|
||||
function WeakAuras.IsAuraLoaded(id)
|
||||
return Private.loaded[id]
|
||||
end
|
||||
|
||||
@@ -798,10 +798,14 @@ local function GetBuffTriggerOptions(data, triggernum)
|
||||
useHostility = {
|
||||
type = "toggle",
|
||||
width = WeakAuras.normalWidth,
|
||||
name = L["Filter by Nameplate Type"],
|
||||
name = L["Filter by Hostility"],
|
||||
order = 69.1,
|
||||
hidden = function() return
|
||||
not (trigger.type == "aura2" and trigger.unit == "nameplate")
|
||||
hidden = function()
|
||||
return not (trigger.type == "aura2"
|
||||
and (trigger.unit == "group"
|
||||
or trigger.unit == "raid"
|
||||
or trigger.unit == "party"
|
||||
or trigger.unit == "nameplate"))
|
||||
end
|
||||
},
|
||||
hostility = {
|
||||
@@ -809,7 +813,14 @@ local function GetBuffTriggerOptions(data, triggernum)
|
||||
width = WeakAuras.normalWidth,
|
||||
name = L["Hostility"],
|
||||
values = OptionsPrivate.Private.hostility_types,
|
||||
hidden = function() return not (trigger.type == "aura2" and trigger.useHostility) end,
|
||||
hidden = function()
|
||||
return not (trigger.type == "aura2"
|
||||
and trigger.useHostility
|
||||
and (trigger.unit == "group"
|
||||
or trigger.unit == "raid"
|
||||
or trigger.unit == "party"
|
||||
or trigger.unit == "nameplate"))
|
||||
end,
|
||||
order = 69.2
|
||||
},
|
||||
hostilitySpace = {
|
||||
@@ -817,7 +828,14 @@ local function GetBuffTriggerOptions(data, triggernum)
|
||||
name = "",
|
||||
order = 69.3,
|
||||
width = WeakAuras.normalWidth,
|
||||
hidden = function() return not (trigger.type == "aura2" and trigger.unit == "nameplate" and not trigger.useHostility) end
|
||||
hidden = function()
|
||||
return not (trigger.type == "aura2"
|
||||
and not trigger.useHostility
|
||||
and (trigger.unit == "group"
|
||||
or trigger.unit == "raid"
|
||||
or trigger.unit == "party"
|
||||
or trigger.unit == "nameplate"))
|
||||
end
|
||||
},
|
||||
|
||||
useNpcId = {
|
||||
|
||||
@@ -92,7 +92,7 @@ function spellCache.GetIcon(name)
|
||||
if (icons) then
|
||||
if (icons.spells) then
|
||||
for spellId, icon in pairs(icons.spells) do
|
||||
if not bestMatch or (type(spellId) == "number" and IsSpellKnown(spellId)) then
|
||||
if not bestMatch or (type(spellId) == "number" and spellId ~= 0 and IsSpellKnown(spellId)) then
|
||||
bestMatch = spellId
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,6 +16,23 @@ local frameChooserBox
|
||||
|
||||
local oldFocus
|
||||
local oldFocusName
|
||||
|
||||
-- if frame doesn't have a name, try to use the key from it's parent
|
||||
local function recurseGetName(frame)
|
||||
local name = frame.GetName and frame:GetName() or nil
|
||||
if name then
|
||||
return name
|
||||
end
|
||||
local parent = frame.GetParent and frame:GetParent()
|
||||
if parent then
|
||||
for key, child in pairs(parent) do
|
||||
if child == frame then
|
||||
return (recurseGetName(parent) or "") .. "." .. key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function OptionsPrivate.StartFrameChooser(data, path)
|
||||
local frame = OptionsPrivate.Private.OptionsFrame();
|
||||
if not(frameChooserFrame) then
|
||||
@@ -46,7 +63,7 @@ function OptionsPrivate.StartFrameChooser(data, path)
|
||||
local focusName;
|
||||
|
||||
if(focus) then
|
||||
focusName = focus:GetName();
|
||||
focusName = recurseGetName(focus)
|
||||
if(focusName == "WorldFrame" or not focusName) then
|
||||
focusName = nil;
|
||||
local focusIsGroup = false;
|
||||
|
||||
@@ -975,6 +975,13 @@ function OptionsPrivate.CreateFrame()
|
||||
end
|
||||
end
|
||||
|
||||
frame.ReloadOptions = function(self)
|
||||
if self.pickedDisplay then
|
||||
self:ClearAndUpdateOptions(self.pickedDisplay, true)
|
||||
self:FillOptions()
|
||||
end
|
||||
end
|
||||
|
||||
frame.ClearAndUpdateOptions = function(self, id, clearChildren)
|
||||
frame:ClearOptions(id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user