模块:Fa
外观
此模块的文档可以在Module:Fa/doc创建
local p = {}
--[[
===== 配置区 =====
--]]
-- FontAwesome 版本和类名映射
local FA_VERSIONS = {
[4] = { solid = "fa", regular = "fa", brands = "fa" },
[5] = { solid = "fas", regular = "far", brands = "fab" },
[6] = { solid = "fas", regular = "far", brands = "fab" },
[7] = { solid = "fa-solid", regular = "fa-regular", brands = "fa-brands" }
}
-- 样式缩写映射
local STYLE_MAP = {
s = "solid", solid = "solid",
r = "regular", regular = "regular",
b = "brands", brands = "brands",
l = "light", light = "light",
d = "duotone", duotone = "duotone"
}
-- 图标别名系统
local ICON_ALIASES = {
cog = "gear",
refresh = "sync",
trash = "trash-alt",
envelope = "envelope-o"
}
-- 默认无障碍标签
local DEFAULT_TITLES = {
question = "Help",
gear = "Settings",
rocket = "Rocket",
home = "Home",
search = "Search",
user = "User"
}
-- 安全规则配置
local SECURITY = {
max_size = "5em",
safe_colors = {
"red", "blue", "green", "black", "white",
"#333", "#666", "#999", "#ccc",
"rgb(255,255,255)", "rgb(0,0,0)"
},
safe_icons = {
"question", "gear", "rocket", "home",
"search", "user", "envelope", "bell"
}
}
--[[
===== 工具函数 =====
--]]
local function sanitize(text)
return text and tostring(text):gsub("[<>%c]", "") or nil
end
local function validate_color(color)
if not color then return nil end
-- HEX 颜色 (#fff or #ffffff)
if color:match("^#[0-9a-fA-F]{3,6}$") then
return color:lower()
end
-- RGB 颜色
if color:match("^rgb%(%d+%s*,%s*%d+%s*,%s*%d+%s*%)$") then
return color:gsub("%s+", "")
end
-- 颜色名称白名单
local safe_colors = {
red = true, blue = true, green = true,
black = true, white = true
}
return safe_colors[color:lower()] and color:lower() or nil
end
local function validate_size(size, strict_mode)
if not size then return nil end
-- 纯数字自动追加em单位
if size:match("^%d+%.?%d*$") then
size = size .. "em"
end
-- 带单位验证
local num, unit = size:match("^(%d+%.?%d*)(em|rem|px|%%)$")
if not num then return nil end
num = tonumber(num)
if strict_mode then
if unit == "%" then return math.min(num, 100) .. unit end
if unit == "px" then return math.min(num, 32) .. unit end
return math.min(num, 5) .. unit
end
return num .. unit
end
local function validate_icon(icon, strict_mode)
icon = sanitize(icon) or "question"
icon = ICON_ALIASES[icon] or icon
if strict_mode then
for _, v in ipairs(SECURITY.safe_icons) do
if v == icon then return icon end
end
return "question"
end
return icon:match("^[%w-]+$") and icon or "question"
end
--[[
===== 主函数 =====
--]]
function p.main(frame)
if not frame or not frame.args then
return '<span class="error">无效的模块调用</span>'
end
local args = frame.args
local parent_args = frame:getParent().args
-- 参数合并与处理
local config = {
icon = validate_icon(args.icon or parent_args.icon, args.mode == "strict"),
version = tonumber(args.version or parent_args.version or 7),
style = STYLE_MAP[args.style or parent_args.style or "s"] or "solid",
size = validate_size(args.size or parent_args.size, args.mode == "strict"),
color = validate_color(args.color or parent_args.color),
spin = args.spin or parent_args.spin,
title = sanitize(args.title or parent_args.title),
id = sanitize(args.id or parent_args.id),
class = sanitize(args.class or parent_args.class),
mode = args.mode or parent_args.mode or "enhanced"
}
-- 版本回退
config.version = FA_VERSIONS[config.version] and config.version or 7
-- 默认标题
config.title = config.title or DEFAULT_TITLES[config.icon]
-- 严格模式颜色检查
if config.mode == "strict" and config.color then
local safe = false
for _, v in ipairs(SECURITY.safe_colors) do
if v == config.color then safe = true; break end
end
if not safe then config.color = nil end
end
-- 构建CSS类
local classes = {
FA_VERSIONS[config.version][config.style],
"fa-" .. config.icon
}
if config.spin and config.spin ~= "false" then
table.insert(classes, "fa-spin")
end
if config.class then
for cls in config.class:gmatch("[%w-]+") do
table.insert(classes, cls)
end
end
-- 构建内联样式
local styles = {}
if config.size then table.insert(styles, "font-size:" .. config.size) end
if config.color then table.insert(styles, "color:" .. config.color) end
-- 生成HTML
local html = mw.html.create("span")
:attr("role", "img")
:css("display", "inline-flex")
:css("align-items", "center")
:tag("i")
:addClass(table.concat(classes, " "))
:attr("aria-hidden", "true")
:attr("title", config.title)
:attr("id", config.id)
:cssText(table.concat(styles, ";"))
if config.title then
html:done():tag("span")
:addClass("screen-reader-only")
:wikitext(config.title)
end
return tostring(html)
end
return p