chore: resolve undefined-field
This commit is contained in:
@@ -178,7 +178,7 @@ local function custom(self, path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
|
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
|
||||||
---@param git_status table|nil optional results of git.load_project_status(...)
|
---@param git_status table|nil optional results of git.load_project(...)
|
||||||
---@return table
|
---@return table
|
||||||
--- git_status: reference
|
--- git_status: reference
|
||||||
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
|
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ function Explorer:refresh_parent_nodes_for_path(path)
|
|||||||
local project = git.get_project(toplevel) or {}
|
local project = git.get_project(toplevel) or {}
|
||||||
|
|
||||||
self:reload(node, project)
|
self:reload(node, project)
|
||||||
git.update_parent_statuses(node, project, toplevel)
|
git.update_parent_projects(node, project, toplevel)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.profile_end(profile)
|
log.profile_end(profile)
|
||||||
@@ -331,7 +331,7 @@ end
|
|||||||
---@param node DirectoryNode
|
---@param node DirectoryNode
|
||||||
function Explorer:_load(node)
|
function Explorer:_load(node)
|
||||||
local cwd = node.link_to or node.absolute_path
|
local cwd = node.link_to or node.absolute_path
|
||||||
local git_status = git.load_project_status(cwd)
|
local git_status = git.load_project(cwd)
|
||||||
self:explore(node, git_status, self)
|
self:explore(node, git_status, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ function Explorer:explore(node, status, parent)
|
|||||||
local single_child = node:single_child_directory()
|
local single_child = node:single_child_directory()
|
||||||
if config.renderer.group_empty and not is_root and single_child then
|
if config.renderer.group_empty and not is_root and single_child then
|
||||||
local child_cwd = single_child.link_to or single_child.absolute_path
|
local child_cwd = single_child.link_to or single_child.absolute_path
|
||||||
local child_status = git.load_project_status(child_cwd)
|
local child_status = git.load_project(child_cwd)
|
||||||
node.group_next = single_child
|
node.group_next = single_child
|
||||||
local ns = self:explore(single_child, child_status, parent)
|
local ns = self:explore(single_child, child_status, parent)
|
||||||
node.nodes = ns or {}
|
node.nodes = ns or {}
|
||||||
@@ -463,7 +463,7 @@ function Explorer:reload_explorer()
|
|||||||
end
|
end
|
||||||
event_running = true
|
event_running = true
|
||||||
|
|
||||||
local projects = git.reload()
|
local projects = git.reload_all_projects()
|
||||||
self:refresh_nodes(projects)
|
self:refresh_nodes(projects)
|
||||||
if view.is_visible() then
|
if view.is_visible() then
|
||||||
self.renderer:draw()
|
self.renderer:draw()
|
||||||
@@ -477,7 +477,7 @@ function Explorer:reload_git()
|
|||||||
end
|
end
|
||||||
event_running = true
|
event_running = true
|
||||||
|
|
||||||
local projects = git.reload()
|
local projects = git.reload_all_projects()
|
||||||
git.reload_node_status(self, projects)
|
git.reload_node_status(self, projects)
|
||||||
self.renderer:draw()
|
self.renderer:draw()
|
||||||
event_running = false
|
event_running = false
|
||||||
|
|||||||
@@ -7,20 +7,39 @@ local Watcher = require("nvim-tree.watcher").Watcher
|
|||||||
local Iterator = require("nvim-tree.iterators.node-iterator")
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
local DirectoryNode = require("nvim-tree.node.directory")
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
---@class (exact) GitStatus -- xy short-format statuses
|
---Git xy short-format statuses for a single node
|
||||||
|
---@class (exact) GitStatus
|
||||||
---@field file string?
|
---@field file string?
|
||||||
---@field dir table<"direct" | "indirect", string[]>?
|
---@field dir table<"direct" | "indirect", string[]>?
|
||||||
|
|
||||||
|
-- Git xy short-format status
|
||||||
|
---@alias GitPathXY table<string, string>
|
||||||
|
|
||||||
|
-- Git xy short-format statuses
|
||||||
|
---@alias GitPathXYs table<string, string[]>
|
||||||
|
|
||||||
|
---@alias GitProjectFiles GitPathXY
|
||||||
|
---@alias GitProjectDirs table<"direct" | "indirect", GitPathXYs>
|
||||||
|
|
||||||
|
---Git state for an entire repo
|
||||||
|
---@class (exact) GitProject
|
||||||
|
---@field files GitProjectFiles?
|
||||||
|
---@field dirs GitProjectDirs?
|
||||||
|
---@field watcher Watcher?
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
config = {},
|
config = {},
|
||||||
|
|
||||||
-- all projects keyed by toplevel
|
---all projects keyed by toplevel
|
||||||
|
---@type table<string, GitProject>
|
||||||
_projects_by_toplevel = {},
|
_projects_by_toplevel = {},
|
||||||
|
|
||||||
-- index of paths inside toplevels, false when not inside a project
|
---index of paths inside toplevels, false when not inside a project
|
||||||
|
---@type table<string, string|false>
|
||||||
_toplevels_by_path = {},
|
_toplevels_by_path = {},
|
||||||
|
|
||||||
-- git dirs by toplevel
|
-- git dirs by toplevel
|
||||||
|
---@type table<string, string>
|
||||||
_git_dirs_by_toplevel = {},
|
_git_dirs_by_toplevel = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,33 +55,33 @@ local WATCHED_FILES = {
|
|||||||
|
|
||||||
---@param toplevel string|nil
|
---@param toplevel string|nil
|
||||||
---@param path string|nil
|
---@param path string|nil
|
||||||
---@param project table
|
---@param project GitProject
|
||||||
---@param statuses GitXYByPath?
|
---@param project_files GitProjectFiles?
|
||||||
local function reload_git_statuses(toplevel, path, project, statuses)
|
local function reload_git_project(toplevel, path, project, project_files)
|
||||||
if path then
|
if path then
|
||||||
for p in pairs(project.files) do
|
for p in pairs(project.files) do
|
||||||
if p:find(path, 1, true) == 1 then
|
if p:find(path, 1, true) == 1 then
|
||||||
project.files[p] = nil
|
project.files[p] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
project.files = vim.tbl_deep_extend("force", project.files, statuses)
|
project.files = vim.tbl_deep_extend("force", project.files, project_files)
|
||||||
else
|
else
|
||||||
project.files = statuses
|
project.files = project_files or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
project.dirs = git_utils.file_status_to_dir_status(project.files, toplevel)
|
project.dirs = git_utils.project_files_to_project_dirs(project.files, toplevel)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Is this path in a known ignored directory?
|
--- Is this path in a known ignored directory?
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param project table git status
|
---@param project GitProject
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function path_ignored_in_project(path, project)
|
local function path_ignored_in_project(path, project)
|
||||||
if not path or not project then
|
if not path or not project then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if project and project.files then
|
if project.files then
|
||||||
for file, status in pairs(project.files) do
|
for file, status in pairs(project.files) do
|
||||||
if status == "!!" and vim.startswith(path, file) then
|
if status == "!!" and vim.startswith(path, file) then
|
||||||
return true
|
return true
|
||||||
@@ -72,9 +91,8 @@ local function path_ignored_in_project(path, project)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Reload all projects
|
---@return GitProject[] maybe empty
|
||||||
---@return table projects maybe empty
|
function M.reload_all_projects()
|
||||||
function M.reload()
|
|
||||||
if not M.config.git.enable then
|
if not M.config.git.enable then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
@@ -87,11 +105,12 @@ function M.reload()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Reload one project. Does nothing when no project or path is ignored
|
--- Reload one project. Does nothing when no project or path is ignored
|
||||||
---@param toplevel string|nil
|
---@param toplevel string?
|
||||||
---@param path string|nil optional path to update only
|
---@param path string? optional path to update only
|
||||||
---@param callback function|nil
|
---@param callback function?
|
||||||
function M.reload_project(toplevel, path, callback)
|
function M.reload_project(toplevel, path, callback)
|
||||||
local project = M._projects_by_toplevel[toplevel]
|
local project = M._projects_by_toplevel[toplevel] --[[@as GitProject]]
|
||||||
|
|
||||||
if not toplevel or not project or not M.config.git.enable then
|
if not toplevel or not project or not M.config.git.enable then
|
||||||
if callback then
|
if callback then
|
||||||
callback()
|
callback()
|
||||||
@@ -116,21 +135,21 @@ function M.reload_project(toplevel, path, callback)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if callback then
|
if callback then
|
||||||
---@param statuses GitXYByPath
|
---@param statuses GitPathXY
|
||||||
runner_opts.callback = function(statuses)
|
runner_opts.callback = function(statuses)
|
||||||
reload_git_statuses(toplevel, path, project, statuses)
|
reload_git_project(toplevel, path, project, statuses)
|
||||||
callback()
|
callback()
|
||||||
end
|
end
|
||||||
GitRunner:run(runner_opts)
|
GitRunner:run(runner_opts)
|
||||||
else
|
else
|
||||||
-- TODO #1974 use callback once async/await is available
|
-- TODO #1974 use callback once async/await is available
|
||||||
reload_git_statuses(toplevel, path, project, GitRunner:run(runner_opts))
|
reload_git_project(toplevel, path, project, GitRunner:run(runner_opts))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Retrieve a known project
|
--- Retrieve a known project
|
||||||
---@param toplevel string|nil
|
---@param toplevel string?
|
||||||
---@return table|nil project
|
---@return GitProject? project
|
||||||
function M.get_project(toplevel)
|
function M.get_project(toplevel)
|
||||||
return M._projects_by_toplevel[toplevel]
|
return M._projects_by_toplevel[toplevel]
|
||||||
end
|
end
|
||||||
@@ -151,11 +170,10 @@ function M.get_toplevel(path)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if M._toplevels_by_path[path] then
|
local tl = M._toplevels_by_path[path]
|
||||||
return M._toplevels_by_path[path]
|
if tl then
|
||||||
end
|
return tl
|
||||||
|
elseif tl == false then
|
||||||
if M._toplevels_by_path[path] == false then
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -194,8 +212,15 @@ function M.get_toplevel(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
M._toplevels_by_path[path] = toplevel
|
M._toplevels_by_path[path] = toplevel
|
||||||
|
|
||||||
M._git_dirs_by_toplevel[toplevel] = git_dir
|
M._git_dirs_by_toplevel[toplevel] = git_dir
|
||||||
return M._toplevels_by_path[path]
|
|
||||||
|
toplevel = M._toplevels_by_path[path]
|
||||||
|
if toplevel == false then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return toplevel
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function reload_tree_at(toplevel)
|
local function reload_tree_at(toplevel)
|
||||||
@@ -230,8 +255,8 @@ end
|
|||||||
--- Load the project status for a path. Does nothing when no toplevel for path.
|
--- Load the project status for a path. Does nothing when no toplevel for path.
|
||||||
--- Only fetches project status when unknown, otherwise returns existing.
|
--- Only fetches project status when unknown, otherwise returns existing.
|
||||||
---@param path string absolute
|
---@param path string absolute
|
||||||
---@return table project maybe empty
|
---@return GitProject maybe empty
|
||||||
function M.load_project_status(path)
|
function M.load_project(path)
|
||||||
if not M.config.git.enable then
|
if not M.config.git.enable then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
@@ -242,12 +267,12 @@ function M.load_project_status(path)
|
|||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local status = M._projects_by_toplevel[toplevel]
|
local project = M._projects_by_toplevel[toplevel]
|
||||||
if status then
|
if project then
|
||||||
return status
|
return project
|
||||||
end
|
end
|
||||||
|
|
||||||
local statuses = GitRunner:run({
|
local path_xys = GitRunner:run({
|
||||||
toplevel = toplevel,
|
toplevel = toplevel,
|
||||||
list_untracked = git_utils.should_show_untracked(toplevel),
|
list_untracked = git_utils.should_show_untracked(toplevel),
|
||||||
list_ignored = true,
|
list_ignored = true,
|
||||||
@@ -275,10 +300,10 @@ function M.load_project_status(path)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if statuses then
|
if path_xys then
|
||||||
M._projects_by_toplevel[toplevel] = {
|
M._projects_by_toplevel[toplevel] = {
|
||||||
files = statuses,
|
files = path_xys,
|
||||||
dirs = git_utils.file_status_to_dir_status(statuses, toplevel),
|
dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel),
|
||||||
watcher = watcher,
|
watcher = watcher,
|
||||||
}
|
}
|
||||||
return M._projects_by_toplevel[toplevel]
|
return M._projects_by_toplevel[toplevel]
|
||||||
@@ -289,9 +314,9 @@ function M.load_project_status(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param dir DirectoryNode
|
---@param dir DirectoryNode
|
||||||
---@param project table?
|
---@param project GitProject?
|
||||||
---@param root string?
|
---@param root string?
|
||||||
function M.update_parent_statuses(dir, project, root)
|
function M.update_parent_projects(dir, project, root)
|
||||||
while project and dir do
|
while project and dir do
|
||||||
-- step up to the containing project
|
-- step up to the containing project
|
||||||
if dir.absolute_path == root then
|
if dir.absolute_path == root then
|
||||||
@@ -331,14 +356,14 @@ function M.refresh_dir(dir)
|
|||||||
|
|
||||||
dir.explorer:reload(node, project)
|
dir.explorer:reload(node, project)
|
||||||
|
|
||||||
M.update_parent_statuses(dir, project, toplevel)
|
M.update_parent_projects(dir, project, toplevel)
|
||||||
|
|
||||||
dir.explorer.renderer:draw()
|
dir.explorer.renderer:draw()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param dir DirectoryNode?
|
---@param dir DirectoryNode?
|
||||||
---@param projects table
|
---@param projects GitProject[]
|
||||||
function M.reload_node_status(dir, projects)
|
function M.reload_node_status(dir, projects)
|
||||||
dir = dir and dir:as(DirectoryNode)
|
dir = dir and dir:as(DirectoryNode)
|
||||||
if not dir or #dir.nodes == 0 then
|
if not dir or #dir.nodes == 0 then
|
||||||
|
|||||||
@@ -4,19 +4,17 @@ local notify = require("nvim-tree.notify")
|
|||||||
|
|
||||||
local Class = require("nvim-tree.class")
|
local Class = require("nvim-tree.class")
|
||||||
|
|
||||||
---@alias GitXYByPath table<string, string> -- short-format statuses
|
|
||||||
|
|
||||||
---@class (exact) GitRunnerOpts
|
---@class (exact) GitRunnerOpts
|
||||||
---@field toplevel string absolute path
|
---@field toplevel string absolute path
|
||||||
---@field path string? absolute path
|
---@field path string? absolute path
|
||||||
---@field list_untracked boolean
|
---@field list_untracked boolean
|
||||||
---@field list_ignored boolean
|
---@field list_ignored boolean
|
||||||
---@field timeout integer
|
---@field timeout integer
|
||||||
---@field callback fun(statuses: GitXYByPath)?
|
---@field callback fun(statuses: GitPathXY)?
|
||||||
|
|
||||||
---@class (exact) GitRunner: Class
|
---@class (exact) GitRunner: Class
|
||||||
---@field private opts GitRunnerOpts
|
---@field private opts GitRunnerOpts
|
||||||
---@field private statuses GitXYByPath
|
---@field private statuses GitPathXY
|
||||||
---@field private rc integer? -- -1 indicates timeout
|
---@field private rc integer? -- -1 indicates timeout
|
||||||
local GitRunner = Class:new()
|
local GitRunner = Class:new()
|
||||||
|
|
||||||
@@ -207,7 +205,8 @@ function GitRunner:finalise()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return GitXYByPath? statuses nil if callback present
|
---@private
|
||||||
|
---@return GitPathXY? statuses nil if callback present
|
||||||
function GitRunner:execute()
|
function GitRunner:execute()
|
||||||
local async = self.opts.callback ~= nil
|
local async = self.opts.callback ~= nil
|
||||||
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path)
|
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path)
|
||||||
@@ -240,7 +239,7 @@ end
|
|||||||
|
|
||||||
---Static method to run a git process, which will be killed if it takes more than timeout
|
---Static method to run a git process, which will be killed if it takes more than timeout
|
||||||
---@param opts GitRunnerOpts
|
---@param opts GitRunnerOpts
|
||||||
---@return GitXYByPath? statuses nil if callback present
|
---@return GitPathXY? statuses nil if callback present
|
||||||
function GitRunner:run(opts)
|
function GitRunner:run(opts)
|
||||||
---@type GitRunner
|
---@type GitRunner
|
||||||
local runner = {
|
local runner = {
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ function M.should_show_untracked(cwd)
|
|||||||
return untracked[cwd]
|
return untracked[cwd]
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param t table|nil
|
---@param t table<string|integer, boolean>?
|
||||||
---@param k string
|
---@param k string|integer
|
||||||
---@return table
|
---@return table
|
||||||
local function nil_insert(t, k)
|
local function nil_insert(t, k)
|
||||||
t = t or {}
|
t = t or {}
|
||||||
@@ -91,31 +91,33 @@ local function nil_insert(t, k)
|
|||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param status table
|
---@param project_files GitProjectFiles
|
||||||
---@param cwd string|nil
|
---@param cwd string|nil
|
||||||
---@return table
|
---@return GitProjectDirs
|
||||||
function M.file_status_to_dir_status(status, cwd)
|
function M.project_files_to_project_dirs(project_files, cwd)
|
||||||
local direct = {}
|
---@type GitProjectDirs
|
||||||
for p, s in pairs(status) do
|
local project_dirs = {}
|
||||||
|
|
||||||
|
project_dirs.direct = {}
|
||||||
|
for p, s in pairs(project_files) do
|
||||||
if s ~= "!!" then
|
if s ~= "!!" then
|
||||||
local modified = vim.fn.fnamemodify(p, ":h")
|
local modified = vim.fn.fnamemodify(p, ":h")
|
||||||
direct[modified] = nil_insert(direct[modified], s)
|
project_dirs.direct[modified] = nil_insert(project_dirs.direct[modified], s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local indirect = {}
|
project_dirs.indirect = {}
|
||||||
for dirname, statuses in pairs(direct) do
|
for dirname, statuses in pairs(project_dirs.direct) do
|
||||||
for s, _ in pairs(statuses) do
|
for s, _ in pairs(statuses) do
|
||||||
local modified = dirname
|
local modified = dirname
|
||||||
while modified ~= cwd and modified ~= "/" do
|
while modified ~= cwd and modified ~= "/" do
|
||||||
modified = vim.fn.fnamemodify(modified, ":h")
|
modified = vim.fn.fnamemodify(modified, ":h")
|
||||||
indirect[modified] = nil_insert(indirect[modified], s)
|
project_dirs.indirect[modified] = nil_insert(project_dirs.indirect[modified], s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = { indirect = indirect, direct = direct }
|
for _, d in pairs(project_dirs) do
|
||||||
for _, d in pairs(r) do
|
|
||||||
for dirname, statuses in pairs(d) do
|
for dirname, statuses in pairs(d) do
|
||||||
local new_statuses = {}
|
local new_statuses = {}
|
||||||
for s, _ in pairs(statuses) do
|
for s, _ in pairs(statuses) do
|
||||||
@@ -124,7 +126,8 @@ function M.file_status_to_dir_status(status, cwd)
|
|||||||
d[dirname] = new_statuses
|
d[dirname] = new_statuses
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return r
|
|
||||||
|
return project_dirs
|
||||||
end
|
end
|
||||||
|
|
||||||
---Git file status for an absolute path with optional fallback
|
---Git file status for an absolute path with optional fallback
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ function DirectoryLinkNode:destroy()
|
|||||||
DirectoryNode.destroy(self)
|
DirectoryNode.destroy(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-----Update the directory GitStatus of link target and the file status of the link itself
|
---Update the directory GitStatus of link target and the file status of the link itself
|
||||||
-----@param parent_ignored boolean
|
---@param parent_ignored boolean
|
||||||
-----@param status table|nil
|
---@param status table|nil
|
||||||
function DirectoryLinkNode:update_git_status(parent_ignored, status)
|
function DirectoryLinkNode:update_git_status(parent_ignored, status)
|
||||||
self.git_status = git_utils.git_status_dir(parent_ignored, status, self.link_to, self.absolute_path)
|
self.git_status = git_utils.git_status_dir(parent_ignored, status, self.link_to, self.absolute_path)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ function FileLinkNode:destroy()
|
|||||||
FileNode.destroy(self)
|
FileNode.destroy(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-----Update the GitStatus of the target otherwise the link itself
|
---Update the GitStatus of the target otherwise the link itself
|
||||||
-----@param parent_ignored boolean
|
---@param parent_ignored boolean
|
||||||
-----@param status table|nil
|
---@param status table|nil
|
||||||
function FileLinkNode:update_git_status(parent_ignored, status)
|
function FileLinkNode:update_git_status(parent_ignored, status)
|
||||||
self.git_status = git_utils.git_status_file(parent_ignored, status, self.link_to, self.absolute_path)
|
self.git_status = git_utils.git_status_file(parent_ignored, status, self.link_to, self.absolute_path)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user