Module:Sandbox/TheUnit 72/Universal clickable button
Appearance
local p = {}
local colorMap = {
red = "#d33333",
["deep red"] = "#8b0000",
["light red"] = "#ff7f7f",
orange = "#ff8c00",
["light orange"] = "#ffd1a3",
yellow = "#ffeb3b",
gold = "#d4af37",
["deep yellow"] = "#d4af37",
["light yellow"] = "#fff59d",
green = "#4caf50",
["deep green"] = "#1b5e20",
["light green"] = "#88e788",
turquoise = "#40e0d0",
turqoise = "#40e0d0",
blue = "#3366cc",
["deep blue"] = "#0d47a1",
["light blue"] = "#90caf9",
purple = "#7953a9",
["deep purple"] = "#4a148c",
["light purple"] = "#ce93d8",
pink = "#e91e63",
magenta = "#c2185b",
white = "#ffffff",
black = "#000000",
grey = "#a2a9b1",
gray = "#a2a9b1",
silver = "#c8ccd1",
["light grey"] = "#f8f9fa",
["deep grey"] = "#54595d",
["charcoal"] = "#36454f",
beige = "#f8eaba",
["deep beige"] = "#c0c090",
["progressive"] = "#3366cc",
["destructive"] = "#bf3c2c",
["constructive"] = "#008000",
["effective"] = "#d4af37",
["neutral"] = "#f8f9fa"
}
local DEFAULT_BG = "#f8f9fa"
local DEFAULT_FG = "#000000"
local DEFAULT_BORDER = "#a2a9b1"
local function normalizeKey(str)
if not str then return nil end
str = tostring(str):lower()
:gsub("^%s+", "")
:gsub("%s+$", "")
:gsub("%s+", " ")
str = str:gsub("^dark%s+", "deep ")
return str
end
local function resolveColor(input)
if not input then return nil end
local key = normalizeKey(input)
if colorMap[key] then return colorMap[key] end
if key:match("^#%x%x%x$") or key:match("^#%x%x%x%x%x%x$") then return key end
if key:match("^%x%x%x$") or key:match("^%x%x%x%x%x%x$") then return "#" .. key end
return nil
end
local function expandHex(hex)
hex = hex:gsub("#", "")
if #hex == 3 then
return hex:sub(1,1) .. hex:sub(1,1)
.. hex:sub(2,2) .. hex:sub(2,2)
.. hex:sub(3,3) .. hex:sub(3,3)
elseif #hex == 6 then
return hex
end
return "808080"
end
local function getTextColor(bg)
local hex = expandHex(bg)
local r = tonumber(hex:sub(1,2), 16)
local g = tonumber(hex:sub(3,4), 16)
local b = tonumber(hex:sub(5,6), 16)
local lum = (0.299 * r + 0.587 * g + 0.114 * b)
return (lum > 186) and "#000" or "#fff"
end
local function pickBaseColor(args)
local raw = args.color or args.colour or args[3]
local resolved = resolveColor(raw)
if resolved then
local border
if resolved == "#f8f9fa" then
border = "#a2a9b1"
elseif resolved == "#f8eaba" then
border = "#c0c090"
else
border = resolved
end
return resolved, getTextColor(resolved), border
end
return DEFAULT_BG, DEFAULT_FG, DEFAULT_BORDER
end
local function isValidUrl(url)
if not url or url == "" then return false end
return url:match("^https?://") ~= nil
or url:match("^//") ~= nil
end
local function adjustColor(hex, factor)
local h = expandHex(hex)
local r = tonumber(h:sub(1,2), 16)
local g = tonumber(h:sub(3,4), 16)
local b = tonumber(h:sub(5,6), 16)
r = math.min(255, math.max(0, math.floor(r * factor)))
g = math.min(255, math.max(0, math.floor(g * factor)))
b = math.min(255, math.max(0, math.floor(b * factor)))
return string.format("#%02x%02x%02x", r, g, b)
end
local function getHoverColor(bg)
local hex = expandHex(bg)
local r = tonumber(hex:sub(1,2), 16)
local g = tonumber(hex:sub(3,4), 16)
local b = tonumber(hex:sub(5,6), 16)
local lum = (0.299 * r + 0.587 * g + 0.114 * b)
if bg == "#000000" or bg == "#000" then
return adjustColor(bg, 1.15)
elseif lum >= 220 and lum < 255 then
-- Near-white: lighten slightly
return adjustColor(bg, 1.08)
else
return adjustColor(bg, 0.82)
end
end
local function buildDisplay(args, bg, fg, border)
local span = mw.html.create("span")
span
:addClass("ucb-button")
:css({
["background-color"] = bg,
["color"] = fg,
["border-color"] = border,
["vertical-align"] = "middle"
})
local userClass = args.class and tostring(args.class):lower():gsub("^%s+", ""):gsub("%s+$", "") or nil
if userClass and userClass ~= "" and userClass ~= "mw-ui-button" then
span:addClass(userClass)
end
if args.tooltip and tostring(args.tooltip) ~= "" then
span:attr("title", tostring(args.tooltip))
end
if args.style then
span:cssText(args.style)
end
local text = args.text or args[2] or args[1] or ""
if args.icon and tostring(args.icon) ~= "" then
local filename = tostring(args.icon):gsub("^[Ff]ile:", "")
span:wikitext(string.format('[[File:%s|16px|link=]] ', filename))
end
span:wikitext(text)
return tostring(span)
end
function p.main(frame)
local args = frame:getParent().args
local styleLink = frame:extensionTag("templatestyles", "", {
src = "Module:Sandbox/TheUnit_72/Universal clickable button/styles.css"
})
local link = args.url or args[1] or args.link
if not link or tostring(link) == "" then
return ""
end
local isUrl = args.url ~= nil and tostring(args.url) ~= ""
if isUrl and not isValidUrl(tostring(args.url)) then
return '<span class="error">Universal Clickable Button: invalid or unsafe URL.</span>'
end
local bg, fg, border = pickBaseColor(args)
local useHover = not isUrl
and args.hover and tostring(args.hover):lower():gsub("^%s+", ""):gsub("%s+$", "") == "yes"
if not isUrl then
local currentTitle = mw.title.getCurrentTitle()
local targetTitle = mw.title.new(tostring(link))
if targetTitle and currentTitle
and targetTitle.prefixedText == currentTitle.prefixedText then
return styleLink .. buildDisplay(args, bg, fg, border)
end
end
local display = buildDisplay(args, bg, fg, border)
local linkHtml
if useHover then
linkHtml = string.format('[[%s|%s]]', tostring(link), display)
return styleLink .. string.format('<span class="plainlinks">%s</span>', linkHtml)
end
local href
if isUrl then
href = tostring(args.url)
else
local titleObj = mw.title.new(tostring(link))
href = titleObj and titleObj:fullUrl() or tostring(link)
end
linkHtml = string.format('[%s %s]', href, display)
return styleLink .. string.format('<span class="plainlinks">%s</span>', linkHtml)
end
return p