From 522eb0a9eefbdb102bcba993676fd6de4379b521 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 31 Jul 2020 12:36:28 +0200 Subject: [PATCH 01/14] rename api, add cc and bcc, handle multiple players rename: src -> from, dst -> to --- api.lua | 64 +++++++++++++++++++++++++++++++++++++--------- api.md | 25 ++++++++++++++++-- gui.lua | 20 ++++++++++----- init.lua | 1 + util/normalize.lua | 26 +++++++++++++++++++ 5 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 util/normalize.lua diff --git a/api.lua b/api.lua index a8d7e30..6af7d36 100644 --- a/api.lua +++ b/api.lua @@ -14,6 +14,7 @@ all 4 parameters (old compat version) see: "Mail format" api.md --]] function mail.send(src, dst, subject, body) + -- figure out format local m if dst == nil and subject == nil and body == nil then -- new format (one object param) @@ -21,36 +22,75 @@ function mail.send(src, dst, subject, body) else -- old format m = {} - m.src = src - m.dst = dst + m.from = src + m.to = dst m.subject = subject m.body = body end + -- log mail send action + if m.cc or m.bcc then + if m.cc then + cc = "CC: " .. m.cc + if m.bcc then + cc = cc .. " - " + end + else + cc = "" + end + if m.bcc then + bcc = "BCC: " .. m.bcc + else + bcc = "" + end + extra = "(" .. cc .. bcc .. ") " + else + extra = "" + end + minetest.log("action", "[mail] '" .. m.from .. "' sends mail to '" .. m.to .. + extra .. "' with subject '" .. m.subject .. "' and body: '" .. m.body .. "'") - minetest.log("action", "[mail] '" .. m.src .. "' sends mail to '" .. m.dst .. - "' with subject '" .. m.subject .. "' and body: '" .. m.body .. "'") - local messages = mail.getMessages(m.dst) + -- normalize to, cc and bcc while compiling a list of all recipients + local recipients = {} + m.to = normalize_players_and_add_recipients(m.to, recipients) + if m.cc then + m.cc = normalize_players_and_add_recipients(m.cc, recipients) + end + if m.bcc then + m.bcc = normalize_players_and_add_recipients(m.bcc, recipients) + end - table.insert(messages, 1, { + -- form the actual mail + msg = { unread = true, - sender = m.src, + from = m.from, + to = m.to, subject = m.subject, body = m.body, time = os.time(), - }) - mail.setMessages(m.dst, messages) + } + if m.cc then + msg.cc = m.cc + end + -- send the mail to all recipients + for _, recipient in pairs(recipients) do + local messages = mail.getMessages(recipient) + table.insert(messages, 1, msg) + mail.setMessages(recipient, messages) + end + + -- notify recipients that happen to be online for _, player in ipairs(minetest.get_connected_players()) do local name = player:get_player_name() - if name == m.dst then + if recipients[string.lower(name)] ~= nil then if m.subject == "" then m.subject = "(No subject)" end if string.len(m.subject) > 30 then m.subject = string.sub(m.subject,1,27) .. "..." end - minetest.chat_send_player(m.dst, - string.format(mail.receive_mail_message, m.src, m.subject)) + minetest.chat_send_player(name, + string.format(mail.receive_mail_message, m.from, m.subject)) end end diff --git a/api.md b/api.md index 5d059c2..984cd28 100644 --- a/api.md +++ b/api.md @@ -2,6 +2,25 @@ # Mail format The mail format in the api hooks +```lua +mail = { + from = "sender name", + to = "players, which, are, addressed", + cc = "carbon copy", + bcc = "players, which, get, a, copy, but, are, not, visible, to, others", + subject = "subject line", + body = "mail body", + -- 8 attachments max + attachments = {"default:stone 99", "default:gold_ingot 99"} +} +``` + +The fields `to`, `cc` and `bcc` can contain a player, multiple player names separated by commas, or be empty. Players in `to` are the recipiants, who are addressed directly. `cc` specifies players that get the mail to get notified, but are not immediate part of the conversation. There is no technical difference between `to` and `cc`, it just implies meaning for the players. Players can see all fields making up the mail except `bcc`, which is the only difference to `cc`. + +Attachments need to be provided for each player getting the mail. Until this is implemented, trying to send a mail to multiple players will fail. + +The `from` and `to` fields were renamed from the previous format: + ```lua mail = { src = "source name", @@ -22,8 +41,10 @@ mail.send("source name", "destination name", "subject line", "mail body") New variant (1.1+) ```lua mail.send({ - src = "source name", - dst = "destination name", + from = "sender name", + to = "destination name", + cc = "carbon copy", + bcc = "blind carbon copy", subject = "subject line", body = "mail body" }) diff --git a/gui.lua b/gui.lua index f608b85..6baf311 100644 --- a/gui.lua +++ b/gui.lua @@ -89,18 +89,20 @@ function mail.show_message(name, msgnumber) button[5,6.7;2,1;delete;Delete] ]] .. theme - local sender = minetest.formspec_escape(message.sender) + local from = minetest.formspec_escape(message.from) local subject = minetest.formspec_escape(message.subject) local body = minetest.formspec_escape(message.body) - formspec = string.format(formspec, sender, subject, body) + formspec = string.format(formspec, from, subject, body) minetest.show_formspec(name,"mail:message",formspec) end -function mail.show_compose(name, defaulttgt, defaultsubj, defaultbody) +function mail.show_compose(name, defaultto, defaultsubj, defaultbody, defaultcc, defaultbcc) local formspec = [[ size[8,7.2] field[0.25,0.5;4,1;to;To:;%s] + field[0.25,0.5;4,1;to;CC:;%s] + field[0.25,0.5;4,1;to;BCC:;%s] field[0.25,1.7;8,1;subject;Subject:;%s] textarea[0.25,2.4;8,5;body;;%s] button[0.5,6.7;3,1;cancel;Cancel] @@ -111,7 +113,9 @@ function mail.show_compose(name, defaulttgt, defaultsubj, defaultbody) formspec = string.format(formspec, minetest.formspec_escape(defaulttgt), minetest.formspec_escape(defaultsubj), - minetest.formspec_escape(defaultbody)) + minetest.formspec_escape(defaultbody), + minetest.formspec_escape(defaultcc), + minetest.formspec_escape(defaultbcc)) minetest.show_formspec(name, "mail:compose", formspec) end @@ -155,7 +159,7 @@ function mail.handle_receivefields(player, formname, fields) elseif fields.reply and messages[selected_message_idxs[name]] then local message = messages[selected_message_idxs[name]] local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, message.sender, "Re: "..message.subject,replyfooter) + mail.show_compose(name, message.from, "Re: "..message.subject,replyfooter) elseif fields.forward and messages[selected_message_idxs[name]] then local message = messages[selected_message_idxs[name]] @@ -215,8 +219,10 @@ function mail.handle_receivefields(player, formname, fields) elseif formname == "mail:compose" then if fields.send then mail.send({ - src = player:get_player_name(), - dst = fields.to, + from = player:get_player_name(), + to = fields.to, + cc = "", + bcc = "" subject = fields.subject, body = fields.body }) diff --git a/init.lua b/init.lua index e35875b..4410e13 100644 --- a/init.lua +++ b/init.lua @@ -26,6 +26,7 @@ mail = { local MP = minetest.get_modpath(minetest.get_current_modname()) +dofile(MP .. "/util/normalize.lua") dofile(MP .. "/chatcommands.lua") dofile(MP .. "/migrate.lua") dofile(MP .. "/attachment.lua") diff --git a/util/normalize.lua b/util/normalize.lua new file mode 100644 index 0000000..13f51b0 --- /dev/null +++ b/util/normalize.lua @@ -0,0 +1,26 @@ +--[[ +return the field normalized (comma separated, single space) +and add individual player names to recipient list +--]] +function normalize_players_and_add_recipients(field, recipients) + local separator = ", " + local pattern = "([^" .. separator .. "]+)" + + -- get individual players + local player_set = {} + local order = {} + field:gsub(pattern, function(c) + if player_set[string.lower(c)] ~= nil then + player_set[string.lower(c)] = c + order[#order+1] = c + + -- also sort into recipients + if recipients[string.lower(c)] ~= nil then + recipients[string.lower(c)] = c + end + end + end) + + -- turn list of players back into normalized string + return table.concat(order, ", ") +end From f5387e27503c370b6bcfdab19ddd8ccf2a5b58b8 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 31 Jul 2020 19:08:31 +0200 Subject: [PATCH 02/14] fix variable to be local --- api.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.lua b/api.lua index 6af7d36..65d466f 100644 --- a/api.lua +++ b/api.lua @@ -62,7 +62,7 @@ function mail.send(src, dst, subject, body) end -- form the actual mail - msg = { + local msg = { unread = true, from = m.from, to = m.to, From 406613ad67924bb0360dd80ec09fc755280d7670 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 31 Jul 2020 23:51:13 +0200 Subject: [PATCH 03/14] WIP: start to add contacts --- gui.lua | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 10 deletions(-) diff --git a/gui.lua b/gui.lua index 6baf311..02810e7 100644 --- a/gui.lua +++ b/gui.lua @@ -7,19 +7,34 @@ else theme = "" end -mail.inbox_formspec = "size[8,9;]" .. theme .. [[ - button_exit[7.5,0;0.5,0.5;quit;X] - button[6,1;2,0.5;new;New Message] - button[6,2;2,0.5;read;Read] - button[6,3;2,0.5;reply;Reply] - button[6,4;2,0.5;forward;Forward] - button[6,5;2,0.5;delete;Delete] - button[6,6;2,0.5;markread;Mark Read] - button[6,7;2,0.5;markunread;Mark Unread] - button[6,8;2,0.5;about;About] +mail.inbox_formspec = "size[8,10;]" .. theme .. [[ + button[6,0.00;2,0.5;new;New] + button[6,0.75;2,0.5;read;Read] + button[6,1.50;2,0.5;reply;Reply] + button[6,2.25;2,0.5;forward;Forward] + button[6,3.00;2,0.5;delete;Delete] + button[6,4.25;2,0.5;markread;Mark Read] + button[6,5.00;2,0.5;markunread;Mark Unread] + button[6,6.25;2,0.5;contacts;Contacts] + button[6,7.00;2,0.5;about;About] + button_exit[6,8.25;2,0.5;quit;Close] tablecolumns[color;text;text] table[0,0;5.75,9;messages;#999,From,Subject]] +mail.contacts_formspec = "size[8,10;]" .. theme .. [[ + button[6,0.00;2,0.5;new;New] + button[6,0.75;2,0.5;edit;Edit] + button[6,1.50;2,0.5;delete;Delete] + button[6,8.25;2,0.5;back;Back] + tablecolumns[color;text;text] + table[0,0;5.75,9;contacts;#999,Name,Note]] + +mail.select_contact_formspec = "size[8,10;]" .. theme .. [[ + button[6,0.00;2,0.5;select;Select] + button[6,8.25;2,0.5;back;Back] + tablecolumns[color;text] + table[0,0;5.75,9;contacts;#999,Name]] + function mail.show_about(name) local formspec = [[ @@ -75,6 +90,53 @@ function mail.show_inbox(name) minetest.show_formspec(name, "mail:inbox", table.concat(formspec, "")) end +function mail.show_contacts(name) + local formspec = { mail.contacts_formspec } + formspec = mail.compile_contact_list(name, formspec) + minetest.show_formspec(name, "mail:contacts", table.concat(formspec, "")) +end + +function mail.show_edit_contact(name, contact_name, note) + local formspec = [[ + size[8,7.2] + button[7,0;1,0.5;back;X] + label[0,0;Player name: %s] + label[0,0.5;Note: %s] + button[1,6.7;2,1;reply;Reply] + button[3,6.7;2,1;forward;Forward] + button[5,6.7;2,1;delete;Delete] + ]] .. theme + minetest.show_formspec(name, "mail:editcontact", table.concat(formspec, "")) +end + +function mail.show_select_contact(name) + local formspec = { mail.select_contact_formspec } + formspec = mail.compile_contact_list(name, formspec) + minetest.show_formspec(name, "mail:selectcontact", table.concat(formspec, "")) +end + +function mail.compile_contact_list(name, formspec) + local contacts = mail.getContacts(name) + + if contacts[1] then + for _, contact in ipairs(contacts) do + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = minetest.formspec_escape(contact.name) + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = minetest.formspec_escape(contact.note) + end + if selected_contact_idxs[name] then + formspec[#formspec + 1] = ";" + formspec[#formspec + 1] = tostring(selected_contact_idxs[name] + 1) + end + formspec[#formspec + 1] = "]" + else + formspec[#formspec + 1] = "]label[2,4.5;No contacts]" + end + return formspec +end + function mail.show_message(name, msgnumber) local messages = mail.getMessages(name) local message = messages[msgnumber] @@ -231,6 +293,55 @@ function mail.handle_receivefields(player, formname, fields) mail.show_inbox(player:get_player_name()) end) return true + --[[elseif formname == "mail:contacts" then + if fields.back then + mail.show_inbox(name) + elseif fields.new then + mail.show_edit_contact(name, "", "") + elseif fields.edit then + if contacts[selected_contact_idxs[name]]--[[ then + mail.show_edit_contact(name, selected_contact_idxs[name], "") + elseif fields.delete then + if contacts[selected_contact_idxs[name]]--[[ then + table.remove(contacts,selected_contact_idxs[name]) + end + mail.show_contacts(name) + end + + mail.setContacts(name, contacts) + return true]]-- + + elseif formname == "mail:contacts" then + local name = player:get_player_name() + local contacts = mail.getContacts(name) + + if fields.contacts then + local evt = minetest.explode_table_event(fields.contacts) + selected_contact_idxs[name] = evt.row - 1 + if evt.type == "DCL" and contacts[selected_contact_idxs[name]] then + mail.show_contact(name, selected_contact_idxs[name]) + end + mail.setContacts(name, contacts) + return true + end + elseif fields.new then + mail.show_edit_contact(name,"", "") + elseif fields.edit then + mail.show_contact(name, selected_contact_idxs[name]) + elseif fields.delete then + if contacts[selected_contact_idxs[name]] then + table.remove(contacts, selected_contact_idxs[name]) + end + + mail.show_inbox(name) + + elseif fields.back then + mail.show_inbox(name) + + end + + mail.setMessages(name, messages) + return true elseif fields.mail then mail.show_inbox(player:get_player_name()) From f5f21feb4908a6ae38a5431dc170736e75b59dc7 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 31 Jul 2020 23:53:49 +0200 Subject: [PATCH 04/14] fix rename thing leftover --- gui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.lua b/gui.lua index 6baf311..9ff2840 100644 --- a/gui.lua +++ b/gui.lua @@ -111,7 +111,7 @@ function mail.show_compose(name, defaultto, defaultsubj, defaultbody, defaultcc, ]] .. theme formspec = string.format(formspec, - minetest.formspec_escape(defaulttgt), + minetest.formspec_escape(defaultto), minetest.formspec_escape(defaultsubj), minetest.formspec_escape(defaultbody), minetest.formspec_escape(defaultcc), From ea0de708da76aaaaf3e5a4b4a24b219bd02ca3ec Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 10 Aug 2020 11:43:10 +0200 Subject: [PATCH 05/14] fix some things, adjust GUI elements - messages are now actually sent (bug in parse player list) - no more crashes on sending mail (forgot to make variables local) - actually handle CC and BCC fields instead of leaving them empty, duh - make new functions be under the mail namespace - add util functions to ensure the new format, parse and player list as well as checking whether a player is in that list or not - rearrange some GUI elements (tighter spacing, grouping, increase window height to be consistent) - convert mails to new format only as needed (old mails stay intact in case someone reverts to old version) - mails are shaded differently in inbox, depending on whether the player is in the TO field - FROM, TO and CC fields are all displayed when reading a mail - add "Reply All" button (TO includes all original recipients plus the sender, but excluding the player himself, while adopting the CC field. To contrast: "Reply" just sets the original sender as TO and leaves the rest empty) - move reply, replyall and forward to their own functions in GUI (was duplicated for inbox and show mail) - don't needlessly set messages table when we do nothing but go back --- api.lua | 13 ++-- gui.lua | 147 ++++++++++++++++++++++++++++++++------------- util/normalize.lua | 44 +++++++++++--- 3 files changed, 150 insertions(+), 54 deletions(-) diff --git a/api.lua b/api.lua index 65d466f..1b3c3a8 100644 --- a/api.lua +++ b/api.lua @@ -28,6 +28,9 @@ function mail.send(src, dst, subject, body) m.body = body end + local cc + local bcc + local extra -- log mail send action if m.cc or m.bcc then if m.cc then @@ -43,22 +46,22 @@ function mail.send(src, dst, subject, body) else bcc = "" end - extra = "(" .. cc .. bcc .. ") " + extra = " (" .. cc .. bcc .. ")" else extra = "" end - minetest.log("action", "[mail] '" .. m.from .. "' sends mail to '" .. m.to .. + minetest.log("action", "[mail] '" .. m.from .. "' sends mail to '" .. m.to .. "'" .. extra .. "' with subject '" .. m.subject .. "' and body: '" .. m.body .. "'") -- normalize to, cc and bcc while compiling a list of all recipients local recipients = {} - m.to = normalize_players_and_add_recipients(m.to, recipients) + m.to = mail.normalize_players_and_add_recipients(m.to, recipients) if m.cc then - m.cc = normalize_players_and_add_recipients(m.cc, recipients) + m.cc = mail.normalize_players_and_add_recipients(m.cc, recipients) end if m.bcc then - m.bcc = normalize_players_and_add_recipients(m.bcc, recipients) + m.bcc = mail.normalize_players_and_add_recipients(m.bcc, recipients) end -- form the actual mail diff --git a/gui.lua b/gui.lua index 9ff2840..b7efc5e 100644 --- a/gui.lua +++ b/gui.lua @@ -8,15 +8,16 @@ else end mail.inbox_formspec = "size[8,9;]" .. theme .. [[ - button_exit[7.5,0;0.5,0.5;quit;X] + button_exit[7.25,0;0.75,0.5;quit;X] button[6,1;2,0.5;new;New Message] button[6,2;2,0.5;read;Read] button[6,3;2,0.5;reply;Reply] - button[6,4;2,0.5;forward;Forward] - button[6,5;2,0.5;delete;Delete] - button[6,6;2,0.5;markread;Mark Read] - button[6,7;2,0.5;markunread;Mark Unread] - button[6,8;2,0.5;about;About] + button[6,3.8;2,0.5;replyall;Reply All] + button[6,4.6;2,0.5;forward;Forward] + button[6,5.6;2,0.5;markread;Mark Read] + button[6,6.4;2,0.5;markunread;Mark Unread] + button[6,7.4;2,0.5;delete;Delete] + button[6,8.4;2,0.5;about;About] tablecolumns[color;text;text] table[0,0;5.75,9;messages;#999,From,Subject]] @@ -24,7 +25,7 @@ mail.inbox_formspec = "size[8,9;]" .. theme .. [[ function mail.show_about(name) local formspec = [[ size[8,5;] - button[7.5,0;0.5,0.5;back;X] + button[7.25,0;0.75,0.5;back;X] label[0,0;Mail] label[0,0.5;By cheapie] label[0,1;http://github.com/cheapie/mail] @@ -44,13 +45,22 @@ function mail.show_inbox(name) if messages[1] then for _, message in ipairs(messages) do + mail.ensure_new_format(message) if message.unread then - formspec[#formspec + 1] = ",#FFD700" + if not mail.player_in_list(name, message.to) then + formspec[#formspec + 1] = ",#FFD788" + else + formspec[#formspec + 1] = ",#FFD700" + end else - formspec[#formspec + 1] = "," + if not mail.player_in_list(name, message.to) then + formspec[#formspec + 1] = ",#CCCCDD" + else + formspec[#formspec + 1] = "," + end end formspec[#formspec + 1] = "," - formspec[#formspec + 1] = minetest.formspec_escape(message.sender) + formspec[#formspec + 1] = minetest.formspec_escape(message.from) formspec[#formspec + 1] = "," if message.subject ~= "" then if string.len(message.subject) > 30 then @@ -79,47 +89,90 @@ function mail.show_message(name, msgnumber) local messages = mail.getMessages(name) local message = messages[msgnumber] local formspec = [[ - size[8,7.2] - button[7,0;1,0.5;back;X] + size[8,9] + button[7.25,0;0.75,0.5;back;X] label[0,0;From: %s] - label[0,0.5;Subject: %s] - textarea[0.25,1.25;8,6.25;body;;%s] - button[1,6.7;2,1;reply;Reply] - button[3,6.7;2,1;forward;Forward] - button[5,6.7;2,1;delete;Delete] + label[0,0.4;To: %s] + label[0,0.8;CC: %s] + label[0,1.3;Subject: %s] + textarea[0.25,1.8;8,7.8;body;;%s] + button[0,8.5;2,1;reply;Reply] + button[2,8.5;2,1;replyall;Reply All] + button[4,8.5;2,1;forward;Forward] + button[6,8.5;2,1;delete;Delete] ]] .. theme local from = minetest.formspec_escape(message.from) + local to = minetest.formspec_escape(message.to) + local cc = minetest.formspec_escape(message.cc) local subject = minetest.formspec_escape(message.subject) local body = minetest.formspec_escape(message.body) - formspec = string.format(formspec, from, subject, body) + formspec = string.format(formspec, from, to, cc, subject, body) minetest.show_formspec(name,"mail:message",formspec) end function mail.show_compose(name, defaultto, defaultsubj, defaultbody, defaultcc, defaultbcc) local formspec = [[ - size[8,7.2] - field[0.25,0.5;4,1;to;To:;%s] - field[0.25,0.5;4,1;to;CC:;%s] - field[0.25,0.5;4,1;to;BCC:;%s] - field[0.25,1.7;8,1;subject;Subject:;%s] - textarea[0.25,2.4;8,5;body;;%s] - button[0.5,6.7;3,1;cancel;Cancel] - button[7,0;1,0.5;cancel;X] - button[4.5,6.7;3,1;send;Send] + size[8,9] + field[0.25,0.5;3.5,1;to;To:;%s] + field[3.75,0.5;3.75,1;cc;CC:;%s] + field[3.75,1.6;3.75,1;bcc;BCC:;%s] + field[0.25,2.5;8,1;subject;Subject:;%s] + textarea[0.25,3.2;8,6;body;;%s] + button[0.5,8.5;3,1;cancel;Cancel] + button[7.25,0;0.75,0.5;cancel;X] + button[4.5,8.5;3,1;send;Send] ]] .. theme + defaultto = defaultto or "" + defaultsubj = defaultsubj or "" + defaultbody = defaultbody or "" + defaultcc = defaultcc or "" + defaultbcc = defaultbcc or "" + formspec = string.format(formspec, minetest.formspec_escape(defaultto), - minetest.formspec_escape(defaultsubj), - minetest.formspec_escape(defaultbody), minetest.formspec_escape(defaultcc), - minetest.formspec_escape(defaultbcc)) + minetest.formspec_escape(defaultbcc), + minetest.formspec_escape(defaultsubj), + minetest.formspec_escape(defaultbody)) minetest.show_formspec(name, "mail:compose", formspec) end +function mail.reply(name, message) + mail.ensure_new_format(message) + local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body + mail.show_compose(name, message.from, "Re: "..message.subject, replyfooter) +end + +function mail.replyall(name, message) + mail.ensure_new_format(message) + local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body + -- new recipients are the sender plus the original recipients, minus ourselves + local recipients = message.to + if message.from ~= nil then + recipients = message.from .. ", " .. recipients + end + print('parsing recipients: '..recipients) + recipients = mail.parse_player_list(recipients) + for k,v in pairs(recipients) do + if v == name then + table.remove(recipients, k) + break + end + end + recipients = mail.concat_player_list(recipients) + print('resulting recipients: '..recipients) + mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, message.cc) +end + +function mail.forward(name, message) + local fwfooter = "Type your message here.\n\n--Original message follows--\n" ..message.body + mail.show_compose(name, "", "Fw: "..message.subject, fwfooter) +end + function mail.handle_receivefields(player, formname, fields) if formname == "" and fields and fields.quit and minetest.get_modpath("unified_inventory") then unified_inventory.set_inventory_formspec(player, "craft") @@ -158,28 +211,36 @@ function mail.handle_receivefields(player, formname, fields) mail.show_inbox(name) elseif fields.reply and messages[selected_message_idxs[name]] then local message = messages[selected_message_idxs[name]] - local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, message.from, "Re: "..message.subject,replyfooter) + mail.reply(name, message) + + elseif fields.replyall and messages[selected_message_idxs[name]] then + local message = messages[selected_message_idxs[name]] + mail.replyall(name, message) elseif fields.forward and messages[selected_message_idxs[name]] then local message = messages[selected_message_idxs[name]] - local fwfooter = "Type your message here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, "", "Fw: "..message.subject, fwfooter) + mail.forward(name, message) elseif fields.markread then if messages[selected_message_idxs[name]] then messages[selected_message_idxs[name]].unread = false end + -- set messages immediately, so it shows up already when updating the inbox + mail.setMessages(name, messages) mail.show_inbox(name) + return true elseif fields.markunread then if messages[selected_message_idxs[name]] then messages[selected_message_idxs[name]].unread = true end + -- set messages immediately, so it shows up already when updating the inbox + mail.setMessages(name, messages) mail.show_inbox(name) + return true elseif fields.new then - mail.show_compose(name,"","","Type your message here.") + mail.show_compose(name) elseif fields.quit then if minetest.get_modpath("unified_inventory") then @@ -199,14 +260,16 @@ function mail.handle_receivefields(player, formname, fields) if fields.back then mail.show_inbox(name) + return true -- don't uselessly set messages elseif fields.reply then local message = messages[selected_message_idxs[name]] - local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, message.sender, "Re: "..message.subject, replyfooter) + mail.reply(name, message) + elseif fields.replyall then + local message = messages[selected_message_idxs[name]] + mail.replyall(name, message) elseif fields.forward then local message = messages[selected_message_idxs[name]] - local fwfooter = "Type your message here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, "", "Fw: "..message.subject, fwfooter) + mail.forward(name, message.subject) elseif fields.delete then if messages[selected_message_idxs[name]] then table.remove(messages,selected_message_idxs[name]) @@ -221,10 +284,10 @@ function mail.handle_receivefields(player, formname, fields) mail.send({ from = player:get_player_name(), to = fields.to, - cc = "", - bcc = "" + cc = fields.cc, + bcc = fields.bcc, subject = fields.subject, - body = fields.body + body = fields.body, }) end minetest.after(0.5, function() diff --git a/util/normalize.lua b/util/normalize.lua index 13f51b0..f91859f 100644 --- a/util/normalize.lua +++ b/util/normalize.lua @@ -2,7 +2,18 @@ return the field normalized (comma separated, single space) and add individual player names to recipient list --]] -function normalize_players_and_add_recipients(field, recipients) +function mail.normalize_players_and_add_recipients(field, recipients) + local order = mail.parse_player_list(field) + for i,c in ipairs(order) do + if recipients[string.lower(c)] == nil then + recipients[string.lower(c)] = c + end + end + return mail.concat_player_list(order) +end + + +function mail.parse_player_list(field) local separator = ", " local pattern = "([^" .. separator .. "]+)" @@ -10,17 +21,36 @@ function normalize_players_and_add_recipients(field, recipients) local player_set = {} local order = {} field:gsub(pattern, function(c) - if player_set[string.lower(c)] ~= nil then + if player_set[string.lower(c)] == nil then player_set[string.lower(c)] = c order[#order+1] = c - - -- also sort into recipients - if recipients[string.lower(c)] ~= nil then - recipients[string.lower(c)] = c - end end end) + return order +end + +function mail.concat_player_list(order) -- turn list of players back into normalized string return table.concat(order, ", ") end + +function mail.player_in_list(name, list) + if type(list) == "string" then + list = mail.parse_player_list(list) + end + for k,c in pairs(list) do + if name == c then + return true + end + end + return false +end + + +function mail.ensure_new_format(message) + if message.sender then + message.from = message.sender + message.to = name + end +end From 1f5a963d782a1ed8277ee8d9d0305247a223b5f0 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 10 Aug 2020 12:19:42 +0200 Subject: [PATCH 06/14] remove prints --- gui.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/gui.lua b/gui.lua index b7efc5e..0739d9d 100644 --- a/gui.lua +++ b/gui.lua @@ -155,7 +155,6 @@ function mail.replyall(name, message) if message.from ~= nil then recipients = message.from .. ", " .. recipients end - print('parsing recipients: '..recipients) recipients = mail.parse_player_list(recipients) for k,v in pairs(recipients) do if v == name then @@ -164,7 +163,6 @@ function mail.replyall(name, message) end end recipients = mail.concat_player_list(recipients) - print('resulting recipients: '..recipients) mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, message.cc) end From f82c3d2b82f63858d4d892ae512ed1c7b6a374be Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 10 Aug 2020 18:29:11 +0200 Subject: [PATCH 07/14] add contacts dir, leave mail files as is - restructured storage.lua so reading/writing json is not duplicated - when a player joins and has no contacts file yet, automatically add all players he wrote to --- init.lua | 1 + migrate.lua | 26 +++++++++++++++++++++++ onjoin.lua | 2 ++ storage.lua | 60 +++++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/init.lua b/init.lua index 4410e13..4423265 100644 --- a/init.lua +++ b/init.lua @@ -8,6 +8,7 @@ mail = { -- mail directory maildir = minetest.get_worldpath().."/mails", + contactsdir = minetest.get_worldpath().."/mails/contacts", -- allow item/node attachments allow_attachments = minetest.settings:get("mail.allow_attachments") == "true", diff --git a/migrate.lua b/migrate.lua index 6ce5dde..5780732 100644 --- a/migrate.lua +++ b/migrate.lua @@ -4,6 +4,7 @@ mail.migrate = function() -- create directory, just in case minetest.mkdir(mail.maildir) + minetest.mkdir(mail.contactsdir) local file = io.open(minetest.get_worldpath().."/mail.db", "r") if file then @@ -23,3 +24,28 @@ mail.migrate = function() end end + + +mail.migrate_contacts = function(playername) + local file = io.open(mail.getContactsFile(playername), 'r') + if not file then + file:close() -- file doesn't exist! This is a case for Migrate Man! + + local messages = mail.getMessages(playername) + local contacts = {} + + if messages and not contacts then + for k,message in pairs(messages) do + mail.ensure_new_format(message) + if contacts[string.lower(message.from)] == nil then + contacts[string.lower(message.from)] = { + name = message.from, + note = "", + } + end + end + end + else + file:close() -- uh, um, nope, let's leave those alone, shall we? + end +end diff --git a/onjoin.lua b/onjoin.lua index 79ad287..2433540 100644 --- a/onjoin.lua +++ b/onjoin.lua @@ -16,4 +16,6 @@ minetest.register_on_joinplayer(function(player) end end, player:get_player_name()) + + mail.migrate_contacts(player:get_player_name()) end) diff --git a/storage.lua b/storage.lua index 8f50a74..671242b 100644 --- a/storage.lua +++ b/storage.lua @@ -1,32 +1,70 @@ -- TODO: maybe local cache? -function getMailFile(playername) +function mail.getMailFile(playername) local saneplayername = string.gsub(playername, "[.|/]", "") return mail.maildir .. "/" .. saneplayername .. ".json" end +function mail.getContactsFile(playername) + local saneplayername = string.gsub(playername, "[.|/]", "") + return mail.maildir .. "/contacts/" .. saneplayername .. ".json" +end + + mail.getMessages = function(playername) - local file = io.open(getMailFile(playername), "r") - local messages = {} - if file then - local json = file:read("*a") - messages = minetest.parse_json(json or "[]") or {} + local messages = mail.read_json_file(mail.getMailFile(playername)) + if messages then mail.hud_update(playername, messages) - file:close() end return messages end mail.setMessages = function(playername, messages) - local file = io.open(getMailFile(playername),"w") - local json = minetest.write_json(messages) - if file and file:write(json) and file:close() then + if mail.write_json_file(mail.getMailFile(playername), messages) then mail.hud_update(playername, messages) return true else - minetest.log("error","[mail] Save failed - messages may be lost!") + minetest.log("error","[mail] Save failed - messages may be lost! ("..playername..")") + return false + end +end + + +mail.getContacts = function(playername) + return mail.read_json_file(mail.getContactsFile(playername)) +end + +mail.setContacts = function(playername, contacts) + if mail.write_json_file(mail.getContactsFile(playername), contacts) then + return true + else + minetest.log("error","[mail] Save failed - contacts may be lost! ("..playername..")") + return false + end +end + + +function mail.read_json_file(path) + local file = io.open(path, "r") + local content = {} + if file then + local json = file:read("*a") + print(string.format('read from %s: %s', path, json)) + content = minetest.parse_json(json or "[]") or {} + file:close() + end + return content +end + +function mail.write_json_file(path, content) + local file = io.open(path,"w") + local json = minetest.write_json(content) + print(string.format('writing to %s: %s', path, json)) + if file and file:write(json) and file:close() then + return true + else return false end end From b60cd978b9330461106a1fa9ed8623f19232bacc Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 10 Aug 2020 18:41:05 +0200 Subject: [PATCH 08/14] add GUI to manage contacts - show hint when trying to save an illegal contact (empty name or collision) - contacts list (selection logic is a bit less straight forward than with messages because contacts are not a list but have strings as keys to prevent duplicate entries) - contacts list: only show first line of notes - add direct recipients (in TO) to contacts when sending (in gui.lua, as it's a comfort feature) - set messages immediately when clicking read/unread, not *after* redrawing the inbox - set messages only when something changed to reduce disk writes - move marking mails read to the show message formspec (exept the direct button) - adjust "No Mail" label --- gui.lua | 200 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 63 deletions(-) diff --git a/gui.lua b/gui.lua index ea73342..7ee95e2 100644 --- a/gui.lua +++ b/gui.lua @@ -1,4 +1,5 @@ selected_message_idxs = {} +selected_contact_idxs = {} local theme if minetest.get_modpath("default") then @@ -23,15 +24,15 @@ mail.inbox_formspec = "size[8,9;]" .. theme .. [[ tablecolumns[color;text;text] table[0,0;5.75,9;messages;#999,From,Subject]] -mail.contacts_formspec = "size[8,10;]" .. theme .. [[ - button[6,0.00;2,0.5;new;New] - button[6,0.75;2,0.5;edit;Edit] - button[6,1.50;2,0.5;delete;Delete] +mail.contacts_formspec = "size[8,9;]" .. theme .. [[ + button[6,0.10;2,0.5;new;New] + button[6,0.85;2,0.5;edit;Edit] + button[6,1.60;2,0.5;delete;Delete] button[6,8.25;2,0.5;back;Back] tablecolumns[color;text;text] table[0,0;5.75,9;contacts;#999,Name,Note]] -mail.select_contact_formspec = "size[8,10;]" .. theme .. [[ +mail.select_contact_formspec = "size[8,9;]" .. theme .. [[ button[6,0.00;2,0.5;select;Select] button[6,8.25;2,0.5;back;Back] tablecolumns[color;text] @@ -96,7 +97,7 @@ function mail.show_inbox(name) end formspec[#formspec + 1] = "]" else - formspec[#formspec + 1] = "]label[2,4.5;No mail]" + formspec[#formspec + 1] = "]label[2.25,4.5;No mail]" end minetest.show_formspec(name, "mail:inbox", table.concat(formspec, "")) end @@ -107,17 +108,32 @@ function mail.show_contacts(name) minetest.show_formspec(name, "mail:contacts", table.concat(formspec, "")) end -function mail.show_edit_contact(name, contact_name, note) +function mail.show_edit_contact(name, contact_name, note, illegal_name_hint) local formspec = [[ - size[8,7.2] - button[7,0;1,0.5;back;X] - label[0,0;Player name: %s] - label[0,0.5;Note: %s] - button[1,6.7;2,1;reply;Reply] - button[3,6.7;2,1;forward;Forward] - button[5,6.7;2,1;delete;Delete] - ]] .. theme - minetest.show_formspec(name, "mail:editcontact", table.concat(formspec, "")) + size[6,7] + button[4,6.25;2,0.5;back;Back] + field[0.25,0.5;4,1;name;Player name:;%s] + textarea[0.25,1.6;4,6.25;note;Note:;%s] + button[4,0.10;2,1;save;Save] + ]] + if illegal_name_hint == "collision" then + formspec = formspec .. [[ + label[4,1;That name] + label[4,1.5;is already in] + label[4,2;your contacts.] + ]] + elseif illegal_name_hint == "empty" then + formspec = formspec .. [[ + label[4,1;The contact] + label[4,1.5;name cannot] + label[4,2;be empty.] + ]] + end + formspec = formspec .. theme + formspec = string.format(formspec, + minetest.formspec_escape(contact_name or ""), + minetest.formspec_escape(note or "")) + minetest.show_formspec(name, "mail:editcontact", formspec) end function mail.show_select_contact(name) @@ -129,17 +145,29 @@ end function mail.compile_contact_list(name, formspec) local contacts = mail.getContacts(name) - if contacts[1] then - for _, contact in ipairs(contacts) do - formspec[#formspec + 1] = "," - formspec[#formspec + 1] = "," - formspec[#formspec + 1] = minetest.formspec_escape(contact.name) - formspec[#formspec + 1] = "," - formspec[#formspec + 1] = minetest.formspec_escape(contact.note) + local i = 0 + local selected = nil + for k, contact in pairs(contacts) do + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = minetest.formspec_escape(contact.name) + formspec[#formspec + 1] = "," + local note = contact.note + -- display an ellipsis if the note spans multiple lines + local idx = string.find(note, '\n') + if idx ~= nil then + note = string.sub(note, 1, idx-1) .. ' ...' end - if selected_contact_idxs[name] then + formspec[#formspec + 1] = minetest.formspec_escape(note) + i = i + 1 + if selected_contact_idxs[name] == k then + selected = i + end + end + if i > 0 then + if selected then formspec[#formspec + 1] = ";" - formspec[#formspec + 1] = tostring(selected_contact_idxs[name] + 1) + formspec[#formspec + 1] = tostring(selected + 1) end formspec[#formspec + 1] = "]" else @@ -172,6 +200,11 @@ function mail.show_message(name, msgnumber) local body = minetest.formspec_escape(message.body) formspec = string.format(formspec, from, to, cc, subject, body) + if message.unread then + message.unread = false + mail.setMessages(name, messages) + end + minetest.show_formspec(name,"mail:message",formspec) end @@ -252,21 +285,19 @@ function mail.handle_receivefields(player, formname, fields) local evt = minetest.explode_table_event(fields.messages) selected_message_idxs[name] = evt.row - 1 if evt.type == "DCL" and messages[selected_message_idxs[name]] then - messages[selected_message_idxs[name]].unread = false mail.show_message(name, selected_message_idxs[name]) end - mail.setMessages(name, messages) return true end if fields.read then if messages[selected_message_idxs[name]] then - messages[selected_message_idxs[name]].unread = false mail.show_message(name, selected_message_idxs[name]) end elseif fields.delete then if messages[selected_message_idxs[name]] then table.remove(messages, selected_message_idxs[name]) + mail.setMessages(name, messages) end mail.show_inbox(name) @@ -285,24 +316,27 @@ function mail.handle_receivefields(player, formname, fields) elseif fields.markread then if messages[selected_message_idxs[name]] then messages[selected_message_idxs[name]].unread = false + -- set messages immediately, so it shows up already when updating the inbox + mail.setMessages(name, messages) end - -- set messages immediately, so it shows up already when updating the inbox - mail.setMessages(name, messages) mail.show_inbox(name) return true elseif fields.markunread then if messages[selected_message_idxs[name]] then messages[selected_message_idxs[name]].unread = true + -- set messages immediately, so it shows up already when updating the inbox + mail.setMessages(name, messages) end - -- set messages immediately, so it shows up already when updating the inbox - mail.setMessages(name, messages) mail.show_inbox(name) return true elseif fields.new then mail.show_compose(name) + elseif fields.contacts then + mail.show_contacts(name) + elseif fields.quit then if minetest.get_modpath("unified_inventory") then unified_inventory.set_inventory_formspec(player, "craft") @@ -313,7 +347,6 @@ function mail.handle_receivefields(player, formname, fields) end - mail.setMessages(name, messages) return true elseif formname == "mail:message" then local name = player:get_player_name() @@ -334,44 +367,43 @@ function mail.handle_receivefields(player, formname, fields) elseif fields.delete then if messages[selected_message_idxs[name]] then table.remove(messages,selected_message_idxs[name]) + mail.setMessages(name, messages) end mail.show_inbox(name) end - - mail.setMessages(name, messages) return true + elseif formname == "mail:compose" then if fields.send then + local name = player:get_player_name() mail.send({ - from = player:get_player_name(), + from = name, to = fields.to, cc = fields.cc, bcc = fields.bcc, subject = fields.subject, body = fields.body, }) + local contacts = mail.getContacts(name) + local recipients = mail.parse_player_list(fields.to) + local changed = false + for _,v in pairs(recipients) do + if contacts[string.lower(v)] == nil then + contacts[string.lower(v)] = { + name = v, + note = "", + } + changed = true + end + end + if changed then + mail.setContacts(name, contacts) + end end minetest.after(0.5, function() mail.show_inbox(player:get_player_name()) end) return true - --[[elseif formname == "mail:contacts" then - if fields.back then - mail.show_inbox(name) - elseif fields.new then - mail.show_edit_contact(name, "", "") - elseif fields.edit then - if contacts[selected_contact_idxs[name]]--[[ then - mail.show_edit_contact(name, selected_contact_idxs[name], "") - elseif fields.delete then - if contacts[selected_contact_idxs[name]]--[[ then - table.remove(contacts,selected_contact_idxs[name]) - end - mail.show_contacts(name) - end - - mail.setContacts(name, contacts) - return true]]-- elseif formname == "mail:contacts" then local name = player:get_player_name() @@ -379,30 +411,72 @@ function mail.handle_receivefields(player, formname, fields) if fields.contacts then local evt = minetest.explode_table_event(fields.contacts) - selected_contact_idxs[name] = evt.row - 1 - if evt.type == "DCL" and contacts[selected_contact_idxs[name]] then - mail.show_contact(name, selected_contact_idxs[name]) + --selected_contact_idxs[name] = evt.row - 1 + local i = 0 + for k,c in pairs(contacts) do + i = i + 1 + if i == evt.row - 1 then + selected_contact_idxs[name] = k + break + end + end + if evt.type == "DCL" and contacts[selected_contact_idxs[name]] then + mail.show_edit_contact(name, contacts[selected_contact_idxs[name]].name, contacts[selected_contact_idxs[name]].note) end - mail.setContacts(name, contacts) return true elseif fields.new then - mail.show_edit_contact(name,"", "") + selected_contact_idxs[name] = "#NEW#" + mail.show_edit_contact(name, "", "") elseif fields.edit then - mail.show_contact(name, selected_contact_idxs[name]) + mail.show_edit_contact(name, contacts[selected_contact_idxs[name]].name, contacts[selected_contact_idxs[name]].note) elseif fields.delete then if contacts[selected_contact_idxs[name]] then - table.remove(contacts, selected_contact_idxs[name]) + contacts[selected_contact_idxs[name]] = nil + mail.setContacts(name, contacts) end - mail.show_inbox(name) + mail.show_contacts(name) elseif fields.back then mail.show_inbox(name) end + elseif formname == "mail:editcontact" then + local name = player:get_player_name() + local contacts = mail.getContacts(name) - mail.setMessages(name, messages) - return true + if fields.save then + if selected_contact_idxs[name] and selected_contact_idxs[name] ~= "#NEW#" then + local contact = contacts[selected_contact_idxs[name]] + if selected_contact_idxs[name] ~= string.lower(fields.name) then + -- name changed! + if #fields.name == 0 then + mail.show_edit_contact(name, contact.name, fields.note, "empty") + return true + elseif contacts[string.lower(fields.name)] ~= nil then + mail.show_edit_contact(name, contact.name, fields.note, "collision") + return true + else + contacts[string.lower(fields.name)] = contact + contacts[selected_contact_idxs[name]] = nil + end + end + contact.name = fields.name + contact.note = fields.note + else + local contact = { + name = fields.name, + note = fields.note, + } + contacts[string.lower(contact.name)] = contact + end + mail.setContacts(name, contacts) + mail.show_contacts(name) + + elseif fields.back then + mail.show_contacts(name) + + end elseif fields.mail then mail.show_inbox(player:get_player_name()) From 756b951d91ca57e3bc4b59805b16c877ac6cf8d7 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 12 Aug 2020 11:20:48 +0200 Subject: [PATCH 09/14] add working contact selection dialog --- gui.lua | 324 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 243 insertions(+), 81 deletions(-) diff --git a/gui.lua b/gui.lua index 7ee95e2..b29e7aa 100644 --- a/gui.lua +++ b/gui.lua @@ -1,5 +1,12 @@ -selected_message_idxs = {} -selected_contact_idxs = {} +-- refactor these to some proper management thing +selected_idxs = { + messages = {}, + contacts = {}, + to = {}, + cc = {}, + bcc = {}, +} +message_drafts = {} local theme if minetest.get_modpath("default") then @@ -33,10 +40,18 @@ mail.contacts_formspec = "size[8,9;]" .. theme .. [[ table[0,0;5.75,9;contacts;#999,Name,Note]] mail.select_contact_formspec = "size[8,9;]" .. theme .. [[ - button[6,0.00;2,0.5;select;Select] - button[6,8.25;2,0.5;back;Back] - tablecolumns[color;text] - table[0,0;5.75,9;contacts;#999,Name]] + tablecolumns[color;text;text] + table[0,0;3.5,9;contacts;#999,Name,Note%s] + button[3.55,2.00;1.75,0.5;toadd;→ Add] + button[3.55,2.75;1.75,0.5;toremove;← Remove] + button[3.55,6.00;1.75,0.5;ccadd;→ Add] + button[3.55,6.75;1.75,0.5;ccremove;← Remove] + tablecolumns[color;text;text] + table[5.15,0.0;2.75,4.5;to;#999,Name,Note%s] + tablecolumns[color;text;text] + table[5.15,4.6;2.75,4.5;cc;#999,Name,Note%s] + button[3.55,8.25;1.75,0.5;back;Back] + ]] function mail.show_about(name) @@ -60,6 +75,8 @@ function mail.show_inbox(name) local formspec = { mail.inbox_formspec } local messages = mail.getMessages(name) + message_drafts[name] = nil + if messages[1] then for _, message in ipairs(messages) do mail.ensure_new_format(message) @@ -91,9 +108,9 @@ function mail.show_inbox(name) formspec[#formspec + 1] = "(No subject)" end end - if selected_message_idxs[name] then + if selected_idxs.messages[name] then formspec[#formspec + 1] = ";" - formspec[#formspec + 1] = tostring(selected_message_idxs[name] + 1) + formspec[#formspec + 1] = tostring(selected_idxs.messages[name] + 1) end formspec[#formspec + 1] = "]" else @@ -103,9 +120,8 @@ function mail.show_inbox(name) end function mail.show_contacts(name) - local formspec = { mail.contacts_formspec } - formspec = mail.compile_contact_list(name, formspec) - minetest.show_formspec(name, "mail:contacts", table.concat(formspec, "")) + local formspec = mail.contacts_formspec .. mail.compile_contact_list(name, selected_idxs.contacts[name]) + minetest.show_formspec(name, "mail:contacts", formspec) end function mail.show_edit_contact(name, contact_name, note, illegal_name_hint) @@ -136,44 +152,101 @@ function mail.show_edit_contact(name, contact_name, note, illegal_name_hint) minetest.show_formspec(name, "mail:editcontact", formspec) end -function mail.show_select_contact(name) - local formspec = { mail.select_contact_formspec } - formspec = mail.compile_contact_list(name, formspec) - minetest.show_formspec(name, "mail:selectcontact", table.concat(formspec, "")) +function mail.show_select_contact(name, to, cc, bcc) + local formspec = mail.select_contact_formspec + local contacts = mail.compile_contact_list(name, selected_idxs.contacts[name]) + + -- compile lists + if to then + to = mail.compile_contact_list(name, selected_idxs.to[name], to) + else + to = "" + end + if cc then + cc = mail.compile_contact_list(name, selected_idxs.cc[name], cc) + else + cc = "" + end + --[[if bcc then + bcc = table.concat(mail.compile_contact_list(name, selected_idxs.bcc[name], bcc) + else + bcc = "" + end]]-- + formspec = string.format(formspec, contacts, to, cc)--, bcc() + minetest.show_formspec(name, "mail:selectcontact", formspec) end -function mail.compile_contact_list(name, formspec) +function mail.compile_contact_list(name, selected, playernames) + -- TODO: refactor this - not just compiles *a* list, but *the* list for the contacts screen (too inflexible) + local formspec = {} local contacts = mail.getContacts(name) local i = 0 - local selected = nil - for k, contact in pairs(contacts) do - formspec[#formspec + 1] = "," - formspec[#formspec + 1] = "," - formspec[#formspec + 1] = minetest.formspec_escape(contact.name) - formspec[#formspec + 1] = "," - local note = contact.note - -- display an ellipsis if the note spans multiple lines - local idx = string.find(note, '\n') - if idx ~= nil then - note = string.sub(note, 1, idx-1) .. ' ...' + if playernames == nil then + for k, contact in pairs(contacts) do + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = minetest.formspec_escape(contact.name) + formspec[#formspec + 1] = "," + local note = contact.note + -- display an ellipsis if the note spans multiple lines + local idx = string.find(note, '\n') + if idx ~= nil then + note = string.sub(note, 1, idx-1) .. ' ...' + end + formspec[#formspec + 1] = minetest.formspec_escape(note) + i = i + 1 + if type(selected) == "string" then + if selected == k then + selected = i + end + end end - formspec[#formspec + 1] = minetest.formspec_escape(note) - i = i + 1 - if selected_contact_idxs[name] == k then - selected = i + if i > 0 then + if selected then + formspec[#formspec + 1] = ";" + formspec[#formspec + 1] = tostring(selected + 1) + end + formspec[#formspec + 1] = "]" + else + formspec[#formspec + 1] = "]label[2,4.5;No contacts]" end - end - if i > 0 then - if selected then + else + if type(playernames) == "string" then + playernames = mail.parse_player_list(playernames) + end + for k,c in pairs(playernames) do + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = "," + formspec[#formspec + 1] = minetest.formspec_escape(c) + formspec[#formspec + 1] = "," + if contacts[string.lower(c)] == nil then + formspec[#formspec + 1] = "" + else + local note = contacts[string.lower(c)].note + -- display an ellipsis if the note spans multiple lines + local idx = string.find(note, '\n') + if idx ~= nil then + note = string.sub(note, 1, idx-1) .. ' ...' + end + formspec[#formspec + 1] = minetest.formspec_escape(note) + end + i = i + 1 + if not selected then + if type(selected) == "string" then + if k == selected then + selected = i + end + end + end + end + if i > 0 and selected then formspec[#formspec + 1] = ";" formspec[#formspec + 1] = tostring(selected + 1) end formspec[#formspec + 1] = "]" - else - formspec[#formspec + 1] = "]label[2,4.5;No contacts]" end - return formspec + return table.concat(formspec, "") end function mail.show_message(name, msgnumber) @@ -211,13 +284,15 @@ end function mail.show_compose(name, defaultto, defaultsubj, defaultbody, defaultcc, defaultbcc) local formspec = [[ size[8,9] - field[0.25,0.5;3.5,1;to;To:;%s] - field[3.75,0.5;3.75,1;cc;CC:;%s] - field[3.75,1.6;3.75,1;bcc;BCC:;%s] - field[0.25,2.5;8,1;subject;Subject:;%s] - textarea[0.25,3.2;8,6;body;;%s] + button[0,0;1,1;tocontacts;To:] + field[1.1,0.3;3.2,1;to;;%s] + button[4,0;1,1;cccontacts;CC:] + field[5.1,0.3;3.1,1;cc;;%s] + button[4,0.75;1,1;bcccontacts;BCC:] + field[5.1,1.05;3.1,1;bcc;;%s] + field[0.25,2;8,1;subject;Subject:;%s] + textarea[0.25,2.5;8,6;body;;%s] button[0.5,8.5;3,1;cancel;Cancel] - button[7.25,0;0.75,0.5;cancel;X] button[4.5,8.5;3,1;send;Send] ]] .. theme @@ -283,39 +358,39 @@ function mail.handle_receivefields(player, formname, fields) if fields.messages then local evt = minetest.explode_table_event(fields.messages) - selected_message_idxs[name] = evt.row - 1 - if evt.type == "DCL" and messages[selected_message_idxs[name]] then - mail.show_message(name, selected_message_idxs[name]) + selected_idxs.messages[name] = evt.row - 1 + if evt.type == "DCL" and messages[selected_idxs.messages[name]] then + mail.show_message(name, selected_idxs.messages[name]) end return true end if fields.read then - if messages[selected_message_idxs[name]] then - mail.show_message(name, selected_message_idxs[name]) + if messages[selected_idxs.messages[name]] then + mail.show_message(name, selected_idxs.messages[name]) end elseif fields.delete then - if messages[selected_message_idxs[name]] then - table.remove(messages, selected_message_idxs[name]) + if messages[selected_idxs.messages[name]] then + table.remove(messages, selected_idxs.messages[name]) mail.setMessages(name, messages) end mail.show_inbox(name) - elseif fields.reply and messages[selected_message_idxs[name]] then - local message = messages[selected_message_idxs[name]] + elseif fields.reply and messages[selected_idxs.messages[name]] then + local message = messages[selected_idxs.messages[name]] mail.reply(name, message) - elseif fields.replyall and messages[selected_message_idxs[name]] then - local message = messages[selected_message_idxs[name]] + elseif fields.replyall and messages[selected_idxs.messages[name]] then + local message = messages[selected_idxs.messages[name]] mail.replyall(name, message) - elseif fields.forward and messages[selected_message_idxs[name]] then - local message = messages[selected_message_idxs[name]] + elseif fields.forward and messages[selected_idxs.messages[name]] then + local message = messages[selected_idxs.messages[name]] mail.forward(name, message) elseif fields.markread then - if messages[selected_message_idxs[name]] then - messages[selected_message_idxs[name]].unread = false + if messages[selected_idxs.messages[name]] then + messages[selected_idxs.messages[name]].unread = false -- set messages immediately, so it shows up already when updating the inbox mail.setMessages(name, messages) end @@ -323,8 +398,8 @@ function mail.handle_receivefields(player, formname, fields) return true elseif fields.markunread then - if messages[selected_message_idxs[name]] then - messages[selected_message_idxs[name]].unread = true + if messages[selected_idxs.messages[name]] then + messages[selected_idxs.messages[name]].unread = true -- set messages immediately, so it shows up already when updating the inbox mail.setMessages(name, messages) end @@ -356,17 +431,17 @@ function mail.handle_receivefields(player, formname, fields) mail.show_inbox(name) return true -- don't uselessly set messages elseif fields.reply then - local message = messages[selected_message_idxs[name]] + local message = messages[selected_idxs.messages[name]] mail.reply(name, message) elseif fields.replyall then - local message = messages[selected_message_idxs[name]] + local message = messages[selected_idxs.messages[name]] mail.replyall(name, message) elseif fields.forward then - local message = messages[selected_message_idxs[name]] + local message = messages[selected_idxs.messages[name]] mail.forward(name, message.subject) elseif fields.delete then - if messages[selected_message_idxs[name]] then - table.remove(messages,selected_message_idxs[name]) + if messages[selected_idxs.messages[name]] then + table.remove(messages,selected_idxs.messages[name]) mail.setMessages(name, messages) end mail.show_inbox(name) @@ -374,8 +449,8 @@ function mail.handle_receivefields(player, formname, fields) return true elseif formname == "mail:compose" then + local name = player:get_player_name() if fields.send then - local name = player:get_player_name() mail.send({ from = name, to = fields.to, @@ -399,10 +474,97 @@ function mail.handle_receivefields(player, formname, fields) if changed then mail.setContacts(name, contacts) end + + minetest.after(0.5, function() + mail.show_inbox(name) + end) + + elseif fields.tocontacts or fields.cccontacts or fields.bcccontacts then + message_drafts[name] = { + to = fields.to, + cc = fields.cc, + bcc = fields.bcc, + subject = fields.subject, + body = fields.body, + } + mail.show_select_contact(name, fields.to, fields.cc, fields.bcc) + elseif fields.cancel then + message_drafts[name] = nil + mail.show_inbox(name) end - minetest.after(0.5, function() - mail.show_inbox(player:get_player_name()) - end) + return true + + elseif formname == "mail:selectcontact" then + local name = player:get_player_name() + local contacts = mail.getContacts(name) + local draft = message_drafts[name] + + -- get indexes for fields with selected rows + -- execute their default button's actions if double clicked + for k,action in pairs({ + contacts = "toadd", + to = "toremove", + cc = "ccremove", + bcc = "bccremove" + }) do + if fields[k] then + local evt = minetest.explode_table_event(fields[k]) + selected_idxs[k][name] = evt.row - 1 + if evt.type == "DCL" and selected_idxs[k][name] then + fields[action] = true + end + return true + end + end + + local update = false + -- add + for _,v in pairs({"to","cc","bcc"}) do + if fields[v.."add"] then + update = true + if selected_idxs.contacts[name] then + local i = 0 + for k, contact in pairs(contacts) do + i = i+1 + if i == selected_idxs.contacts[name] then + local list = mail.parse_player_list(draft[v]) + list[#list+1] = contact.name + selected_idxs[v][name] = #list + draft[v] = mail.concat_player_list(list) + break + end + end + end + end + end + -- remove + for _,v in pairs({"to","cc","bcc"}) do + if fields[v.."remove"] then + update = true + if selected_idxs[v][name] then + local list = mail.parse_player_list(draft[v]) + table.remove(list, selected_idxs[v][name]) + if #list < selected_idxs[v][name] then + selected_idxs[v][name] = #list + end + draft[v] = mail.concat_player_list(list) + end + end + end + if update then + mail.show_select_contact(name, draft.to, draft.cc, draft.bcc) + return true + end + + if fields.back then + -- just do the default stuff below, as ESC will + end + + -- delete old idxs + for _,v in ipairs({"contacts","to","cc","bcc"}) do + selected_idxs[v][name] = nil + end + mail.show_compose(name, draft.to, draft.subject, draft.body, draft.cc, draft.bcc) return true elseif formname == "mail:contacts" then @@ -411,27 +573,27 @@ function mail.handle_receivefields(player, formname, fields) if fields.contacts then local evt = minetest.explode_table_event(fields.contacts) - --selected_contact_idxs[name] = evt.row - 1 + --selected_idxs.contacts[name] = evt.row - 1 local i = 0 for k,c in pairs(contacts) do i = i + 1 if i == evt.row - 1 then - selected_contact_idxs[name] = k + selected_idxs.contacts[name] = k break end end - if evt.type == "DCL" and contacts[selected_contact_idxs[name]] then - mail.show_edit_contact(name, contacts[selected_contact_idxs[name]].name, contacts[selected_contact_idxs[name]].note) + if evt.type == "DCL" and contacts[selected_idxs.contacts[name]] then + mail.show_edit_contact(name, contacts[selected_idxs.contacts[name]].name, contacts[selected_idxs.contacts[name]].note) end return true elseif fields.new then - selected_contact_idxs[name] = "#NEW#" + selected_idxs.contacts[name] = "#NEW#" mail.show_edit_contact(name, "", "") elseif fields.edit then - mail.show_edit_contact(name, contacts[selected_contact_idxs[name]].name, contacts[selected_contact_idxs[name]].note) + mail.show_edit_contact(name, contacts[selected_idxs.contacts[name]].name, contacts[selected_idxs.contacts[name]].note) elseif fields.delete then - if contacts[selected_contact_idxs[name]] then - contacts[selected_contact_idxs[name]] = nil + if contacts[selected_idxs.contacts[name]] then + contacts[selected_idxs.contacts[name]] = nil mail.setContacts(name, contacts) end @@ -446,9 +608,9 @@ function mail.handle_receivefields(player, formname, fields) local contacts = mail.getContacts(name) if fields.save then - if selected_contact_idxs[name] and selected_contact_idxs[name] ~= "#NEW#" then - local contact = contacts[selected_contact_idxs[name]] - if selected_contact_idxs[name] ~= string.lower(fields.name) then + if selected_idxs.contacts[name] and selected_idxs.contacts[name] ~= "#NEW#" then + local contact = contacts[selected_idxs.contacts[name]] + if selected_idxs.contacts[name] ~= string.lower(fields.name) then -- name changed! if #fields.name == 0 then mail.show_edit_contact(name, contact.name, fields.note, "empty") @@ -458,7 +620,7 @@ function mail.handle_receivefields(player, formname, fields) return true else contacts[string.lower(fields.name)] = contact - contacts[selected_contact_idxs[name]] = nil + contacts[selected_idxs.contacts[name]] = nil end end contact.name = fields.name From 8beec340d3b6c025895962b19144a8d2c2894820 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 12 Aug 2020 11:51:01 +0200 Subject: [PATCH 10/14] revert renaming "sender" to "from" to maintain backwards compatibility --- api.lua | 2 +- gui.lua | 10 +++++----- test/auth.sqlite | Bin 24576 -> 24576 bytes util/normalize.lua | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/api.lua b/api.lua index 1b3c3a8..c480f7c 100644 --- a/api.lua +++ b/api.lua @@ -67,7 +67,7 @@ function mail.send(src, dst, subject, body) -- form the actual mail local msg = { unread = true, - from = m.from, + sender = m.from, to = m.to, subject = m.subject, body = m.body, diff --git a/gui.lua b/gui.lua index 0739d9d..3973b5e 100644 --- a/gui.lua +++ b/gui.lua @@ -60,7 +60,7 @@ function mail.show_inbox(name) end end formspec[#formspec + 1] = "," - formspec[#formspec + 1] = minetest.formspec_escape(message.from) + formspec[#formspec + 1] = minetest.formspec_escape(message.sender) formspec[#formspec + 1] = "," if message.subject ~= "" then if string.len(message.subject) > 30 then @@ -102,7 +102,7 @@ function mail.show_message(name, msgnumber) button[6,8.5;2,1;delete;Delete] ]] .. theme - local from = minetest.formspec_escape(message.from) + local from = minetest.formspec_escape(message.sender) local to = minetest.formspec_escape(message.to) local cc = minetest.formspec_escape(message.cc) local subject = minetest.formspec_escape(message.subject) @@ -144,7 +144,7 @@ end function mail.reply(name, message) mail.ensure_new_format(message) local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body - mail.show_compose(name, message.from, "Re: "..message.subject, replyfooter) + mail.show_compose(name, message.sender, "Re: "..message.subject, replyfooter) end function mail.replyall(name, message) @@ -152,8 +152,8 @@ function mail.replyall(name, message) local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body -- new recipients are the sender plus the original recipients, minus ourselves local recipients = message.to - if message.from ~= nil then - recipients = message.from .. ", " .. recipients + if message.sender ~= nil then + recipients = message.sender .. ", " .. recipients end recipients = mail.parse_player_list(recipients) for k,v in pairs(recipients) do diff --git a/test/auth.sqlite b/test/auth.sqlite index eaf402dc8dc47ebb62cc33f2928b4de31443851f..e3f882920a157151ebc4af34d92adacfd6ff78b0 100644 GIT binary patch delta 695 zcmX|*yKmEA6vh430X0e6uW5@iv_VK6GdOjUCK(ea&7*eXJe6>Lp_YdHn_HXm9;+sJr7`3N9Yr#=R?ge37@IY| z7VnrlrlM1Mj?5TRHrhFi5wT)Uh*rb^3abptY?CdT74(X1=0cHzDbk%pN>cNPz$)XSS#6T(Y7C8euUQojbu@02$Y{Ns z7ks_8FQYUoT(H3Q1p*Q3s3MmqMwM@QOZk{+q=pZ#iqR5OCR_^AMm}0J zg@_jNZl}Y$jGRP$d1MIQJw`4j`vHyU1tMLtEQ$JHr;=6_HHn0Bt7Fn!LKyjaRG*E8 zkyawvBwLnJVmc-nSrpKMTtm&V_c{k#>-_Y3<0-Hm_&@xdP98o1D+m7fFC7?$;qVfJ zf8iCpoVp%;fp4#f`8hv6y&_!85Aa1xa37vy@FP5jf8Y=J4Su#{CvbmZ83!J`C!>~H zYalzsfg9Jl105}_SzzuCtXg1h2AnIn-E(`Ncj0#T?CgMFt%wsGp1@D=JNyOD;Dr_O z3hpo3>>hAySXr_G54a0lcG`d&+$2Ky8*|v~ZZP}jvMM{R{Xa9$U6-|)=cdf+KTnI< Ai~s-t delta 214 zcmZoTz}Rqrae_1>+e8^>Mz)O!OZYjMc>Xf*)${z_*!YoW^Iw@dMwYmR26r~s%hfUp z09pIE7#J9s_}?+`|KNYOSrPn;m@z{A4KD9eGQv$^;bvhJXM`BT%*(oD*yi0Q>zsSO5S3 diff --git a/util/normalize.lua b/util/normalize.lua index f91859f..ec440d9 100644 --- a/util/normalize.lua +++ b/util/normalize.lua @@ -50,7 +50,6 @@ end function mail.ensure_new_format(message) if message.sender then - message.from = message.sender message.to = name end end From 39f47437b65bae470fdae1667c22b2103c2c75e7 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 12 Aug 2020 12:23:46 +0200 Subject: [PATCH 11/14] migration fixes --- gui.lua | 2 +- migrate.lua | 3 +-- util/normalize.lua | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gui.lua b/gui.lua index 20f0a30..796171d 100644 --- a/gui.lua +++ b/gui.lua @@ -322,7 +322,7 @@ function mail.replyall(name, message) mail.ensure_new_format(message) local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body -- new recipients are the sender plus the original recipients, minus ourselves - local recipients = message.to + local recipients = message.to or "" if message.sender ~= nil then recipients = message.sender .. ", " .. recipients end diff --git a/migrate.lua b/migrate.lua index 5780732..66dc6b2 100644 --- a/migrate.lua +++ b/migrate.lua @@ -29,8 +29,7 @@ end mail.migrate_contacts = function(playername) local file = io.open(mail.getContactsFile(playername), 'r') if not file then - file:close() -- file doesn't exist! This is a case for Migrate Man! - + -- file doesn't exist! This is a case for Migrate Man! local messages = mail.getMessages(playername) local contacts = {} diff --git a/util/normalize.lua b/util/normalize.lua index ec440d9..76b8fee 100644 --- a/util/normalize.lua +++ b/util/normalize.lua @@ -36,6 +36,7 @@ function mail.concat_player_list(order) end function mail.player_in_list(name, list) + list = list or {} if type(list) == "string" then list = mail.parse_player_list(list) end From 93d1af947c982853240f708abfa229fabd64ef20 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sat, 15 Aug 2020 13:52:26 +0200 Subject: [PATCH 12/14] always sort contacts alphabetically - introduce pairsByKeys() (from http://www.lua.org/pil/19.3.html but adding current index and total length as iterator return values) - use pairsByKeys() everywhere, where contacts are iterated over (for now only when displaying in gui.lua) so the order stays the same although the structure is a hashmap - correctly update selected position when adding/removing contacts to TO/CC/BCC fields - remove print statements in storage.lua --- gui.lua | 51 ++++++++++++++++++++++++++++++++------------------- storage.lua | 21 +++++++++++++++++++-- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/gui.lua b/gui.lua index 796171d..a9db281 100644 --- a/gui.lua +++ b/gui.lua @@ -181,9 +181,10 @@ function mail.compile_contact_list(name, selected, playernames) local formspec = {} local contacts = mail.getContacts(name) - local i = 0 if playernames == nil then - for k, contact in pairs(contacts) do + local length = 0 + for k, contact, i, l in pairsByKeys(contacts) do + if i == 1 then length = l end formspec[#formspec + 1] = "," formspec[#formspec + 1] = "," formspec[#formspec + 1] = minetest.formspec_escape(contact.name) @@ -195,15 +196,14 @@ function mail.compile_contact_list(name, selected, playernames) note = string.sub(note, 1, idx-1) .. ' ...' end formspec[#formspec + 1] = minetest.formspec_escape(note) - i = i + 1 if type(selected) == "string" then - if selected == k then + if string.lower(selected) == k then selected = i end end end - if i > 0 then - if selected then + if length > 0 then + if selected and type(selected) == "number" then formspec[#formspec + 1] = ";" formspec[#formspec + 1] = tostring(selected + 1) end @@ -215,7 +215,7 @@ function mail.compile_contact_list(name, selected, playernames) if type(playernames) == "string" then playernames = mail.parse_player_list(playernames) end - for k,c in pairs(playernames) do + for i,c in ipairs(playernames) do formspec[#formspec + 1] = "," formspec[#formspec + 1] = "," formspec[#formspec + 1] = minetest.formspec_escape(c) @@ -231,16 +231,15 @@ function mail.compile_contact_list(name, selected, playernames) end formspec[#formspec + 1] = minetest.formspec_escape(note) end - i = i + 1 if not selected then if type(selected) == "string" then - if k == selected then + if string.lower(selected) == string.lower(c) then selected = i end end end end - if i > 0 and selected then + if #playernames > 0 and selected and type(selected) == "number" then formspec[#formspec + 1] = ";" formspec[#formspec + 1] = tostring(selected + 1) end @@ -523,10 +522,8 @@ function mail.handle_receivefields(player, formname, fields) if fields[v.."add"] then update = true if selected_idxs.contacts[name] then - local i = 0 - for k, contact in pairs(contacts) do - i = i+1 - if i == selected_idxs.contacts[name] then + for k, contact, i in pairsByKeys(contacts) do + if k == selected_idxs.contacts[name] or i == selected_idxs.contacts[name] then local list = mail.parse_player_list(draft[v]) list[#list+1] = contact.name selected_idxs[v][name] = #list @@ -573,10 +570,7 @@ function mail.handle_receivefields(player, formname, fields) if fields.contacts then local evt = minetest.explode_table_event(fields.contacts) - --selected_idxs.contacts[name] = evt.row - 1 - local i = 0 - for k,c in pairs(contacts) do - i = i + 1 + for k,c,i in pairsByKeys(contacts) do if i == evt.row - 1 then selected_idxs.contacts[name] = k break @@ -593,7 +587,26 @@ function mail.handle_receivefields(player, formname, fields) mail.show_edit_contact(name, contacts[selected_idxs.contacts[name]].name, contacts[selected_idxs.contacts[name]].note) elseif fields.delete then if contacts[selected_idxs.contacts[name]] then - contacts[selected_idxs.contacts[name]] = nil + -- delete the contact and set the selected to the next in the list, except if it was the last. Then determine the new last + local found = false + local last = nil + for k,v,i in pairsByKeys(contacts) do + if found then + selected_idxs.contacts[name] = k + break + elseif k == selected_idxs.contacts[name] then + contacts[selected_idxs.contacts[name]] = nil + selected_idxs.contacts[name] = nil + found = true + else + last = k + end + end + if found and not selected_idxs.contacts[name] then + -- was the last in the list, so take the previous (new last) + selected_idxs.contacts[name] = last + end + mail.setContacts(name, contacts) end diff --git a/storage.lua b/storage.lua index 671242b..0aaff28 100644 --- a/storage.lua +++ b/storage.lua @@ -36,6 +36,25 @@ mail.getContacts = function(playername) return mail.read_json_file(mail.getContactsFile(playername)) end +function pairsByKeys(t, f) + -- http://www.lua.org/pil/19.3.html + local a = {} + for n in pairs(t) do table.insert(a, n) end + table.sort(a, f) + local i = 0 -- iterator variable + local iter = function() -- iterator function + i = i + 1 + if a[i] == nil then + return nil + else + --return a[i], t[a[i]] + -- add the current position and the length for convenience + return a[i], t[a[i]], i, #a + end + end + return iter +end + mail.setContacts = function(playername, contacts) if mail.write_json_file(mail.getContactsFile(playername), contacts) then return true @@ -51,7 +70,6 @@ function mail.read_json_file(path) local content = {} if file then local json = file:read("*a") - print(string.format('read from %s: %s', path, json)) content = minetest.parse_json(json or "[]") or {} file:close() end @@ -61,7 +79,6 @@ end function mail.write_json_file(path, content) local file = io.open(path,"w") local json = minetest.write_json(content) - print(string.format('writing to %s: %s', path, json)) if file and file:write(json) and file:close() then return true else From 0d937711aa27e99b9565436458f4b997333f3f49 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sat, 15 Aug 2020 14:44:25 +0200 Subject: [PATCH 13/14] fix mail.ensure_new_format() (had lead to wrong TO field when using Reply All), remove player from CC when using Reply All --- gui.lua | 16 ++++++++++++++-- util/normalize.lua | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/gui.lua b/gui.lua index a9db281..2d14bc6 100644 --- a/gui.lua +++ b/gui.lua @@ -79,7 +79,7 @@ function mail.show_inbox(name) if messages[1] then for _, message in ipairs(messages) do - mail.ensure_new_format(message) + mail.ensure_new_format(message, name) if message.unread then if not mail.player_in_list(name, message.to) then formspec[#formspec + 1] = ",#FFD788" @@ -320,6 +320,7 @@ end function mail.replyall(name, message) mail.ensure_new_format(message) local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body + -- new recipients are the sender plus the original recipients, minus ourselves local recipients = message.to or "" if message.sender ~= nil then @@ -333,7 +334,18 @@ function mail.replyall(name, message) end end recipients = mail.concat_player_list(recipients) - mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, message.cc) + + -- new CC is old CC minus ourselves + local cc = mail.parse_player_list(message.cc) + for k,v in pairs(cc) do + if v == name then + table.remove(cc, k) + break + end + end + cc = mail.concat_player_list(cc) + + mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, cc) end function mail.forward(name, message) diff --git a/util/normalize.lua b/util/normalize.lua index 76b8fee..961c1bc 100644 --- a/util/normalize.lua +++ b/util/normalize.lua @@ -49,8 +49,8 @@ function mail.player_in_list(name, list) end -function mail.ensure_new_format(message) - if message.sender then +function mail.ensure_new_format(message, name) + if message.to == nil then message.to = name end end From f35b62649b8ecc2de658d508184cf4a24c502a50 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sat, 15 Aug 2020 14:53:59 +0200 Subject: [PATCH 14/14] recipient selection: indicate what is TO and what is CC --- gui.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui.lua b/gui.lua index 2d14bc6..576a1d5 100644 --- a/gui.lua +++ b/gui.lua @@ -47,9 +47,9 @@ mail.select_contact_formspec = "size[8,9;]" .. theme .. [[ button[3.55,6.00;1.75,0.5;ccadd;→ Add] button[3.55,6.75;1.75,0.5;ccremove;← Remove] tablecolumns[color;text;text] - table[5.15,0.0;2.75,4.5;to;#999,Name,Note%s] + table[5.15,0.0;2.75,4.5;to;#999,TO:,Note%s] tablecolumns[color;text;text] - table[5.15,4.6;2.75,4.5;cc;#999,Name,Note%s] + table[5.15,4.6;2.75,4.5;cc;#999,CC:,Note%s] button[3.55,8.25;1.75,0.5;back;Back] ]]