0.1.155: Mail: Lesefenster (Linksklick), Loeschen, Archiv mit Suche, Aufraeumen nach Alle oeffnen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
Claude
2026-10-28 22:43:09 +01:00
parent 6797506a5f
commit 7138eaf572
5 changed files with 677 additions and 23 deletions
+2 -1
View File
@@ -3,10 +3,11 @@
## Notes: Own mailbox window: open all or selected mails safely, recent recipients, mailing lists with automatic sending, multi-item mails, gold surplus and cash on delivery.
## Notes-deDE: Eigenes Briefkasten-Fenster: alles oder Ausgewähltes sicher öffnen, letzte Empfänger, Mailinglisten mit Automatik, mehrere Items, Gold-Überschuss und Nachnahme.
## Author: Lacustat
## Version: 0.1.154
## Version: 0.1.155
## Dependencies: Freewood-OctoWoW-Core
## OptionalDeps: Freewood-OctoWoW-Creator, Freewood-OctoWoW-Brett
Locales.lua
Mail.lua
MailUI.lua
MailArchive.lua
+76 -2
View File
@@ -27,7 +27,7 @@ FW:AddLocale("enUS", {
MAIL_ROW_LETTER = "Letter only (not touched)",
MAIL_ROW_DAYS = "%d d",
MAIL_ROW_FROM = "From %s",
MAIL_NOTE_SAFE = "Mails are never deleted or returned. Cash-on-delivery mails and plain letters are skipped. One bag slot always stays free.",
MAIL_NOTE_SAFE = "Cash-on-delivery mails are never taken. One bag slot always stays free. Emptied mails are removed and kept in the archive.",
MAIL_RUN_DONE = "Mailbox: %d gold mails and %d items taken%s.",
MAIL_RUN_SKIP_FULL = ", %d items left because the bags are nearly full",
MAIL_RUN_STOPPED = "Mailbox: stopped. %d gold mails and %d items taken.",
@@ -103,6 +103,43 @@ FW:AddLocale("enUS", {
MAIL_CMD_ON = "Freewood mailbox window: on",
MAIL_CMD_OFF = "Freewood mailbox window: off",
MAIL_CMD_CLOSED = "Open a mailbox first.",
MAIL_BTN_ARCHIVE = "Archive",
MAIL_DETAIL_EMPTY = "Click a mail to read it.",
MAIL_DETAIL_FROM = "From %s",
MAIL_DETAIL_NOTEXT = "(no text)",
MAIL_ATTACH_ITEM = "Item: %s",
MAIL_ATTACH_GOLD = "Gold: %s",
MAIL_ATTACH_COD = "Cash on delivery: %s",
MAIL_ATTACH_NONE = "Nothing attached",
MAIL_TAKE_GOLD = "Take gold",
MAIL_TAKE_ITEM = "Take item",
MAIL_TAKE_COD = "Pay %s and take",
MAIL_TAKE_COD_CONFIRM = "Really pay %s? Click again",
MAIL_REPLY = "Reply",
MAIL_DELETE = "Delete",
MAIL_DELETE_TIP = "Moves the mail to the archive and deletes it from the mailbox. Only possible when nothing is attached (take items and gold first).",
MAIL_ERR_codbad = "Blocked: the cash on delivery is higher than agreed (board check).",
MAIL_ERR_codmoney = "Not enough gold for the cash on delivery.",
MAIL_ERR_full = "Bags nearly full (one slot stays free).",
MAIL_ERR_busy = "Another action is running.",
MAIL_ERR_data = "This mail carries Freewood data. It stays until you have handled it.",
MAIL_ERR_notempty = "Take the attached items and gold first.",
MAIL_RUN_CLEANED = "Mailbox: %d emptied mails removed, %d of them kept in the archive.",
MAIL_ARCH_TITLE = "Mail archive",
MAIL_ARCH_SEARCH = "Search subject or sender",
MAIL_ARCH_EMPTY = "Nothing in the archive.",
MAIL_ARCH_COUNT = "%d of %d entries",
MAIL_ARCH_DELETE = "Delete entry",
MAIL_ARCH_CLEAR = "Clear archive",
MAIL_ARCH_CLEAR_CONFIRM = "Really clear? Click again",
MAIL_ARCH_TAKEN = "Taken: %s",
MAIL_ARCH_CHAR = "Received by %s",
MAIL_ARCH_NOSUBJECT = "(no subject)",
MAIL_SET_CLEANUP = "After \"Open all\": remove emptied mails (they are kept in the archive first)",
MAIL_SET_ARCHIVE_DAYS = "Delete archive entries after",
MAIL_SET_ARCHIVE_OFF = "never",
MAIL_SET_ARCHIVE_N = "%d days",
MAIL_SET_ARCHIVE_BTN = "Open archive",
})
FW:AddLocale("deDE", {
@@ -126,7 +163,7 @@ FW:AddLocale("deDE", {
MAIL_ROW_LETTER = "Nur ein Brief (bleibt unberührt)",
MAIL_ROW_DAYS = "%d T",
MAIL_ROW_FROM = "Von %s",
MAIL_NOTE_SAFE = "Briefe werden nie gelöscht oder zurückgeschickt. Nachnahme-Briefe und reine Briefe werden übersprungen. Ein Taschenplatz bleibt immer frei.",
MAIL_NOTE_SAFE = "Nachnahme-Briefe werden nie genommen. Ein Taschenplatz bleibt immer frei. Geleerte Briefe werden entfernt und im Archiv gesichert.",
MAIL_RUN_DONE = "Briefkasten: %d Gold-Briefe und %d Items genommen%s.",
MAIL_RUN_SKIP_FULL = ", %d Items blieben liegen, weil die Taschen fast voll sind",
MAIL_RUN_STOPPED = "Briefkasten: gestoppt. %d Gold-Briefe und %d Items genommen.",
@@ -202,4 +239,41 @@ FW:AddLocale("deDE", {
MAIL_CMD_ON = "Freewood-Briefkasten-Fenster: an",
MAIL_CMD_OFF = "Freewood-Briefkasten-Fenster: aus",
MAIL_CMD_CLOSED = "Öffne zuerst einen Briefkasten.",
MAIL_BTN_ARCHIVE = "Archiv",
MAIL_DETAIL_EMPTY = "Klicke einen Brief an, um ihn zu lesen.",
MAIL_DETAIL_FROM = "Von %s",
MAIL_DETAIL_NOTEXT = "(kein Text)",
MAIL_ATTACH_ITEM = "Item: %s",
MAIL_ATTACH_GOLD = "Gold: %s",
MAIL_ATTACH_COD = "Nachnahme: %s",
MAIL_ATTACH_NONE = "Nichts angehängt",
MAIL_TAKE_GOLD = "Gold nehmen",
MAIL_TAKE_ITEM = "Item nehmen",
MAIL_TAKE_COD = "%s zahlen und nehmen",
MAIL_TAKE_COD_CONFIRM = "%s wirklich zahlen? Nochmal klicken",
MAIL_REPLY = "Antworten",
MAIL_DELETE = "Löschen",
MAIL_DELETE_TIP = "Legt den Brief ins Archiv und löscht ihn aus dem Briefkasten. Nur möglich, wenn nichts mehr angehängt ist (erst Items und Gold nehmen).",
MAIL_ERR_codbad = "Gesperrt: Die Nachnahme ist höher als vereinbart (Brett-Prüfung).",
MAIL_ERR_codmoney = "Nicht genug Gold für die Nachnahme.",
MAIL_ERR_full = "Taschen fast voll (ein Platz bleibt frei).",
MAIL_ERR_busy = "Es läuft gerade eine andere Aktion.",
MAIL_ERR_data = "Dieser Brief enthält Freewood-Daten. Er bleibt, bis du ihn bearbeitet hast.",
MAIL_ERR_notempty = "Erst die angehängten Items und das Gold nehmen.",
MAIL_RUN_CLEANED = "Briefkasten: %d geleerte Briefe entfernt, %d davon im Archiv gesichert.",
MAIL_ARCH_TITLE = "Brief-Archiv",
MAIL_ARCH_SEARCH = "Betreff oder Absender suchen",
MAIL_ARCH_EMPTY = "Das Archiv ist leer.",
MAIL_ARCH_COUNT = "%d von %d Einträgen",
MAIL_ARCH_DELETE = "Eintrag löschen",
MAIL_ARCH_CLEAR = "Archiv leeren",
MAIL_ARCH_CLEAR_CONFIRM = "Wirklich leeren? Nochmal klicken",
MAIL_ARCH_TAKEN = "Genommen: %s",
MAIL_ARCH_CHAR = "Empfangen von %s",
MAIL_ARCH_NOSUBJECT = "(ohne Betreff)",
MAIL_SET_CLEANUP = "Nach \"Alle öffnen\": geleerte Briefe entfernen (sie kommen vorher ins Archiv)",
MAIL_SET_ARCHIVE_DAYS = "Archiv-Einträge löschen nach",
MAIL_SET_ARCHIVE_OFF = "nie",
MAIL_SET_ARCHIVE_N = "%d Tagen",
MAIL_SET_ARCHIVE_BTN = "Archiv öffnen",
})
+170 -5
View File
@@ -51,9 +51,11 @@ local Mail = FW:NewModule("Mail", {
title = L.MAIL_TITLE,
description = L.MAIL_DESC,
defaults = {
settings = { replace = false, chat = true },
settings = { replace = false, chat = true, cleanup = true, archiveDays = 0 },
recipients = {},
lists = {},
archive = {}, -- entfernte Briefe, neueste zuerst
sentSubjects = {}, -- Betreffe, die wir selbst ohne Text gesendet haben (Betreff -> Zeit)
},
})
NS.Mail = Mail
@@ -258,6 +260,141 @@ function Mail.ReadInbox()
return list
end
------------------------------------------------------------------------
-- Brieftext, Archiv, einzelne Briefe
------------------------------------------------------------------------
local MAX_ARCHIVE = 500
Mail.taken = {} -- "Absender|Betreff" -> { money, items = { "Name xN" } }: was aus dem Brief genommen wurde
local function Pair(m) return tostring(m.sender) .. "|" .. tostring(m.subject) end
Mail.Pair = Pair
-- Text eines Briefs (markiert ihn als gelesen). Rueckgabe: Text, istRechnung
function Mail.ReadText(i)
if not GetInboxText then return "", false end
local ok, body, _, _, invoice = pcall(GetInboxText, i)
if not ok then return "", false end
body = type(body) == "string" and body or ""
if invoice and GetInboxInvoiceInfo then
local ok2, kind, item, who, bid = pcall(GetInboxInvoiceInfo, i)
if ok2 and item then
local parts = { tostring(kind or "invoice"), tostring(item) }
if who then parts[#parts + 1] = tostring(who) end
if bid then parts[#parts + 1] = Mail.Money(bid) end
body = (body ~= "" and (body .. "\n") or "") .. table.concat(parts, " - ")
end
end
return body, invoice and true or false
end
-- Nichts mehr angehaengt, kein Gold, keine Nachnahme: dann gehen beim Loeschen keine Werte verloren
function Mail.Deletable(m)
return m ~= nil and not m.hasItem and m.money == 0 and m.cod == 0
end
local function NoteTaken(m, what)
local p = Pair(m)
local t = Mail.taken[p] or { money = 0, items = {} }
Mail.taken[p] = t
if what == "money" then
t.money = t.money + m.money
else
t.items[#t.items + 1] = (m.itemName or "?") .. ((m.itemCount or 1) > 1 and (" x" .. m.itemCount) or "")
end
end
-- Eigene Briefe ohne Text (Betreff steht alles): werden ohne Archiv entfernt
function Mail:NoteSent(subject, body)
if (body or "") ~= "" or not subject or subject == "" then return end
local t = self.db.sentSubjects
local now = time()
t[subject] = now
local n = 0
for k, v in pairs(t) do
n = n + 1
if now - (tonumber(v) or 0) > 30 * 86400 then t[k] = nil end
end
end
function Mail:IsOwnSent(subject, body)
return (body or "") == "" and self.db.sentSubjects[subject] ~= nil
end
function Mail:AddArchive(m, body)
local info = Mail.taken[Pair(m)]
local e = {
t = time(), sender = m.sender, subject = m.subject, body = body or "",
money = info and info.money or 0, items = info and info.items or {},
char = UnitName and UnitName("player") or "?",
}
table.insert(self.db.archive, 1, e)
while #self.db.archive > MAX_ARCHIVE do table.remove(self.db.archive) end
FW:Fire("MAIL_ARCHIVE_CHANGED")
end
-- Archiv-Eintraege nach der eingestellten Zeit loeschen (0 = nie)
function Mail:Purge()
local days = tonumber(self.db.settings.archiveDays) or 0
if days <= 0 then return 0 end
local limit = time() - days * 86400
local a, n = self.db.archive, 0
for i = #a, 1, -1 do
if (a[i].t or 0) < limit then table.remove(a, i) n = n + 1 end
end
if n > 0 then FW:Fire("MAIL_ARCHIVE_CHANGED") end
return n
end
-- Leeren Brief entfernen: erst ins Archiv (ausser unseren eigenen Textlosen), dann loeschen.
-- Rueckgabe: true, "archived"|"own" oder false, Grund
function Mail:RemoveMail(m)
if not Mail.Deletable(m) then return false, "notempty" end
local body = Mail.ReadText(m.i)
if body:find("FW%d+:[a-z0-9]+:%x+:") then return false, "data" end -- Freewood-Daten: bleiben fuer die Annahme
local own = self:IsOwnSent(m.subject, body)
if not own then self:AddArchive(m, body) end
pcall(DeleteInboxItem, m.i)
Mail.taken[Pair(m)] = nil
return true, own and "own" or "archived"
end
function Mail:FindMail(key, pair)
local list = Mail.ReadInbox()
for _, m in ipairs(list) do
if m.key == key then return m end
end
if pair then
for _, m in ipairs(list) do
if Pair(m) == pair then return m end
end
end
end
-- Inhalt eines einzelnen Briefs nehmen (erst Gold, dann Item). Nachnahme zahlt der Spieler mit einem zweiten Klick (UI).
function Mail:TakeOne(m)
if self.busy then return false, "busy" end
if m.cod > 0 then
local B = FW.Brett
if B and B.InspectMail then
local ok, st = pcall(B.InspectMail, m.i)
if ok and (st == "high" or st == "nodeal") then return false, "codbad" end
end
if ((GetMoney and GetMoney()) or 0) < m.cod then return false, "codmoney" end
end
if m.money > 0 and m.cod == 0 then
NoteTaken(m, "money")
pcall(TakeInboxMoney, m.i)
return true
elseif m.hasItem then
if Mail.FreeSlots() <= KEEP_FREE then return false, "full" end
NoteTaken(m, "item")
pcall(TakeInboxItem, m.i)
return true
end
return false, "empty"
end
Mail.selected = {} -- key -> true
function Mail:SelectedCount()
@@ -291,7 +428,7 @@ function Mail:StartOpen(onlySelected)
for k in pairs(self.selected) do only[k] = true end
if next(only) == nil then return false end
end
self.run = { only = only, tries = {}, money = 0, items = 0, pending = nil }
self.run = { only = only, tries = {}, money = 0, items = 0, pending = nil, removed = 0, archived = 0, touched = {} }
self.busy = "inbox"
self.driver:Show()
Trace("Posteingang: Start" .. (only and " (nur Ausgewaehlte)" or " (alle)"))
@@ -315,7 +452,8 @@ local function FinishOpen(self, reason)
else
Say(string.format(FW.L.MAIL_RUN_DONE, run.money, run.items, skipped > 0 and string.format(FW.L.MAIL_RUN_SKIP_FULL, skipped) or ""))
end
self.lastRun = { money = run.money, items = run.items, skipped = skipped }
if run.removed > 0 then Say(string.format(FW.L.MAIL_RUN_CLEANED, run.removed, run.archived)) end
self.lastRun = { money = run.money, items = run.items, skipped = skipped, removed = run.removed }
self.selected = {}
FW:Fire("MAIL_STATE")
FW:Fire("MAIL_SELECTION_CHANGED")
@@ -344,6 +482,8 @@ function Mail:StepOpen()
if m.money > 0 then
run.tries[m.sig] = (run.tries[m.sig] or 0) + 1
run.money = run.money + 1
run.touched[Pair(m)] = true
NoteTaken(m, "money")
run.pending, run.updated = now, false
pcall(TakeInboxMoney, m.i)
Trace(string.format("Brief %d von %s: Gold %d genommen", m.i, m.sender, m.money))
@@ -355,6 +495,8 @@ function Mail:StepOpen()
else
run.tries[m.sig] = (run.tries[m.sig] or 0) + 1
run.items = run.items + 1
run.touched[Pair(m)] = true
NoteTaken(m, "item")
run.pending, run.updated = now, false
pcall(TakeInboxItem, m.i)
Trace(string.format("Brief %d von %s: Item %s genommen (frei vorher %d)", m.i, m.sender, tostring(m.itemName), free))
@@ -364,6 +506,24 @@ function Mail:StepOpen()
end
end
end
-- nichts mehr zu nehmen: leere Briefe entfernen (mit Text und fremde kommen vorher ins Archiv)
if self.db.settings.cleanup then
for idx = #list, 1, -1 do
local m = list[idx]
local key = "del|" .. m.sig
if Mail.Deletable(m) and (not run.only or run.touched[Pair(m)]) and (run.tries[key] or 0) < 2 then
run.tries[key] = (run.tries[key] or 0) + 1
local ok, how = self:RemoveMail(m)
if ok then
run.removed = run.removed + 1
if how == "archived" then run.archived = run.archived + 1 end
run.pending, run.updated = now, false
FW:Fire("MAIL_STATE")
return
end
end
end
end
FinishOpen(self, "done")
end
@@ -699,6 +859,7 @@ end
------------------------------------------------------------------------
function Mail:OnMailShow()
pcall(self.Purge, self)
self.open = true
self.gen = self.gen + 1
self.selected = {}
@@ -770,8 +931,11 @@ function Mail:OnEnable()
self.hooked = true
-- Empfaenger merken, egal wer sendet (auch das normale Fenster und Brett-Auftraege)
if hooksecurefunc and _G.SendMail then
pcall(hooksecurefunc, "SendMail", function(to)
if Mail.db then Mail.lastTo = to end
pcall(hooksecurefunc, "SendMail", function(to, subject, body)
if Mail.db then
Mail.lastTo = to
pcall(Mail.NoteSent, Mail, subject, body)
end
end)
end
if _G.MailFrame and MailFrame.HookScript then
@@ -780,6 +944,7 @@ function Mail:OnEnable()
pcall(MailFrame.HookScript, MailFrame, "OnHide", function() pcall(Mail.OnMailClosed, Mail) end)
end
end
After(3, function() pcall(Mail.Purge, Mail) end)
-- Briefkasten schon offen (z. B. nach /reload)?
if _G.MailFrame and MailFrame.IsShown and MailFrame:IsShown() then
After(0.5, function() pcall(Mail.OnMailShow, Mail) end)
+229
View File
@@ -0,0 +1,229 @@
-- Freewood-OctoWoW-Mail: Archiv der entfernten Briefe (eigenes Fenster mit Suche nach Betreff und Absender).
-- Geoeffnet ueber den Knopf "Archiv" im Briefkasten-Fenster, in den Einstellungen oder mit /fwmail archive.
-- Eintraege: { t, sender, subject, body, money, items, char }. Loeschen nach X Tagen: Einstellung (Standard: nie).
local ADDON, NS = ...
local FW = _G.Freewood
local Mail = NS.Mail
if not FW or not Mail then
return
end
local UI = FW.UI
local W, H = 720, 500
local LIST_W = 290
local ROW_H, ROW_GAP = 40, 4
local win
local rows = {}
local filter = ""
local selected -- gewaehlter Eintrag (Tabelle)
local clearConfirm = false
local function T(key) return FW.L[key] or key end
local function Stamp(t) return date and date("%d.%m.%y %H:%M", t or 0) or "" end
local function Matches(e, q)
if q == "" then return true end
q = q:lower()
return (tostring(e.subject or ""):lower():find(q, 1, true) or tostring(e.sender or ""):lower():find(q, 1, true)) and true or false
end
local function Build()
local f = UI.CreatePanel(UIParent, "bgDeep", "gold")
f:SetSize(W, H)
f:SetFrameStrata("DIALOG")
f:SetClampedToScreen(true)
f:EnableMouse(true)
f:SetMovable(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function(self) self:StartMoving() end)
f:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local left, top = self:GetLeft(), self:GetTop()
if left and top then Mail.db.settings.archivePos = { x = left, y = top } end
end)
local pos = Mail.db.settings.archivePos
f:ClearAllPoints()
if pos and pos.x and pos.y then
f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y)
else
f:SetPoint("CENTER", UIParent, "CENTER", 60, 0)
end
local title = UI.CreateLabel(f, T("MAIL_ARCH_TITLE"), { font = "heading", size = 16, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -12)
local close = UI.CreateButton(f, "X", 26, 24, function() f:Hide() end, "danger")
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -10)
local div = UI.CreateDivider(f)
div:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -40)
div:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -40)
-- Suche
local box = UI.CreatePanel(f, "bgDeep", "bronze")
box:SetSize(LIST_W, 26)
box:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -52)
local edit = CreateFrame("EditBox", nil, box)
edit:SetAutoFocus(false)
edit:SetPoint("TOPLEFT", box, "TOPLEFT", 8, -2)
edit:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", -8, 2)
edit:SetMaxLetters(60)
if not edit:SetFont(UI.GetFontPath("body"), 12, "") then edit:SetFont("Fonts\\FRIZQT__.TTF", 12, "") end
edit:SetTextColor(UI.Color("text"))
edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
edit:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
local hint = UI.CreateLabel(box, T("MAIL_ARCH_SEARCH"), { size = 12, color = "textDim" })
hint:SetPoint("LEFT", box, "LEFT", 10, 0)
edit:SetScript("OnTextChanged", function(self)
filter = self:GetText() or ""
if filter == "" then hint:Show() else hint:Hide() end
if f.Refresh then f.Refresh() end
end)
f.count = UI.CreateLabel(f, "", { size = 11, color = "textDim" })
f.count:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -84)
-- Liste
f.scroll = UI.CreateScrollFrame(f)
f.scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -102)
f.scroll:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 16, 16)
f.scroll:SetWidth(LIST_W)
f.empty = UI.CreateLabel(f.scroll.child, T("MAIL_ARCH_EMPTY"), { size = 12, color = "textDim" })
f.empty:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 4, -6)
-- Lesefenster
local d = UI.CreatePanel(f, "card", "bronze")
d:SetPoint("TOPLEFT", f, "TOPLEFT", LIST_W + 28, -52)
d:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 16)
d.subj = UI.CreateLabel(d, "", { font = "heading", size = 14, color = "goldLight" })
d.subj:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -10)
d.subj:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.subj:SetWordWrap(false)
d.meta = UI.CreateLabel(d, "", { size = 11, color = "textDim" })
d.meta:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -32)
d.meta:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.meta:SetWordWrap(false)
d.taken = UI.CreateLabel(d, "", { size = 12 })
d.taken:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -52)
d.taken:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.taken:SetWordWrap(false)
d.bodyScroll = UI.CreateScrollFrame(d)
d.bodyScroll:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -76)
d.bodyScroll:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", -12, 50)
d.body = UI.CreateLabel(d.bodyScroll.child, "", { size = 12, justifyV = "TOP" })
d.body:SetPoint("TOPLEFT", d.bodyScroll.child, "TOPLEFT", 2, -2)
d.del = UI.CreateButton(d, T("MAIL_ARCH_DELETE"), 130, 26, function()
if not selected then return end
local a = Mail.db.archive
for i, e in ipairs(a) do
if e == selected then table.remove(a, i) break end
end
selected = nil
f.Refresh()
end, "danger")
d.del:SetPoint("BOTTOMLEFT", d, "BOTTOMLEFT", 12, 12)
d.clear = UI.CreateButton(d, T("MAIL_ARCH_CLEAR"), 210, 26, function()
if not clearConfirm then
clearConfirm = true
f.Refresh()
return
end
clearConfirm = false
for i = #Mail.db.archive, 1, -1 do table.remove(Mail.db.archive, i) end
selected = nil
f.Refresh()
end, "default")
d.clear:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", -12, 12)
local function Row(i)
local row = rows[i]
if row then return row end
row = UI.CreatePanel(f.scroll.child, "card", "bronze")
row:SetHeight(ROW_H)
row:EnableMouse(true)
row.l1 = UI.CreateLabel(row, "", { size = 12 })
row.l1:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -6)
row.l1:SetPoint("RIGHT", row, "RIGHT", -10, 0)
row.l1:SetWordWrap(false)
row.l2 = UI.CreateLabel(row, "", { size = 11, color = "textDim" })
row.l2:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -22)
row.l2:SetPoint("RIGHT", row, "RIGHT", -10, 0)
row.l2:SetWordWrap(false)
row:SetScript("OnMouseUp", function()
selected = row.entry
clearConfirm = false
f.Refresh()
end)
rows[i] = row
return row
end
function f.Refresh()
local all = Mail.db.archive
local list = {}
for _, e in ipairs(all) do
if Matches(e, filter) then list[#list + 1] = e end
end
local w = math.max(200, (f.scroll:GetWidth() or 0) - 14)
for i, e in ipairs(list) do
local row = Row(i)
row.entry = e
row:ClearAllPoints()
row:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 0, -((i - 1) * (ROW_H + ROW_GAP)))
row:SetWidth(w)
row.l1:SetText(e.subject ~= "" and e.subject or T("MAIL_ARCH_NOSUBJECT"))
row.l2:SetText(tostring(e.sender or "?") .. " " .. Stamp(e.t))
UI.SetBorderColor(row, e == selected and "gold" or "bronze")
row:Show()
end
for i = #list + 1, #rows do rows[i].entry = nil rows[i]:Hide() end
f.empty:SetShown(#list == 0)
f.scroll:SetContentHeight(#list * (ROW_H + ROW_GAP) + 4)
f.count:SetText(string.format(T("MAIL_ARCH_COUNT"), #list, #all))
if selected then
local e = selected
d.subj:SetText(e.subject ~= "" and e.subject or T("MAIL_ARCH_NOSUBJECT"))
d.meta:SetText(string.format(T("MAIL_DETAIL_FROM"), tostring(e.sender or "?")) .. " " .. Stamp(e.t) ..
(e.char and (" " .. string.format(T("MAIL_ARCH_CHAR"), e.char)) or ""))
local parts = {}
if (e.money or 0) > 0 then parts[#parts + 1] = Mail.Money(e.money) end
for _, it in ipairs(e.items or {}) do parts[#parts + 1] = it end
d.taken:SetText(#parts > 0 and string.format(T("MAIL_ARCH_TAKEN"), table.concat(parts, ", ")) or "")
local text = e.body or ""
d.body:SetText(text ~= "" and text or "|cff8a8f9a" .. T("MAIL_DETAIL_NOTEXT") .. "|r")
local bw = math.max(200, (d.bodyScroll:GetWidth() or 0) - 22)
local h = UI.FitLabel(d.body, bw, 20)
d.bodyScroll:SetContentHeight(h + 8)
end
for _, wdg in ipairs({ d.subj, d.meta, d.taken, d.bodyScroll, d.del }) do wdg:SetShown(selected ~= nil) end
d.clear:SetLabel(clearConfirm and T("MAIL_ARCH_CLEAR_CONFIRM") or T("MAIL_ARCH_CLEAR"))
d.clear:SetButtonEnabled(#all > 0)
end
f:SetScript("OnShow", function() f.Refresh() end)
f:Hide()
return f
end
function Mail.OpenArchive(toggle)
if not win then
local ok, err = pcall(function() win = Build() end)
if not ok then
if FW.Print then FW:Print("Mail archive: " .. tostring(err)) end
return
end
end
if toggle and win:IsShown() then
win:Hide()
return
end
pcall(Mail.Purge, Mail)
win:Show()
win:Raise()
win.Refresh()
end
FW:On("MAIL_ARCHIVE_CHANGED", function()
if win and win:IsShown() then win.Refresh() end
end, {})
+200 -15
View File
@@ -20,6 +20,7 @@ local OWNER = {}
local window, content, footer, tabs
local current = "inbox"
local panes = {}
local SelectTab
local picks = {} -- Senden-Reiter: gewaehlte Staepel { id, count, key }
local picker
@@ -289,6 +290,7 @@ end
local function BuildInbox(parent)
local f = CreateFrame("Frame", nil, parent)
local rows = {}
local LIST_W = 300
f.info = UI.CreateLabel(f, "", { size = 12, color = "textDim" })
f.info:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -6)
@@ -313,10 +315,86 @@ local function BuildInbox(parent)
f.scroll = UI.CreateScrollFrame(f)
f.scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -70)
f.scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 36)
f.scroll:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 36)
f.scroll:SetWidth(LIST_W)
f.empty = UI.CreateLabel(f.scroll.child, T("MAIL_INBOX_EMPTY"), { size = 12, color = "textDim" })
f.empty:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 4, -6)
---------------- Lesefenster rechts ----------------
local d = UI.CreatePanel(f, "card", "bronze")
d:SetPoint("TOPLEFT", f, "TOPLEFT", LIST_W + 12, -70)
d:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 36)
d.empty = UI.CreateLabel(d, T("MAIL_DETAIL_EMPTY"), { size = 12, color = "textDim", justify = "CENTER" })
d.empty:SetPoint("CENTER", d, "CENTER", 0, 0)
d.subj = UI.CreateLabel(d, "", { font = "heading", size = 14, color = "goldLight" })
d.subj:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -10)
d.subj:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.subj:SetWordWrap(false)
d.meta = UI.CreateLabel(d, "", { size = 11, color = "textDim" })
d.meta:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -32)
d.meta:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.meta:SetWordWrap(false)
d.attach = UI.CreateLabel(d, "", { size = 12 })
d.attach:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -52)
d.attach:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.attach:SetWordWrap(false)
d.bodyScroll = UI.CreateScrollFrame(d)
d.bodyScroll:SetPoint("TOPLEFT", d, "TOPLEFT", 12, -76)
d.bodyScroll:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", -12, 68)
d.body = UI.CreateLabel(d.bodyScroll.child, "", { size = 12, justifyV = "TOP" })
d.body:SetPoint("TOPLEFT", d.bodyScroll.child, "TOPLEFT", 2, -2)
d.msg = UI.CreateLabel(d, "", { size = 11, color = "danger" })
d.msg:SetPoint("BOTTOMLEFT", d, "BOTTOMLEFT", 12, 44)
d.msg:SetPoint("RIGHT", d, "RIGHT", -12, 0)
d.msg:SetWordWrap(false)
d.take = UI.CreateButton(d, T("MAIL_TAKE_ITEM"), 120, 26, nil, "primary")
d.take:SetPoint("BOTTOMLEFT", d, "BOTTOMLEFT", 12, 12)
d.reply = UI.CreateButton(d, T("MAIL_REPLY"), 80, 26, nil, "default")
d.reply:SetPoint("LEFT", d.take, "RIGHT", 6, 0)
d.delete = UI.CreateButton(d, T("MAIL_DELETE"), 80, 26, nil, "danger")
d.delete:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", -12, 12)
UI.AttachTooltip(d.delete, function() return T("MAIL_DELETE") end, function() return T("MAIL_DELETE_TIP") end)
f.detail = d
local function ShowError(why)
d.msg:SetText(why and (FW.L["MAIL_ERR_" .. why] or why) or "")
end
d.take:SetScript("OnClick", function()
local m = f.current
if not m then return end
if m.cod > 0 and f.codConfirm ~= m.key then
f.codConfirm = m.key
f.Refresh()
return
end
f.codConfirm = nil
local ok, why = Mail:TakeOne(m)
ShowError(not ok and why or nil)
f.Refresh()
end)
d.delete:SetScript("OnClick", function()
local m = f.current
if not m then return end
local ok, why = Mail:RemoveMail(m)
if ok then
Mail.view, f.current, f.shownKey = nil, nil, nil
ShowError(nil)
else
ShowError(why)
end
f.Refresh()
end)
d.reply:SetScript("OnClick", function()
local m = f.current
if m and panes.send and panes.send.Prefill then
local subj = m.subject or ""
if subj:sub(1, 3):lower() ~= "re:" then subj = "Re: " .. subj end
SelectTab("send")
panes.send.Prefill(m.sender, subj)
end
end)
local function Row(i)
local row = rows[i]
if row then return row end
@@ -325,38 +403,100 @@ local function BuildInbox(parent)
row.cb = UI.CreateCheckbox(row, "", function() return row.mail and Mail.selected[row.mail.key] end, function(v)
if row.mail then Mail:ToggleSelect(row.mail.key, v) end
end)
row.cb:SetPoint("LEFT", row, "LEFT", 8, 0)
row.cb:SetPoint("LEFT", row, "LEFT", 6, 0)
row.cb:SetWidth(22)
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetSize(28, 28)
row.icon:SetPoint("LEFT", row, "LEFT", 38, 0)
row.icon:SetPoint("LEFT", row, "LEFT", 32, 0)
row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
row.l1 = UI.CreateLabel(row, "", { size = 12 })
row.l1:SetPoint("TOPLEFT", row, "TOPLEFT", 76, -6)
row.l1:SetPoint("RIGHT", row, "RIGHT", -60, 0)
row.l1:SetPoint("TOPLEFT", row, "TOPLEFT", 66, -6)
row.l1:SetPoint("RIGHT", row, "RIGHT", -40, 0)
row.l1:SetWordWrap(false)
row.l2 = UI.CreateLabel(row, "", { size = 11, color = "textDim" })
row.l2:SetPoint("TOPLEFT", row, "TOPLEFT", 76, -22)
row.l2:SetPoint("RIGHT", row, "RIGHT", -60, 0)
row.l2:SetPoint("TOPLEFT", row, "TOPLEFT", 66, -22)
row.l2:SetPoint("RIGHT", row, "RIGHT", -40, 0)
row.l2:SetWordWrap(false)
row.days = UI.CreateLabel(row, "", { size = 11, color = "textDim", justify = "RIGHT" })
row.days:SetPoint("RIGHT", row, "RIGHT", -10, 0)
row.days:SetPoint("RIGHT", row, "RIGHT", -8, 0)
Tooltip(row)
row:SetScript("OnMouseUp", function()
if row.mail then
Mail.view = { key = row.mail.key, pair = Mail.Pair(row.mail) }
f.codConfirm = nil
ShowError(nil)
f.Refresh()
end
end)
rows[i] = row
return row
end
local function RefreshDetail(list)
local view = Mail.view
local cur
if view then
for _, m in ipairs(list) do
if m.key == view.key then cur = m break end
end
if not cur then
for _, m in ipairs(list) do
if Mail.Pair(m) == view.pair then cur = m break end
end
end
end
f.current = cur
d.empty:SetShown(cur == nil)
for _, w in ipairs({ d.subj, d.meta, d.attach, d.bodyScroll, d.msg, d.take, d.reply, d.delete }) do
w:SetShown(cur ~= nil)
end
if not cur then f.shownKey = nil return end
view.key = cur.key
if f.shownKey ~= cur.key then
f.shownKey = cur.key
f.bodyText = Mail.ReadText(cur.i)
if FW.Brett and FW.Brett.CheckMailData then pcall(FW.Brett.CheckMailData, cur.i) end
end
d.subj:SetText(cur.subject ~= "" and cur.subject or T("MAIL_ARCH_NOSUBJECT"))
d.meta:SetText(string.format(T("MAIL_DETAIL_FROM"), cur.sender) .. (cur.days and (" " .. string.format(T("MAIL_ROW_DAYS"), math.floor(cur.days))) or ""))
local parts = {}
if cur.hasItem then parts[#parts + 1] = string.format(T("MAIL_ATTACH_ITEM"), (cur.itemName or "?") .. ((cur.itemCount or 1) > 1 and (" x" .. cur.itemCount) or "")) end
if cur.money > 0 then parts[#parts + 1] = string.format(T("MAIL_ATTACH_GOLD"), Mail.Money(cur.money)) end
local attach = #parts > 0 and table.concat(parts, " ") or T("MAIL_ATTACH_NONE")
if cur.cod > 0 then attach = attach .. " |cffff7060" .. string.format(T("MAIL_ATTACH_COD"), Mail.Money(cur.cod)) .. "|r" end
d.attach:SetText(attach)
local text = f.bodyText or ""
d.body:SetText(text ~= "" and text or "|cff8a8f9a" .. T("MAIL_DETAIL_NOTEXT") .. "|r")
local bw = math.max(200, (d.bodyScroll:GetWidth() or 0) - 22)
local h = UI.FitLabel(d.body, bw, 20)
d.bodyScroll:SetContentHeight(h + 8)
local busy = Mail.busy ~= nil
local canTake = (cur.money > 0 and cur.cod == 0) or cur.hasItem
if cur.cod > 0 then
d.take:SetLabel(f.codConfirm == cur.key and string.format(T("MAIL_TAKE_COD_CONFIRM"), Mail.Money(cur.cod)) or string.format(T("MAIL_TAKE_COD"), Mail.Money(cur.cod)))
d.take:SetWidth(210)
else
d.take:SetLabel(cur.money > 0 and T("MAIL_TAKE_GOLD") or T("MAIL_TAKE_ITEM"))
d.take:SetWidth(120)
end
d.take:SetButtonEnabled(canTake and not busy and Mail.open)
d.delete:SetButtonEnabled(Mail.Deletable(cur) and not busy and Mail.open)
d.reply:SetButtonEnabled(true)
end
function f.Refresh()
local list = Mail.ReadInbox()
local busy = Mail.busy
local takeable = 0
local view = Mail.view
for i, m in ipairs(list) do local m = m
local row = Row(i)
row.mail = m
row.inboxIndex = m.hasItem and m.i or nil
row:ClearAllPoints()
row:SetPoint("TOPLEFT", f.scroll.child, "TOPLEFT", 0, -((i - 1) * (ROW_H + ROW_GAP)))
row:SetWidth(RowWidth(f.scroll))
row:SetWidth(RowWidth(f.scroll, 250))
if m.itemTex then
row.icon:SetTexture(m.itemTex)
elseif m.money > 0 then
@@ -367,20 +507,21 @@ local function BuildInbox(parent)
row.l1:SetText(m.subject ~= "" and m.subject or m.sender)
local parts = {}
if m.hasItem then parts[#parts + 1] = (m.itemName or "?") .. ((m.itemCount or 1) > 1 and (" x" .. m.itemCount) or "") end
if m.money > 0 then parts[#parts + 1] = string.format(T("MAIL_ROW_GOLD"), Mail.Money(m.money)) end
if m.money > 0 then parts[#parts + 1] = Mail.Money(m.money) end
local text
if m.cod > 0 then
text = "|cffff7060" .. string.format(T("MAIL_ROW_COD"), Mail.Money(m.cod)) .. "|r"
elseif #parts == 0 then
text = T("MAIL_ROW_LETTER")
text = m.sender
else
text = table.concat(parts, " + ")
end
row.l2:SetText(string.format(T("MAIL_ROW_FROM"), m.sender) .. " " .. text)
row.l2:SetText(text)
row.days:SetText(m.days and string.format(T("MAIL_ROW_DAYS"), math.floor(m.days)) or "")
row.cb:SetShown(m.take)
row:SetAlpha(m.take and 1 or 0.55)
row:SetAlpha(m.take and 1 or 0.75)
row.cb:Refresh()
UI.SetBorderColor(row, (view and view.key == m.key) and "gold" or "bronze")
if m.take then takeable = takeable + 1 end
row:Show()
end
@@ -388,10 +529,11 @@ local function BuildInbox(parent)
f.empty:SetShown(#list == 0)
f.scroll:SetContentHeight(#list * (ROW_H + ROW_GAP) + 4)
f.info:SetText(string.format(T("MAIL_INBOX_COUNT"), #list))
RefreshDetail(list)
local sel = Mail:SelectedCount()
f.open:SetLabel(sel > 0 and string.format(T("MAIL_OPEN_SELECTED"), sel) or T("MAIL_OPEN_ALL"))
f.open:SetButtonEnabled(Mail.open and not busy and (sel > 0 or takeable > 0))
f.open:SetButtonEnabled(Mail.open and not busy and (sel > 0 or #list > 0))
f.stop:SetShown(busy ~= nil)
f.all:SetButtonEnabled(not busy and takeable > 0)
f.none:SetButtonEnabled(not busy and sel > 0)
@@ -477,6 +619,12 @@ local function BuildSend(parent)
status:SetTextColor(UI.Color(colorKey or "textDim"))
end
function f.Prefill(to, subject)
toBox:Set(to or "")
subjBox:Set(subject or "")
f.Refresh()
end
function f.Refresh()
for i, pk in ipairs(picks) do local pk, i = pk, i
local row = rows[i]
@@ -693,7 +841,7 @@ local TAB_DEFS = {
{ id = "lists", label = "MAIL_TAB_LISTS", build = BuildLists },
}
local function SelectTab(id)
function SelectTab(id)
current = id
for _, def in ipairs(TAB_DEFS) do
local pane = panes[def.id]
@@ -750,6 +898,10 @@ local function BuildWindow()
end, "danger")
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -10)
UI.AttachTooltip(close, function() return T("MAIL_CLOSE") end, "")
local arch = UI.CreateButton(f, T("MAIL_BTN_ARCHIVE"), 80, 24, function()
if Mail.OpenArchive then Mail.OpenArchive(true) end
end, "default")
arch:SetPoint("TOPRIGHT", f, "TOPRIGHT", -42, -10)
local div = UI.CreateDivider(f)
div:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -40)
div:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -40)
@@ -908,10 +1060,41 @@ if UI.RegisterSettingsSection then
end)
stack:Add(chat, { fit = true, gap = 4 })
boxes[#boxes + 1] = chat
local cleanup = UI.CreateCheckbox(f, T("MAIL_SET_CLEANUP"), function()
return Mail.db.settings.cleanup
end, function(value)
Mail.db.settings.cleanup = value and true or false
end)
stack:Add(cleanup, { fit = true, gap = 4 })
boxes[#boxes + 1] = cleanup
local dd
dd = UI.CreateDropdown(f, 300, 24, {
prefix = T("MAIL_SET_ARCHIVE_DAYS"),
getOptions = function()
local o = {}
for _, days in ipairs({ 0, 7, 14, 30, 90 }) do
o[#o + 1] = { value = days, text = days == 0 and T("MAIL_SET_ARCHIVE_OFF") or string.format(T("MAIL_SET_ARCHIVE_N"), days) }
end
return o
end,
onChange = function(v)
if v ~= nil then
Mail.db.settings.archiveDays = v
Mail:Purge()
dd:SetValue(v)
end
end,
})
stack:Add(dd, { gap = 8, height = 24 })
local archBtn = UI.CreateButton(f, T("MAIL_SET_ARCHIVE_BTN"), 160, 26, function()
if Mail.OpenArchive then Mail.OpenArchive(true) end
end, "default")
stack:Add(archBtn, { gap = 8, height = 26 })
local note = UI.CreateLabel(f, T("MAIL_SET_NOTE"), { size = 11, color = "textDim", justifyV = "TOP" })
stack:Add(note, { fit = true, gap = 10 })
function f.Refresh()
for _, cb in ipairs(boxes) do cb:Refresh() end
dd:SetValue(Mail.db.settings.archiveDays or 0)
end
stack:Layout()
return f
@@ -932,6 +1115,8 @@ SlashCmdList["FREEWOODMAIL"] = function(msg)
if msg == "on" then
Mail:SetReplace(true)
FW:Print(T("MAIL_CMD_ON"))
elseif msg == "archive" or msg == "archiv" then
if Mail.OpenArchive then Mail.OpenArchive(true) end
elseif msg == "off" then
Mail:SetReplace(false)
FW:Print(T("MAIL_CMD_OFF"))