Module:Message
Jump to navigation
Jump to search
Implementation for article and inline notices.
--------------------------------------------------------------------------------
-- 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 review states
-- Note: 'verified' is invisible to readers but detectable as a review marker
local QUALITY_LEVELS = {
unverified = {
class = 'quality-unverified',
icon = 'Ambox warning orange.svg',
title = 'Unverified',
text = 'This article has not yet been reviewed for factual accuracy by editors. Check details against cited sources.',
visible = true,
},
wip = {
class = 'quality-wip',
icon = 'Information icon4.svg',
title = 'Work in progress',
text = 'This article is undergoing revision or fact-checking. Details may change as editors review sources.',
visible = true,
},
verified = {
class = 'quality-verified',
icon = 'Symbol confirmed.svg',
title = 'Verified',
text = 'This article has been reviewed and accepted by 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)
return require('Module:Arguments').getArgs(frame, {
trim = false,
removeBlanks = false,
})
end
local yesno = require('Module:Yesno')
--------------------------------------------------------------------------------
-- 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 imageContent = getArg(args, 'image_content')
local imageSize = getArg(args, 'imagesize', '40px')
local small = yesno(getArg(args, 'small'), false)
local plainlinks = yesno(getArg(args, 'plainlinks'), true)
-- 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 hasValue(imageContent) then
box:tag('div')
:addClass('mbox-image')
:wikitext(imageContent)
elseif 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(args)
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')
:attr('role', 'note')
-- Icon
if args.image_content ~= false then
box:tag('div')
:addClass('stub-icon')
:wikitext(args.image_content or ('[[File:' .. icon .. '|40px|link=|alt=]]'))
end
-- Text
local textDiv = box:tag('div')
:addClass('stub-text')
textDiv:tag('strong'):wikitext(args.text or 'This article is a stub.')
textDiv:wikitext(' You can help Remilia Wiki by ')
textDiv:wikitext('[' .. tostring(mw.title.getCurrentTitle():fullUrl('action=edit')) .. ' expanding it].')
if hasValue(args.note) then
textDiv:tag('div'):addClass('stub-note'):wikitext(args.note)
end
-- Category
local category = 'Stubs'
if hasValue(stubType) then
category = stubType .. ' stubs'
end
local categories = ''
if mw.title.getCurrentTitle().namespace == 0 and not require('Module:Yesno')(args.nocat, false) then
categories = args.categories or ('[[Category:' .. category .. ']]')
end
return tostring(box) .. categories
end
function p.stub(frame)
return p._stub(getFrameArgs(frame))
end
--------------------------------------------------------------------------------
-- INLINE TAGS (Citation needed, etc.)
--------------------------------------------------------------------------------
local function renderTag(args, tagName)
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
function p.tag(frame)
local args = getFrameArgs(frame)
return renderTag(args, getArg(args, 1) or 'cn')
end
-- Convenience aliases
function p.cn(frame)
return renderTag(getFrameArgs(frame), 'cn')
end
function p.citationNeeded(frame)
return p.cn(frame)
end
function p.clarify(frame)
return renderTag(getFrameArgs(frame), 'clarify')
end
function p.when(frame)
return renderTag(getFrameArgs(frame), 'when')
end
function p.update(frame)
return renderTag(getFrameArgs(frame), 'update')
end
--------------------------------------------------------------------------------
-- AS OF
--------------------------------------------------------------------------------
function p.asof(frame)
local args = getFrameArgs(frame)
local date = getArg(args, 1) or getArg(args, 'date')
local since = yesno(getArg(args, 'since'), false)
local lc = yesno(getArg(args, 'lc'), false)
if isEmpty(date) then
return '<span class="error">Error: Date required</span>'
end
local prefix = lc and 'as of ' or 'As of '
if 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