Module:KnownIssue: Difference between revisions
Appearance
Created page with "-- Module:KnownIssue -- Two independent jobs: -- -- p.rowhead — renders the non-text cells of one Known Issues row -- (ID, Version fixed, Version first noticed, Date). The -- description text is NOT handled here: the template appends it -- as raw wikitext so {{TAG|...}}, {{FILE|...}} etc. expand once, -- in place, and render correctly. (Passing rich text through an -- #invoke argument a..." |
No edit summary |
||
| Line 21: | Line 21: | ||
-- * "Version first noticed": --vred-bg (that version still has the issue), | -- * "Version first noticed": --vred-bg (that version still has the issue), | ||
-- or --vgrey-bg for obsolete entries. | -- or --vgrey-bg for obsolete entries. | ||
-- * row anchors | -- * row anchors are <div id="KnownIssueXYZ" ...> with scroll-margin-top so | ||
-- following the link leaves room above the row (see ANCHOR_SCROLL_MARGIN). | |||
local p = {} | local p = {} | ||
| Line 28: | Line 29: | ||
local CYAN = "background-color:var(--vcyan-bg); color:var(--vdefault-text-nb)" | local CYAN = "background-color:var(--vcyan-bg); color:var(--vdefault-text-nb)" | ||
local GREY = "background-color:var(--vgrey-bg); color:var(--vdefault-text-nb)" | local GREY = "background-color:var(--vgrey-bg); color:var(--vdefault-text-nb)" | ||
-- Space left above a row when an #KnownIssueN link is followed, so the row does | |||
-- not land flush against the top (or under the site header) and clip the | |||
-- description's first line. Tune to taste. | |||
local ANCHOR_SCROLL_MARGIN = "5em" | |||
local function trim(s) | local function trim(s) | ||
| Line 74: | Line 80: | ||
local function anchorDiv(target) | local function anchorDiv(target) | ||
if not anchor_on then return "" end | if not anchor_on then return "" end | ||
return '<div id="' .. target .. '" style="display:inline;"></div>' | return '<div id="' .. target | ||
.. '" style="display:inline-block; scroll-margin-top:' | |||
.. ANCHOR_SCROLL_MARGIN .. ';"></div>' | |||
end | end | ||
local n = tonumber(id) | local n = tonumber(id) | ||
| Line 151: | Line 159: | ||
end | end | ||
end | end | ||
-- Lowest positive integer not yet used, to suggest for the next ID. | |||
local nextFree = 1 | |||
while counts[nextFree] do nextFree = nextFree + 1 end | |||
local suggest = ' Next free ID integer: ' .. nextFree .. '.' | |||
local out = {} | local out = {} | ||
if #dups > 0 then | if #dups > 0 then | ||
out[#out + 1] = banner(frame, 'Duplicate Known Issue IDs on this page: ' | out[#out + 1] = banner(frame, 'Duplicate Known Issue IDs on this page: ' | ||
.. table.concat(dups, ', ') .. '. Each issue must have a unique ID.') | .. table.concat(dups, ', ') .. '. Each issue must have a unique ID.' .. suggest) | ||
end | end | ||
if missing > 0 then | if missing > 0 then | ||
out[#out + 1] = banner(frame, missing .. ' Known Issue' | out[#out + 1] = banner(frame, missing .. ' Known Issue' | ||
.. (missing == 1 and ' has' or 's have') .. ' no ID assigned on this page.') | .. (missing == 1 and ' has' or 's have') .. ' no ID assigned on this page.' .. suggest) | ||
end | end | ||
return table.concat(out, '\n') | return table.concat(out, '\n') | ||
Revision as of 10:43, 26 June 2026
Documentation for this module may be created at Module:KnownIssue/doc
-- Module:KnownIssue
-- Two independent jobs:
--
-- p.rowhead — renders the non-text cells of one Known Issues row
-- (ID, Version fixed, Version first noticed, Date). The
-- description text is NOT handled here: the template appends it
-- as raw wikitext so {{TAG|...}}, {{FILE|...}} etc. expand once,
-- in place, and render correctly. (Passing rich text through an
-- #invoke argument and re-emitting it corrupts multi-argument
-- templates, which is why text must stay out of Lua.)
--
-- p.checkdupes — scans the current page's own source via getContent() and
-- warns if any integer ID is used by more than one
-- {{KnownIssue}} (i.e. would emit the same #KnownIssueN anchor
-- twice). This replaces render-time dedup, which is impossible
-- here because Scribunto does not keep state across #invoke.
--
-- Styling:
-- * "Version fixed": open -> --vred-bg ("Open"); resolved -> --vcyan-bg (the
-- version); obsolete -> --vgrey-bg ("Obsolete").
-- * "Version first noticed": --vred-bg (that version still has the issue),
-- or --vgrey-bg for obsolete entries.
-- * row anchors are <div id="KnownIssueXYZ" ...> with scroll-margin-top so
-- following the link leaves room above the row (see ANCHOR_SCROLL_MARGIN).
local p = {}
local RED = "background-color:var(--vred-bg); color:var(--vdefault-text-nb)"
local CYAN = "background-color:var(--vcyan-bg); color:var(--vdefault-text-nb)"
local GREY = "background-color:var(--vgrey-bg); color:var(--vdefault-text-nb)"
-- Space left above a row when an #KnownIssueN link is followed, so the row does
-- not land flush against the top (or under the site header) and clip the
-- description's first line. Tune to taste.
local ANCHOR_SCROLL_MARGIN = "5em"
local function trim(s)
if type(s) ~= "string" then return "" end
return mw.ustring.match(s, "^%s*(.-)%s*$") or ""
end
local function isTruthy(val)
val = mw.ustring.lower(trim(val))
return val == "yes" or val == "1" or val == "true"
end
local function isFalsy(val)
val = mw.ustring.lower(trim(val))
return val == "no" or val == "0" or val == "false"
end
-- ── p.rowhead ───────────────────────────────────────────────────────────────
-- Called by {{KnownIssue}} with plain scalar args (no rich text). Returns the
-- "|-" row start plus the ID / Version fixed / Version first noticed / Date
-- cells. The template then appends "| {{{text}}}" as the final cell.
function p.rowhead(frame)
local args = frame.args
local id = trim(args.ID or args.id or "")
local resolved = trim(args.resolved_in_version or "")
local reported = trim(args.reported_in_version or "")
local date = trim(args.date_added or "")
local obsolete = isTruthy(args.obsolete or "")
local anchor_on = not isFalsy(args.anchor or "")
local hidden = isTruthy(args.hide or "")
-- Version fixed cell. Obsolete > resolved > open.
local fixed_style, fixed_text
if obsolete then
fixed_style, fixed_text = GREY, "Obsolete"
elseif resolved ~= "" and resolved ~= "-" then
fixed_style, fixed_text = CYAN, resolved
else
fixed_style, fixed_text = RED, "Open"
end
-- Version first noticed cell: red (still affected), grey if obsolete.
local reported_style = obsolete and GREY or RED
-- ID cell.
local function anchorDiv(target)
if not anchor_on then return "" end
return '<div id="' .. target
.. '" style="display:inline-block; scroll-margin-top:'
.. ANCHOR_SCROLL_MARGIN .. ';"></div>'
end
local n = tonumber(id)
local id_cell
if id == "" then
id_cell = '|style="' .. RED .. '; text-align:center; font-style:italic;" | None'
elseif not n or n ~= math.floor(n) or n < 0 then
id_cell = '|style="' .. RED .. '; text-align:center; font-weight:bold;" | '
.. anchorDiv('KnownIssue' .. mw.uri.encode(id, "PATH"))
.. '<span title="ID must be a positive integer">⚠ '
.. mw.text.nowiki(id) .. '</span>'
else
id_cell = '|style="text-align:center;" | '
.. anchorDiv('KnownIssue' .. n) .. tostring(n)
end
-- hide -> render the row but collapse it with display:none, so the template
-- never has to wrap the description in a parser function (which would
-- reprocess and mangle rich markup like multi-argument {{TAG|...}}).
-- The final '|' opens the Description cell; the template appends raw text.
-- All cells are centred except the Description (the final raw cell).
return table.concat({
hidden and '|- style="display:none;"' or '|-',
id_cell,
'|style="' .. fixed_style .. '; text-align:center;" |' .. fixed_text,
'|style="' .. reported_style .. '; text-align:center;" |' .. reported,
'|style="text-align:center;" |' .. date,
'|',
}, '\n')
end
-- ── p.checkdupes ─────────────────────────────────────────────────────────────
-- Scans the current page source for EVERY {{KnownIssue}} call (regardless of
-- hide / anchor) and reports, as separate warnings: (1) any integer ID used more
-- than once, and (2) rows with no ID. Returns "" when both are clean.
-- Render via {{NB|<style>|<message>}}. NB's signature is
-- {{NB|<style>|<message>|<<indent>>|<<firstword>>}}; the "warning" style already
-- supplies its own "Warning:" first word, so only style + message are passed.
-- (Passing a 3rd arg here sets <indent>, which mangles the box.) expandTemplate
-- passes each arg discretely, so '|'/'=' in msg can't break NB's parsing.
local function banner(frame, msg)
return frame:expandTemplate{ title = 'NB', args = { 'warning', msg, '', 'WARNING:' } }
end
function p.checkdupes(frame)
local content = mw.title.getCurrentTitle():getContent()
if not content then return "" end
local counts, order, missing = {}, {}, 0
-- Require a "|" right after the name so this matches {{KnownIssue|...}} only,
-- never {{KnownIssueDupCheck}}. The capture runs to the first "}}"; the ID is
-- an early parameter, so it survives even when text contains nested templates
-- or spans multiple lines.
for block in mw.ustring.gmatch(content, "{{%s*[Kk]nown[Ii]ssue%s*(|.-)}}") do
local lb = mw.ustring.lower(block)
local idstr = mw.ustring.match(lb, "[|\n]%s*id%s*=%s*([^|}\n]*)")
local id = idstr and trim(idstr) or ""
if id == "" then
missing = missing + 1
else
local n = tonumber(id)
if n and n == math.floor(n) and n >= 0 then
if not counts[n] then
counts[n] = 0
order[#order + 1] = n
end
counts[n] = counts[n] + 1
end
end
end
local dups = {}
for _, n in ipairs(order) do
if counts[n] > 1 then
dups[#dups + 1] = n .. ' (×' .. counts[n] .. ')'
end
end
-- Lowest positive integer not yet used, to suggest for the next ID.
local nextFree = 1
while counts[nextFree] do nextFree = nextFree + 1 end
local suggest = ' Next free ID integer: ' .. nextFree .. '.'
local out = {}
if #dups > 0 then
out[#out + 1] = banner(frame, 'Duplicate Known Issue IDs on this page: '
.. table.concat(dups, ', ') .. '. Each issue must have a unique ID.' .. suggest)
end
if missing > 0 then
out[#out + 1] = banner(frame, missing .. ' Known Issue'
.. (missing == 1 and ' has' or 's have') .. ' no ID assigned on this page.' .. suggest)
end
return table.concat(out, '\n')
end
return p