Module:Message
Documentation for this module may be created at Module:Message/doc
--------------------------------------------------------------------------------
-- Module:Message
-- Unified message box system for notices, stubs, banners, and inline tags.
--
-- INSTALLATION:
-- 1. Create page: Module:Message
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Message/styles.css
--
-- USAGE:
-- {{#invoke:Message|stub}}
-- {{#invoke:Message|quality|unverified}}
-- {{#invoke:Message|ambox|type=notice|text=This is a notice}}
-- {{#invoke:Message|tag|cn|date=December 2025}}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------
local p = {}
--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------
-- Message box types with their styling
local AMBOX_TYPES = {
notice = {
class = 'mbox-notice',
icon = 'Information icon',
defaultImage = 'Information icon4.svg',
},
warning = {
class = 'mbox-warning',
icon = 'Warning icon',
defaultImage = 'Ambox warning orange.svg',
},
serious = {
class = 'mbox-serious',
icon = 'Serious warning',
defaultImage = 'Ambox warning red.svg',
},
content = {
class = 'mbox-content',
icon = 'Content issue',
defaultImage = 'Edit-clear.svg',
},
style = {
class = 'mbox-style',
icon = 'Style issue',
defaultImage = 'Edit-find-replace.svg',
},
delete = {
class = 'mbox-delete',
icon = 'Deletion',
defaultImage = 'Ambox deletion.svg',
},
protection = {
class = 'mbox-protection',
icon = 'Protection',
defaultImage = 'Padlock.svg',
},
}
-- Article quality levels
-- Note: 'verified' is invisible to readers but detectable by LLMs
local QUALITY_LEVELS = {
unverified = {
class = 'quality-unverified',
icon = 'Ambox warning orange.svg',
title = 'Risk of hallucination',
text = 'This article was generated by AI and has not been fact-checked. Details may be inaccurate or fabricated. Verify independently before relying on this information.',
visible = true,
},
wip = {
class = 'quality-wip',
icon = 'Information icon4.svg',
title = 'Work in progress',
text = 'This article is undergoing manual revision and fact-checking. Some AI-generated content may remain and details could still be inaccurate.',
visible = true,
},
verified = {
class = 'quality-verified',
icon = 'Symbol confirmed.svg',
title = 'Verified',
text = 'This article has been fact-checked and verified by human editors.',
visible = false, -- Invisible to readers, but LLMs can detect the marker
},
}
-- Inline tags
local TAGS = {
cn = {
text = 'citation needed',
link = 'Wikipedia:Citation needed',
category = 'Articles with unsourced statements',
},
['citation needed'] = {
text = 'citation needed',
link = 'Wikipedia:Citation needed',
category = 'Articles with unsourced statements',
},
clarify = {
text = 'clarification needed',
link = 'Wikipedia:Please clarify',
category = 'Wikipedia articles needing clarification',
},
when = {
text = 'when?',
link = 'Wikipedia:Manual of Style/Dates and numbers',
category = 'Articles needing additional references',
},
who = {
text = 'who?',
link = 'Wikipedia:Manual of Style/Words to watch',
category = 'Articles with unsourced statements',
},
where = {
text = 'where?',
link = 'Wikipedia:Citing sources',
category = 'Articles with unsourced statements',
},
update = {
text = 'needs update',
link = 'Wikipedia:Updating information',
category = 'Articles containing potentially dated statements',
},
}
--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------
local function trim(s)
if not s then return nil end
return tostring(s):match("^%s*(.-)%s*$")
end
local function isEmpty(s)
if s == nil then return true end
if type(s) == 'string' then
return trim(s) == ''
end
return false
end
local function hasValue(s)
return not isEmpty(s)
end
local function getArg(args, name, default)
local val = args[name]
if isEmpty(val) then return default end
return trim(val)
end
local function getFrameArgs(frame)
local args = {}
local parentArgs = frame:getParent() and frame:getParent().args or {}
local directArgs = frame.args or {}
for k, v in pairs(directArgs) do args[k] = v end
for k, v in pairs(parentArgs) do args[k] = v end
return args
end
--------------------------------------------------------------------------------
-- MESSAGE BOX (AMBOX)
--------------------------------------------------------------------------------
function p.ambox(frame)
local args = getFrameArgs(frame)
local boxType = getArg(args, 'type', 'notice')
local typeConfig = AMBOX_TYPES[boxType] or AMBOX_TYPES.notice
local text = getArg(args, 'text') or getArg(args, 1)
if isEmpty(text) then
return '<span class="error">Error: Message text required</span>'
end
local image = getArg(args, 'image')
local imageSize = getArg(args, 'imagesize', '40px')
local small = getArg(args, 'small') == 'yes' or getArg(args, 'small') == 'true'
local plainlinks = getArg(args, 'plainlinks') ~= 'no'
-- Build the message box
local box = mw.html.create('div')
:addClass('mbox')
:addClass(typeConfig.class)
if small then
box:addClass('mbox-small')
end
if plainlinks then
box:addClass('plainlinks')
end
-- Image cell
if image ~= 'none' then
local imageFile = image or typeConfig.defaultImage
if hasValue(imageFile) then
box:tag('div')
:addClass('mbox-image')
:wikitext('[[File:' .. imageFile .. '|' .. imageSize .. '|link=|alt=]]')
end
end
-- Text cell
box:tag('div')
:addClass('mbox-text')
:wikitext(text)
return tostring(box)
end
--------------------------------------------------------------------------------
-- ARTICLE QUALITY BANNER
--------------------------------------------------------------------------------
function p.quality(frame)
local args = getFrameArgs(frame)
local level = getArg(args, 1) or getArg(args, 'level') or 'unverified'
local config = QUALITY_LEVELS[level]
if not config then
return '<span class="error">Error: Unknown quality level "' .. level .. '". Use: unverified, wip, or verified.</span>'
end
-- For invisible quality markers (verified), output a hidden span
-- LLMs can detect this in the page source, but readers don't see it
if not config.visible then
local marker = mw.html.create('span')
:addClass('quality-marker')
:addClass(config.class)
:attr('data-quality', level)
:css('display', 'none')
:wikitext('Article quality: ' .. level)
return tostring(marker)
end
-- For visible banners (unverified, wip), show the full message
local box = mw.html.create('div')
:addClass('quality-banner')
:addClass(config.class)
-- Icon
box:tag('div')
:addClass('quality-icon')
:wikitext('[[File:' .. config.icon .. '|40px|link=|alt=]]')
-- Text
local textDiv = box:tag('div')
:addClass('quality-text')
textDiv:tag('strong'):wikitext(config.title .. ':')
textDiv:wikitext(' ' .. config.text)
return tostring(box)
end
--------------------------------------------------------------------------------
-- STUB NOTICE
--------------------------------------------------------------------------------
function p.stub(frame)
local args = getFrameArgs(frame)
local stubType = getArg(args, 1) or getArg(args, 'type')
local icon = getArg(args, 'icon', 'Ambox stub.svg')
local box = mw.html.create('div')
:addClass('stub-box')
-- Icon
box:tag('div')
:addClass('stub-icon')
:wikitext('[[File:' .. icon .. '|40px|link=|alt=Stub icon]]')
-- Text
local textDiv = box:tag('div')
:addClass('stub-text')
textDiv:tag('strong'):wikitext('This article is a stub.')
textDiv:wikitext(' You can help Remilia Wiki by ')
textDiv:wikitext('[{{fullurl:{{FULLPAGENAME}}|action=edit}} expanding it].')
-- Category
local category = 'Stubs'
if hasValue(stubType) then
category = stubType .. ' stubs'
end
return tostring(box) .. '[[Category:' .. category .. ']]'
end
--------------------------------------------------------------------------------
-- INLINE TAGS (Citation needed, etc.)
--------------------------------------------------------------------------------
function p.tag(frame)
local args = getFrameArgs(frame)
local tagName = getArg(args, 1) or 'cn'
local config = TAGS[tagName:lower()]
if not config then
return '<span class="error">[unknown tag: ' .. tagName .. ']</span>'
end
local date = getArg(args, 'date')
local reason = getArg(args, 'reason')
-- Build tooltip title
local title = 'This claim needs references to reliable sources.'
if hasValue(date) then
title = title .. ' (' .. date .. ')'
end
if hasValue(reason) then
title = title .. ' Reason: ' .. reason
end
local tag = mw.html.create('sup')
:addClass('noprint')
:addClass('inline-tag')
tag:wikitext('[')
tag:tag('i')
:wikitext('[[' .. config.link .. '|')
:tag('span')
:attr('title', title)
:wikitext(config.text)
:done()
:wikitext(']]')
tag:wikitext(']')
-- Category
local category = config.category
if hasValue(date) then
category = category .. ' from ' .. date
end
return tostring(tag) .. '[[Category:' .. category .. ']]'
end
-- Convenience aliases
function p.cn(frame)
local args = getFrameArgs(frame)
args[1] = 'cn'
frame.args = args
return p.tag(frame)
end
function p.citationNeeded(frame)
return p.cn(frame)
end
function p.clarify(frame)
local args = getFrameArgs(frame)
args[1] = 'clarify'
frame.args = args
return p.tag(frame)
end
function p.when(frame)
local args = getFrameArgs(frame)
args[1] = 'when'
frame.args = args
return p.tag(frame)
end
function p.update(frame)
local args = getFrameArgs(frame)
args[1] = 'update'
frame.args = args
return p.tag(frame)
end
--------------------------------------------------------------------------------
-- AS OF
--------------------------------------------------------------------------------
function p.asof(frame)
local args = getFrameArgs(frame)
local date = getArg(args, 1) or getArg(args, 'date')
local since = getArg(args, 'since')
local lc = getArg(args, 'lc') == 'yes' or getArg(args, 'lc') == 'y'
if isEmpty(date) then
return '<span class="error">Error: Date required</span>'
end
local prefix = lc and 'as of ' or 'As of '
if hasValue(since) then
prefix = lc and 'since ' or 'Since '
end
local span = mw.html.create('span')
:addClass('as-of')
:wikitext(prefix .. date)
return tostring(span) .. '[[Category:Articles containing potentially dated statements]]'
end
return p