Tree Refresh; Draft Note Replies (#289)
* fix: always refresh discussion tree data after choosing a new branch * fix: rebuild discussion tree without collapsing nodes after all edit/delete/create actions * feat: add command to refresh discussion tree * feat: Add support for draft note replies, e.g. replies to existing notes and comments in draft form * fix: allow backticks in comment suggestions This is a #MINOR release
This commit is contained in:
committed by
GitHub
parent
cf6ccddce3
commit
0d0ed1639a
@@ -29,18 +29,37 @@ local M = {
|
||||
linked_bufnr = nil,
|
||||
---@type number
|
||||
unlinked_bufnr = nil,
|
||||
---@type number
|
||||
---@type NuiTree|nil
|
||||
discussion_tree = nil,
|
||||
---@type NuiTree|nil
|
||||
unlinked_discussion_tree = nil,
|
||||
}
|
||||
|
||||
---Re-fetches all discussions and re-renders the relevant view
|
||||
---@param unlinked boolean
|
||||
---@param all boolean|nil
|
||||
M.rebuild_view = function(unlinked, all)
|
||||
M.load_discussions(function()
|
||||
if all then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
M.rebuild_discussion_tree()
|
||||
elseif unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
end)
|
||||
end
|
||||
|
||||
---Makes API call to get the discussion data, stores it in the state, and calls the callback
|
||||
---@param callback function|nil
|
||||
M.load_discussions = function(callback)
|
||||
job.run_job("/mr/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
||||
state.load_new_state("discussion_data", function(data)
|
||||
state.DISCUSSION_DATA.discussions = u.ensure_table(data.discussions)
|
||||
state.DISCUSSION_DATA.unlinked_discussions = u.ensure_table(data.unlinked_discussions)
|
||||
state.DISCUSSION_DATA.emojis = u.ensure_table(data.emojis)
|
||||
if type(callback) == "function" then
|
||||
if callback ~= nil then
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
@@ -50,7 +69,7 @@ end
|
||||
M.initialize_discussions = function()
|
||||
signs.setup_signs()
|
||||
reviewer.set_callback_for_file_changed(function()
|
||||
M.refresh_view()
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
M.modifiable(false)
|
||||
end)
|
||||
reviewer.set_callback_for_reviewer_enter(function()
|
||||
@@ -77,19 +96,8 @@ M.modifiable = function(bool)
|
||||
end
|
||||
end
|
||||
|
||||
---Refresh discussion data, signs, diagnostics, and winbar with new data from API
|
||||
--- and rebuild the entire view
|
||||
M.refresh = function(cb)
|
||||
M.load_discussions(function()
|
||||
M.refresh_view()
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Take existing data and refresh the diagnostics, the winbar, and the signs
|
||||
M.refresh_view = function()
|
||||
M.refresh_diagnostics_and_winbar = function()
|
||||
if state.settings.discussion_signs.enabled then
|
||||
diagnostics.refresh_diagnostics()
|
||||
end
|
||||
@@ -146,7 +154,7 @@ M.toggle = function(callback)
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
M.refresh_view()
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -219,76 +227,48 @@ M.reply = function(tree)
|
||||
u.notify("Gitlab does not support replying to draft notes", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
local reply_popup = Popup(u.create_popup_state("Reply", state.settings.popup.reply))
|
||||
|
||||
local node = tree:get_node()
|
||||
local discussion_node = common.get_root_node(tree, node)
|
||||
local id = tostring(discussion_node.id)
|
||||
reply_popup:mount()
|
||||
state.set_popup_keymaps(
|
||||
reply_popup,
|
||||
M.send_reply(tree, id),
|
||||
miscellaneous.attach_file,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
end
|
||||
|
||||
-- This function will send the reply to the Go API
|
||||
M.send_reply = function(tree, discussion_id)
|
||||
return function(text)
|
||||
local body = { discussion_id = discussion_id, reply = text }
|
||||
|
||||
job.run_job("/mr/reply", "POST", body, function(data)
|
||||
u.notify("Sent reply!", vim.log.levels.INFO)
|
||||
M.add_reply_to_tree(tree, data.note, discussion_id)
|
||||
M.load_discussions()
|
||||
end)
|
||||
if discussion_node == nil then
|
||||
u.notify("Could not get discussion root", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local discussion_id = tostring(discussion_node.id)
|
||||
local comment = require("gitlab.actions.comment")
|
||||
local unlinked = tree.bufnr == M.unlinked_bufnr
|
||||
local layout = comment.create_comment_layout({ ranged = false, discussion_id = discussion_id, unlinked = unlinked })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.delete_comment) will trigger a popup prompting you to delete the current comment
|
||||
M.delete_comment = function(tree)
|
||||
M.delete_comment = function(tree, unlinked)
|
||||
vim.ui.select({ "Confirm", "Cancel" }, {
|
||||
prompt = "Delete comment?",
|
||||
}, function(choice)
|
||||
if choice == "Confirm" then
|
||||
M.send_deletion(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
if note_node == nil or root_node == nil then
|
||||
u.notify("Could not get note or root node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer
|
||||
if M.is_draft_note(tree) then
|
||||
draft_notes.confirm_delete_draft_note(note_node.id, unlinked)
|
||||
else
|
||||
local note_id = note_node.is_root and root_node.root_note_id or note_node.id
|
||||
local comment = require("gitlab.actions.comment")
|
||||
comment.confirm_delete_comment(note_id, root_node.id, unlinked)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- This function will actually send the deletion to Gitlab
|
||||
-- when you make a selection, and re-render the tree
|
||||
M.send_deletion = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
if note_node == nil or root_node == nil then
|
||||
u.notify("Could not get note or root node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer
|
||||
local note_id = note_node.is_root and root_node.root_note_id or note_node.id
|
||||
|
||||
if root_node.is_draft then
|
||||
draft_notes.send_deletion(tree)
|
||||
else
|
||||
local body = { discussion_id = root_node.id, note_id = tonumber(note_id) }
|
||||
job.run_job("/mr/comment", "DELETE", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
if note_node.is_root then
|
||||
-- Replace root node w/ current node's contents...
|
||||
tree:remove_node("-" .. root_node.id)
|
||||
else
|
||||
tree:remove_node("-" .. note_id)
|
||||
end
|
||||
tree:render()
|
||||
M.refresh()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.edit_comment) will open the edit popup for the current comment in the discussion tree
|
||||
M.edit_comment = function(tree, unlinked)
|
||||
local edit_popup = Popup(u.create_popup_state("Edit Comment", state.settings.popup.edit))
|
||||
@@ -316,39 +296,21 @@ M.edit_comment = function(tree, unlinked)
|
||||
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
|
||||
|
||||
-- Draft notes module handles edits for draft notes
|
||||
if root_node.is_draft then
|
||||
state.set_popup_keymaps(edit_popup, draft_notes.send_edits(root_node.id), nil, miscellaneous.editable_popup_opts)
|
||||
else
|
||||
if M.is_draft_note(tree) then
|
||||
state.set_popup_keymaps(
|
||||
edit_popup,
|
||||
M.send_edits(tostring(root_node.id), tonumber(note_node.root_note_id or note_node.id), unlinked),
|
||||
draft_notes.confirm_edit_draft_note(note_node.id, unlinked),
|
||||
nil,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
else
|
||||
local comment = require("gitlab.actions.comment")
|
||||
state.set_popup_keymaps(
|
||||
edit_popup,
|
||||
comment.confirm_edit_comment(tostring(root_node.id), tonumber(note_node.root_note_id or note_node.id), unlinked),
|
||||
nil,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---This function sends the edited comment to the Go server
|
||||
---@param discussion_id string
|
||||
---@param note_id integer
|
||||
---@param unlinked boolean
|
||||
M.send_edits = function(discussion_id, note_id, unlinked)
|
||||
return function(text)
|
||||
local body = {
|
||||
discussion_id = discussion_id,
|
||||
note_id = note_id,
|
||||
comment = text,
|
||||
}
|
||||
job.run_job("/mr/comment", "PATCH", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
if unlinked then
|
||||
M.replace_text(state.DISCUSSION_DATA.unlinked_discussions, discussion_id, note_id, text)
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.replace_text(state.DISCUSSION_DATA.discussions, discussion_id, note_id, text)
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -374,8 +336,61 @@ M.toggle_discussion_resolved = function(tree)
|
||||
|
||||
job.run_job("/mr/discussions/resolve", "PUT", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
M.redraw_resolved_status(tree, note, not note.resolved)
|
||||
M.refresh()
|
||||
local unlinked = tree.bufnr == M.unlinked_bufnr
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to attach to the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.add_emoji_to_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local emojis = require("gitlab.emoji").emoji_list
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local body = { emoji = name, note_id = note_id }
|
||||
job.run_job("/mr/awardable/note/", "POST", body, function()
|
||||
u.notify("Emoji added", vim.log.levels.INFO)
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to remove from the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.delete_emoji_from_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
|
||||
local e = require("gitlab.emoji")
|
||||
|
||||
local emojis = {}
|
||||
local current_emojis = state.DISCUSSION_DATA.emojis[note_id_str]
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if state.USER.id == current_emoji.user.id then
|
||||
table.insert(emojis, e.emoji_map[current_emoji.name])
|
||||
end
|
||||
end
|
||||
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local awardable_id
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if current_emoji.name == name and current_emoji.user.id == state.USER.id then
|
||||
awardable_id = current_emoji.id
|
||||
break
|
||||
end
|
||||
end
|
||||
job.run_job(string.format("/mr/awardable/note/%d/%d", note_id, awardable_id), "DELETE", nil, function()
|
||||
u.notify("Emoji removed", vim.log.levels.INFO)
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -383,31 +398,46 @@ end
|
||||
-- 🌲 Helper Functions
|
||||
--
|
||||
|
||||
---Used to collect all nodes in a tree prior to rebuilding it, so that they
|
||||
---can be re-expanded before render
|
||||
---@param tree any
|
||||
---@return table
|
||||
M.gather_expanded_node_ids = function(tree)
|
||||
-- Gather all nodes for later expansion, after rebuild
|
||||
local ids = {}
|
||||
for id, node in pairs(tree and tree.nodes.by_id or {}) do
|
||||
if node._is_expanded then
|
||||
table.insert(ids, id)
|
||||
end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
---Rebuilds the discussion tree, which contains all comments and draft comments
|
||||
---linked to specific places in the code.
|
||||
M.rebuild_discussion_tree = function()
|
||||
if M.linked_bufnr == nil then
|
||||
return
|
||||
end
|
||||
local expanded_node_ids = M.gather_expanded_node_ids(M.discussion_tree)
|
||||
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
|
||||
|
||||
vim.api.nvim_buf_set_lines(M.linked_bufnr, 0, -1, false, {})
|
||||
local existing_comment_nodes = discussions_tree.add_discussions_to_table(state.DISCUSSION_DATA.discussions, false)
|
||||
local draft_comment_nodes = draft_notes.add_draft_notes_to_table(false)
|
||||
|
||||
-- Combine inline draft notes with regular comments
|
||||
local all_nodes = {}
|
||||
for _, draft_node in ipairs(draft_comment_nodes) do
|
||||
table.insert(all_nodes, draft_node)
|
||||
end
|
||||
for _, node in ipairs(existing_comment_nodes) do
|
||||
table.insert(all_nodes, node)
|
||||
end
|
||||
local all_nodes = u.join(draft_comment_nodes, existing_comment_nodes)
|
||||
|
||||
local discussion_tree = NuiTree({
|
||||
nodes = all_nodes,
|
||||
bufnr = M.linked_bufnr,
|
||||
prepare_node = tree_utils.nui_tree_prepare_node,
|
||||
})
|
||||
-- Re-expand already expanded nodes
|
||||
for _, id in ipairs(expanded_node_ids) do
|
||||
tree_utils.open_node_by_id(discussion_tree, id)
|
||||
end
|
||||
|
||||
discussion_tree:render()
|
||||
M.set_tree_keymaps(discussion_tree, M.linked_bufnr, false)
|
||||
@@ -423,6 +453,7 @@ M.rebuild_unlinked_discussion_tree = function()
|
||||
if M.unlinked_bufnr == nil then
|
||||
return
|
||||
end
|
||||
local expanded_node_ids = M.gather_expanded_node_ids(M.unlinked_discussion_tree)
|
||||
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
|
||||
vim.api.nvim_buf_set_lines(M.unlinked_bufnr, 0, -1, false, {})
|
||||
local existing_note_nodes =
|
||||
@@ -430,20 +461,20 @@ M.rebuild_unlinked_discussion_tree = function()
|
||||
local draft_comment_nodes = draft_notes.add_draft_notes_to_table(true)
|
||||
|
||||
-- Combine draft notes with regular notes
|
||||
local all_nodes = {}
|
||||
for _, draft_node in ipairs(draft_comment_nodes) do
|
||||
table.insert(all_nodes, draft_node)
|
||||
end
|
||||
for _, node in ipairs(existing_note_nodes) do
|
||||
table.insert(all_nodes, node)
|
||||
end
|
||||
local all_nodes = u.join(draft_comment_nodes, existing_note_nodes)
|
||||
|
||||
local unlinked_discussion_tree = NuiTree({
|
||||
nodes = all_nodes,
|
||||
bufnr = M.unlinked_bufnr,
|
||||
prepare_node = tree_utils.nui_tree_prepare_node,
|
||||
})
|
||||
|
||||
-- Re-expand already expanded nodes
|
||||
for _, id in ipairs(expanded_node_ids) do
|
||||
tree_utils.open_node_by_id(unlinked_discussion_tree, id)
|
||||
end
|
||||
unlinked_discussion_tree:render()
|
||||
|
||||
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_bufnr, true)
|
||||
M.unlinked_discussion_tree = unlinked_discussion_tree
|
||||
common.switch_can_edit_bufs(false, M.linked_bufnr, M.unlinked_bufnr)
|
||||
@@ -498,6 +529,7 @@ M.is_current_node_note = function(tree)
|
||||
end
|
||||
|
||||
M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
---Keybindings only relevant for linked (comment) view
|
||||
if not unlinked then
|
||||
vim.keymap.set("n", state.settings.discussion_tree.jump_to_file, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
@@ -506,13 +538,19 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
end, { buffer = bufnr, desc = "Jump to file" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.jump_to_reviewer, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
common.jump_to_reviewer(tree, M.refresh_view)
|
||||
common.jump_to_reviewer(tree, M.refresh_diagnostics_and_winbar)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Jump to reviewer" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_tree_type, function()
|
||||
M.toggle_tree_type()
|
||||
end, { buffer = bufnr, desc = "Toggle tree type between `simple` and `by_file_name`" })
|
||||
end
|
||||
|
||||
vim.keymap.set("n", state.settings.discussion_tree.refresh_data, function()
|
||||
u.notify("Refreshing data...", vim.log.levels.INFO)
|
||||
draft_notes.rebuild_view(unlinked, false)
|
||||
end, { buffer = bufnr, desc = "Refreshes the view with Gitlab's APIs" })
|
||||
|
||||
vim.keymap.set("n", state.settings.discussion_tree.edit_comment, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
M.edit_comment(tree, unlinked)
|
||||
@@ -525,12 +563,11 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
end, { buffer = bufnr, desc = "Publish draft" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.delete_comment, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
M.delete_comment(tree)
|
||||
M.delete_comment(tree, unlinked)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Delete comment" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_draft_mode, function()
|
||||
state.settings.discussion_tree.draft_mode = not state.settings.discussion_tree.draft_mode
|
||||
winbar.update_winbar()
|
||||
M.toggle_draft_mode()
|
||||
end, { buffer = bufnr, desc = "Toggle between draft mode and live mode" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved, function()
|
||||
if M.is_current_node_note(tree) and not M.is_draft_note(tree) then
|
||||
@@ -591,68 +628,6 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
emoji.init_popup(tree, bufnr)
|
||||
end
|
||||
|
||||
---Redraws the header of a node in a tree when it's been toggled to resolved/unresolved
|
||||
---@param tree NuiTree
|
||||
---@param note NuiTree.Node
|
||||
---@param mark_resolved boolean
|
||||
M.redraw_resolved_status = function(tree, note, mark_resolved)
|
||||
local current_text = tree.nodes.by_id["-" .. note.id].text
|
||||
local target = mark_resolved and "resolved" or "unresolved"
|
||||
local current = mark_resolved and "unresolved" or "resolved"
|
||||
|
||||
local function set_property(key, val)
|
||||
tree.nodes.by_id["-" .. note.id][key] = val
|
||||
end
|
||||
|
||||
local has_symbol = function(s)
|
||||
return state.settings.discussion_tree[s] ~= nil and state.settings.discussion_tree[s] ~= ""
|
||||
end
|
||||
|
||||
set_property("resolved", mark_resolved)
|
||||
|
||||
if not has_symbol(current) and not has_symbol(target) then
|
||||
return
|
||||
end
|
||||
|
||||
if not has_symbol(current) and has_symbol(target) then
|
||||
set_property("text", (current_text .. " " .. state.settings.discussion_tree[target]))
|
||||
elseif has_symbol(current) and not has_symbol(target) then
|
||||
set_property("text", u.remove_last_chunk(current_text))
|
||||
else
|
||||
set_property("text", (u.remove_last_chunk(current_text) .. " " .. state.settings.discussion_tree[target]))
|
||||
end
|
||||
|
||||
tree:render()
|
||||
end
|
||||
|
||||
---Replace text in discussion after note update.
|
||||
---@param data Discussion[]|UnlinkedDiscussion[]
|
||||
---@param discussion_id string
|
||||
---@param note_id integer
|
||||
---@param text string
|
||||
M.replace_text = function(data, discussion_id, note_id, text)
|
||||
for i, discussion in ipairs(data) do
|
||||
if discussion.id == discussion_id then
|
||||
for j, note in ipairs(discussion.notes) do
|
||||
if note.id == note_id then
|
||||
data[i].notes[j].body = text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Given some note data, adds it to the tree and re-renders the tree
|
||||
---@param tree any
|
||||
---@param note any
|
||||
---@param discussion_id any
|
||||
M.add_reply_to_tree = function(tree, note, discussion_id)
|
||||
local note_node = tree_utils.build_note(note)
|
||||
note_node:expand()
|
||||
tree:add_node(note_node, discussion_id and ("-" .. discussion_id) or nil)
|
||||
tree:render()
|
||||
end
|
||||
|
||||
---Toggle comments tree type between "simple" and "by_file_name"
|
||||
M.toggle_tree_type = function()
|
||||
if state.settings.discussion_tree.tree_type == "simple" then
|
||||
@@ -663,89 +638,23 @@ M.toggle_tree_type = function()
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
|
||||
---Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
|
||||
M.toggle_draft_mode = function()
|
||||
state.settings.discussion_tree.draft_mode = not state.settings.discussion_tree.draft_mode
|
||||
winbar.update_winbar()
|
||||
end
|
||||
|
||||
---Indicates whether the node under the cursor is a draft note or not
|
||||
---@param tree NuiTree
|
||||
---@return boolean
|
||||
M.is_draft_note = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
if note_node and note_node.is_draft then
|
||||
return true
|
||||
end
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
return root_node ~= nil and root_node.is_draft
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to attach to the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.add_emoji_to_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
local emojis = require("gitlab.emoji").emoji_list
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local body = { emoji = name, note_id = note_id }
|
||||
job.run_job("/mr/awardable/note/", "POST", body, function(data)
|
||||
if state.DISCUSSION_DATA.emojis[note_id_str] == nil then
|
||||
state.DISCUSSION_DATA.emojis[note_id_str] = {}
|
||||
table.insert(state.DISCUSSION_DATA.emojis[note_id_str], data.Emoji)
|
||||
else
|
||||
table.insert(state.DISCUSSION_DATA.emojis[note_id_str], data.Emoji)
|
||||
end
|
||||
if unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
u.notify("Emoji added", vim.log.levels.INFO)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to remove from the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.delete_emoji_from_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
|
||||
local e = require("gitlab.emoji")
|
||||
|
||||
local emojis = {}
|
||||
local current_emojis = state.DISCUSSION_DATA.emojis[note_id_str]
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if state.USER.id == current_emoji.user.id then
|
||||
table.insert(emojis, e.emoji_map[current_emoji.name])
|
||||
end
|
||||
end
|
||||
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local awardable_id
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if current_emoji.name == name and current_emoji.user.id == state.USER.id then
|
||||
awardable_id = current_emoji.id
|
||||
break
|
||||
end
|
||||
end
|
||||
job.run_job(string.format("/mr/awardable/note/%d/%d", note_id, awardable_id), "DELETE", nil, function(_)
|
||||
local keep = {} -- Emojis to keep after deletion in the UI
|
||||
for _, saved in ipairs(state.DISCUSSION_DATA.emojis[note_id_str]) do
|
||||
if saved.name ~= name or saved.user.id ~= state.USER.id then
|
||||
table.insert(keep, saved)
|
||||
end
|
||||
end
|
||||
state.DISCUSSION_DATA.emojis[note_id_str] = keep
|
||||
if unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
e.init_popup(tree, unlinked and M.unlinked_bufnr or M.linked_bufnr)
|
||||
u.notify("Emoji removed", vim.log.levels.INFO)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
-- is not used in the draft notes tree
|
||||
local u = require("gitlab.utils")
|
||||
local common = require("gitlab.actions.common")
|
||||
local List = require("gitlab.utils.list")
|
||||
local state = require("gitlab.state")
|
||||
local NuiTree = require("nui.tree")
|
||||
local NuiLine = require("nui.line")
|
||||
@@ -56,8 +57,20 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
end
|
||||
end
|
||||
|
||||
-- Attaches draft notes that are replies to their parent discussions
|
||||
local draft_replies = List.new(state.DRAFT_NOTES or {})
|
||||
:filter(function(note)
|
||||
return note.discussion_id == discussion.id
|
||||
end)
|
||||
:map(function(note)
|
||||
local result = M.build_note(note)
|
||||
return result
|
||||
end)
|
||||
|
||||
local all_children = u.join(discussion_children, draft_replies)
|
||||
|
||||
-- Creates the first node in the discussion, and attaches children
|
||||
local body = u.spread(root_text_nodes, discussion_children)
|
||||
local body = u.spread(root_text_nodes, all_children)
|
||||
local root_node = NuiTree.Node({
|
||||
range = range,
|
||||
text = root_text,
|
||||
@@ -460,6 +473,16 @@ M.collapse_recursively = function(tree, node, current_root_node, keep_current_op
|
||||
end
|
||||
end
|
||||
|
||||
---Expands a given node in a given tree by it's ID
|
||||
---@param tree NuiTree
|
||||
---@param id string
|
||||
M.open_node_by_id = function(tree, id)
|
||||
local node = tree:get_node(id)
|
||||
if node then
|
||||
node:expand()
|
||||
end
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.toggle_node) expands/collapses the current node and its children
|
||||
M.toggle_node = function(tree)
|
||||
local node = tree:get_node()
|
||||
|
||||
Reference in New Issue
Block a user