Module:Sandbox/DVRTed

local plain_text = require("Module:Plain text")._main
local section_link = require('Module:Section link')._main

-- helper functs
local function trim(s) return s:match("^%s*(.-)%s*$") end
local function case_insensitive_pattern(str)
    local pattern = {}
    for c in str:gmatch(".") do
        if c:match("%a") then
            table.insert(pattern, string.format("[%s%s]", c:upper(), c:lower()))
        else
            table.insert(pattern, c)
        end
    end
    return table.concat(pattern)
end

-- actual functions
local function get_thread_details(page_content)
    local signatures = 0
    local thread_author_username = ""
    local thread_timestamp = ""

    for line in page_content:gmatch("[^\r\n]+") do

        -- match and count timestamp 
        -- at the end of the line
        local timestamp_pattern = "%d%d:%d%d, %d+ [^ ]+ %d%d%d%d %(UTC%)"
        local timestamp = line:match(timestamp_pattern)
        if timestamp then
            signatures = signatures + 1

            -- currently, we don't do anything with threads that may
            -- have replies
            if signatures >= 2 then return nil end

            -- set the last found timestamp in the thread
            thread_timestamp = timestamp

            -- try and get the username from the same line as timestamp.
            -- it updates to the last found username.
            local ci_user = case_insensitive_pattern("user")

            local username_pattern = "%[%[" .. ci_user ..
                                         "[^:]*:([^%]%|]+).-%]%]"
            for match in line:gmatch(username_pattern) do
                thread_author_username = match
            end

        end
    end

    return {
        author_username = trim(thread_author_username),
        timestamp = thread_timestamp
    }
end

local function get_unanswered_threads(page_content)
    local threads = {}
    local current_title = nil
    local current_body = ""
    local threads_with_data = {}

    for line in page_content:gmatch("[^\r\n]+") do
        local thread_title = line:match("^==%s*(.-)%s*==$")
        if thread_title then
            current_body = trim(current_body)
            if current_title and current_body then
                table.insert(threads,
                             {title = current_title, body = current_body})
            end
            current_title = thread_title
            current_body = ""
        else
            if current_title then
                current_body = current_body .. trim(line) .. "\n"
            end
        end
    end
    current_body = trim(current_body)
    if current_title and current_body then
        table.insert(threads, {title = current_title, body = current_body})
    end

    for _, thread in ipairs(threads) do
        local details = get_thread_details(thread.body)
        if details and details.author_username ~= "" then
            table.insert(threads_with_data, {
                title = thread.title,
                body = thread.body,
                author_username = details.author_username,
                timestamp = details.timestamp
            })
        end

    end

    return threads_with_data
end

local function wrap_in_custom_div(frame, thread, page_name)
    local title = thread.title
    local body = thread.body
    local author_username = thread.author_username
    local timestamp = thread.timestamp
    local timeago = frame:preprocess("{{time ago|" .. timestamp .. "}}")

    body = plain_text(body)
    if mw.ustring.len(body) > 300 then
        body = mw.ustring.sub(body, 1, 300) .. "…"
    end

    local author_link = string.format("[[User:%s]]", author_username)
    local thread_link = section_link(page_name, plain_text(title),
                                     {nopage = true})

    local div_html = string.format(
                         '<div style="background: #fff; border-radius: 6px; padding: 12px 16px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">' ..
                             '<div style="color: #6c757d; font-size: 12px; margin-bottom: 4px;">%s • %s</div>' ..
                             '<div style="font-size: 16px; font-weight: bold; margin-bottom: 6px;">%s</div>' ..
                             '<p style="margin: 0; color: #212529; font-size: 14px;">%s</p>' ..
                             '</div>', author_link, timeago, thread_link, body)

    return div_html
end

local function unanswered(frame)
    local page_name = frame.args[1] or nil
    local max_threads = tonumber(frame.args[2]) or 10

    if not page_name then return "No page name provided." end

    local page = mw.title.new(page_name)
    local page_content = page and page.getContent and page:getContent()
    if not page_content then return "Could not retrieve page content" end

    local unanswered_threads = get_unanswered_threads(page_content)

    local result = string.format(
                       '<div style="background: #f8f9fa; border-radius: 8px; padding: 20px; margin: 16px 0; border: 1px solid #dee2e6;">' ..
                           '<div style="margin-bottom: 16px;">' ..
                           '<div style="font-size: 18px; font-weight: bold; color: #212529; margin-bottom: 4px;">[[%s]]</div>' ..
                           '<div style="font-size: 14px; color: #6c757d; font-weight: 500;">%d unanswered threads</div>' ..
                           '</div>', page_name, #unanswered_threads)
    if #unanswered_threads == 0 then
        result = result .. '</div>'
        return result
    end

    result = result ..
                 '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">'

    --  if there's fewer threads than max_threads
    -- set it to threads count, otherwise set it to max_threads
    local display_count = math.min(#unanswered_threads, max_threads)

    for i = 1, display_count do
        local thread = unanswered_threads[i]
        local wrapped = wrap_in_custom_div(frame, thread, page_name)
        result = result .. wrapped
    end

    result = result .. '</div></div>'

    return result

end

return {unanswered = unanswered}

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.