Actions

Module

Changelog: Difference between revisions

From Project Rebearth

No edit summary
No edit summary
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:Changelog
-- Renders changelog entries from Module:Changelog/data.
-- Public: p.all(frame), p.forPage(frame)
local p = {}
local p = {}


Line 18: Line 14:
}
}


--- Format "YYYY-MM-DD" → "February 14, 2026"
local function formatDate(isoDate)
local function formatDate(isoDate)
     local y, m, d = isoDate:match("^(%d+)-(%d+)-(%d+)$")
     local y, m, d = isoDate:match("^(%d+)-(%d+)-(%d+)$")
Line 27: Line 22:
end
end


--- Validate tag; return tag name or error span.
local function validateTag(tag)
local function validateTag(tag)
     if VALID_TAGS[tag] then
     if VALID_TAGS[tag] then
Line 35: Line 29:
end
end


--- Extract page names that an entry relates to.
--- Scans wikilinks [[Page]] / [[Page|display]], Icon templates {{Icon|Res}},
--- resolves aliases, merges manual pages, and removes excludes.
local function getRelatedPages(entry)
local function getRelatedPages(entry)
     local aliases = mw.loadData("Module:Aliases/data")
     local aliases = mw.loadData("Module:Aliases/data")
Line 44: Line 35:


     local function addPage(name)
     local function addPage(name)
        -- Resolve alias
         local canonical = aliases[name] or name
         local canonical = aliases[name] or name
         if not seen[canonical] then
         if not seen[canonical] then
Line 52: Line 42:
     end
     end


    -- Scan [[PageName]] and [[PageName|display]]
     for target in entry.text:gmatch("%[%[([^%]|]+)[^%]]*%]%]") do
     for target in entry.text:gmatch("%[%[([^%]|]+)[^%]]*%]%]") do
         local trimmed = mw.text.trim(target)
         local trimmed = mw.text.trim(target)
Line 60: Line 49:
     end
     end


    -- Scan {{Icon|ResourceName}}
     for resource in entry.text:gmatch("{{Icon|([^}]+)}}") do
     for resource in entry.text:gmatch("{{Icon|([^}]+)}}") do
         local trimmed = mw.text.trim(resource)
         local trimmed = mw.text.trim(resource)
Line 68: Line 56:
     end
     end


    -- Merge manual pages
     if entry.pages then
     if entry.pages then
         for _, name in ipairs(entry.pages) do
         for _, name in ipairs(entry.pages) do
Line 75: Line 62:
     end
     end


    -- Remove excludes
     if entry.exclude then
     if entry.exclude then
         local excludeSet = {}
         local excludeSet = {}
Line 93: Line 79:
end
end


--- Group entries by date, preserving input order.
local function groupByDate(entries)
local function groupByDate(entries)
     local groups = {}
     local groups = {}
Line 115: Line 100:
end
end


--- Render a single entry as a wikitext bullet.
local function renderEntry(frame, entry)
local function renderEntry(entry)
     local line = "* " .. validateTag(entry.tag) .. " " .. entry.text
     return "* " .. validateTag(entry.tag) .. " " .. entry.text
    return frame:preprocess(line)
end
end


--- p.all — Full changelog for the main page.
function p.all(frame)
function p.all(frame)
     local data = mw.loadData("Module:Changelog/data")
     local _dataTitle = mw.title.new("Module:Changelog/data.json")
    local data = mw.text.jsonDecode(_dataTitle:getContent())
     local groups = groupByDate(data)
     local groups = groupByDate(data)
     local out = {}
     local out = {}
Line 129: Line 114:
         out[#out + 1] = "=== " .. formatDate(group.date) .. " ==="
         out[#out + 1] = "=== " .. formatDate(group.date) .. " ==="
         for _, entry in ipairs(group.entries) do
         for _, entry in ipairs(group.entries) do
             out[#out + 1] = renderEntry(entry)
             out[#out + 1] = renderEntry(frame, entry)
         end
         end
     end
     end


     return table.concat(out, "\n")
     return "\n" .. table.concat(out, "\n")
end
end


--- p.forPage — Per-page filtered changelog.
function p.forPage(frame)
function p.forPage(frame)
     local args = frame.args
     local args = frame.args
Line 146: Line 130:
     local limit = tonumber(args.limit) or 5
     local limit = tonumber(args.limit) or 5


     local data = mw.loadData("Module:Changelog/data")
     local _dataTitle = mw.title.new("Module:Changelog/data.json")
    local data = mw.text.jsonDecode(_dataTitle:getContent())


    -- Filter entries relevant to targetPage
     local matched = {}
     local matched = {}
     for _, entry in ipairs(data) do
     for _, entry in ipairs(data) do
Line 160: Line 144:
     end
     end


    -- Empty state
     if #matched == 0 then
     if #matched == 0 then
         return '<div class="rb-changelog rb-changelog-empty">No changelog entries for this page yet.</div>'
         return '<div class="rb-changelog rb-changelog-empty">No changelog entries for this page yet.</div>'
     end
     end


    -- Limit entries
     local limited = {}
     local limited = {}
     for i = 1, math.min(limit, #matched) do
     for i = 1, math.min(limit, #matched) do
Line 178: Line 160:
         out[#out + 1] = "==== " .. formatDate(group.date) .. " ===="
         out[#out + 1] = "==== " .. formatDate(group.date) .. " ===="
         for _, entry in ipairs(group.entries) do
         for _, entry in ipairs(group.entries) do
             out[#out + 1] = renderEntry(entry)
             out[#out + 1] = renderEntry(frame, entry)
         end
         end
     end
     end

Latest revision as of 14:24, 4 March 2026

Documentation for this module may be created at Module:Changelog/doc

local p = {}

local VALID_TAGS = {
    New = true,
    Balance = true,
    Fix = true,
    QoL = true,
    Performance = true,
}

local MONTH_NAMES = {
    "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December",
}

local function formatDate(isoDate)
    local y, m, d = isoDate:match("^(%d+)-(%d+)-(%d+)$")
    if not y then return isoDate end
    local month = MONTH_NAMES[tonumber(m)]
    if not month then return isoDate end
    return month .. " " .. tonumber(d) .. ", " .. y
end

local function validateTag(tag)
    if VALID_TAGS[tag] then
        return "{{Tag|" .. tag .. "}}"
    end
    return '<span class="error">Unknown tag: "' .. tag .. '"</span>'
end

local function getRelatedPages(entry)
    local aliases = mw.loadData("Module:Aliases/data")
    local pages = {}
    local seen = {}

    local function addPage(name)
        local canonical = aliases[name] or name
        if not seen[canonical] then
            seen[canonical] = true
            pages[#pages + 1] = canonical
        end
    end

    for target in entry.text:gmatch("%[%[([^%]|]+)[^%]]*%]%]") do
        local trimmed = mw.text.trim(target)
        if trimmed ~= "" then
            addPage(trimmed)
        end
    end

    for resource in entry.text:gmatch("{{Icon|([^}]+)}}") do
        local trimmed = mw.text.trim(resource)
        if trimmed ~= "" then
            addPage(trimmed)
        end
    end

    if entry.pages then
        for _, name in ipairs(entry.pages) do
            addPage(name)
        end
    end

    if entry.exclude then
        local excludeSet = {}
        for _, name in ipairs(entry.exclude) do
            excludeSet[name] = true
        end
        local filtered = {}
        for _, name in ipairs(pages) do
            if not excludeSet[name] then
                filtered[#filtered + 1] = name
            end
        end
        pages = filtered
    end

    return pages
end

local function groupByDate(entries)
    local groups = {}
    local dateOrder = {}
    local dateMap = {}

    for _, entry in ipairs(entries) do
        if not dateMap[entry.date] then
            dateMap[entry.date] = {}
            dateOrder[#dateOrder + 1] = entry.date
        end
        local group = dateMap[entry.date]
        group[#group + 1] = entry
    end

    for _, date in ipairs(dateOrder) do
        groups[#groups + 1] = { date = date, entries = dateMap[date] }
    end

    return groups
end

local function renderEntry(frame, entry)
    local line = "* " .. validateTag(entry.tag) .. " " .. entry.text
    return frame:preprocess(line)
end

function p.all(frame)
    local _dataTitle = mw.title.new("Module:Changelog/data.json")
    local data = mw.text.jsonDecode(_dataTitle:getContent())
    local groups = groupByDate(data)
    local out = {}

    for _, group in ipairs(groups) do
        out[#out + 1] = "=== " .. formatDate(group.date) .. " ==="
        for _, entry in ipairs(group.entries) do
            out[#out + 1] = renderEntry(frame, entry)
        end
    end

    return "\n" .. table.concat(out, "\n")
end

function p.forPage(frame)
    local args = frame.args
    local targetPage = args.page or args[1]
    if not targetPage or targetPage == "" then
        targetPage = mw.title.getCurrentTitle().text
    end

    local limit = tonumber(args.limit) or 5

    local _dataTitle = mw.title.new("Module:Changelog/data.json")
    local data = mw.text.jsonDecode(_dataTitle:getContent())

    local matched = {}
    for _, entry in ipairs(data) do
        local relatedPages = getRelatedPages(entry)
        for _, pageName in ipairs(relatedPages) do
            if pageName == targetPage then
                matched[#matched + 1] = entry
                break
            end
        end
    end

    if #matched == 0 then
        return '<div class="rb-changelog rb-changelog-empty">No changelog entries for this page yet.</div>'
    end

    local limited = {}
    for i = 1, math.min(limit, #matched) do
        limited[#limited + 1] = matched[i]
    end

    local groups = groupByDate(limited)
    local out = {}

    out[#out + 1] = '<div class="rb-changelog">'
    for _, group in ipairs(groups) do
        out[#out + 1] = "==== " .. formatDate(group.date) .. " ===="
        for _, entry in ipairs(group.entries) do
            out[#out + 1] = renderEntry(frame, entry)
        end
    end

    out[#out + 1] = '<div class="rb-changelog-more">'
    out[#out + 1] = "''[[Project Rebearth#Changelogs|See full changelog →]]''"
    out[#out + 1] = "</div>"
    out[#out + 1] = "</div>"

    return table.concat(out, "\n")
end

return p