Module:User contrib
Appearance
This module depends on the following other modules: |
This module uses TemplateStyles: |
For {{user contrib}}.
require('strict')
--[=[
Implementation logic for [[Template:User contrib]]
]=]
local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local greatercontrast = require('Module:Color contrast')['_greatercontrast']
local userbox = require('Module:Userbox').userbox
--[=[
Color scheme based on n
]=]
--[=[
Ensure a number is within a range
]=]
local function number_in_range(args)
if tonumber(args[1]) then
return math.min(math.max(tonumber(args[1]), args[2] or 0), args[3] or 1)
else
return nil
end
end
--[=[
Convert hex, HSL, or RBG color to RGB(A) table
]=]
local function color_to_rgb_table(c)
if c == nil or c == "" then
return nil
end
-- html '#' entity
c = c:gsub("#", "#")
-- whitespace
c = c:match('^%s*(.-)[%s;]*$')
-- unstrip nowiki strip markers
c = mw.text.unstripNoWiki(c)
-- lowercase
c = c:lower()
local ctable
-- convert from rgb
if mw.ustring.match(c, '^rgb%([%s]*[0-9][0-9]*[%s]*,[%s]*[0-9][0-9]*[%s]*,[%s]*[0-9][0-9]*[%s]*%)$') then
local R, G, B = mw.ustring.match(c, '^rgb%([%s]*([0-9][0-9]*)[%s]*,[%s]*([0-9][0-9]*)[%s]*,[%s]*([0-9][0-9]*)[%s]*%)$')
ctable = {tonumber(R), tonumber(G), tonumber(B)}
end
-- convert from rgba
if mw.ustring.match(c, '^rgb%([%s]*[0-9][0-9]*[%s]*,[%s]*[0-9][0-9]*[%s]*,[%s]*[0-9][0-9]*[%s]*,[%s]*[0-9][0-9]*[%s]*%)$') then
local R, G, B, A = mw.ustring.match(c, '^rgb%([%s]*([0-9][0-9]*)[%s]*,[%s]*([0-9][0-9]*)[%s]*,[%s]*([0-9][0-9]*)[%s]*,[%s]*([0-9][0-9]*)[%s]*%)$')
ctable = {tonumber(R), tonumber(G), tonumber(B), tonumber(A)}
end
-- convert from rgb percent
if mw.ustring.match(c, '^rgb%([%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*%)$') then
local R, G, B = mw.ustring.match(c, '^rgb%([%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*%)$')
ctable = {255*tonumber(R)/100, 255*tonumber(G)/100, 255*tonumber(B)/100}
end
-- convert from rgba percent
if mw.ustring.match(c, '^rgb%([%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*%)$') then
local R, G, B, A = mw.ustring.match(c, '^rgb%([%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*%)$')
ctable = {255*tonumber(R)/100, 255*tonumber(G)/100, 255*tonumber(B)/100, 255*tonumber(A)/100}
end
-- convert from hsl
if mw.ustring.match(c, '^hsl%([%s]*[0-9][0-9%.]*[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*,[%s]*[0-9][0-9%.]*%%[%s]*%)$') then
local H, S, L = mw.ustring.match(c, '^hsl%([%s]*([0-9][0-9%.]*)[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*,[%s]*([0-9][0-9%.]*)%%[%s]*%)$')
H, S, L = math.fmod(number_in_range({H, 0, 360}), 360), number_in_range({S}), number_in_range({L})
local C = (1 - math.abs(2*L - 1))*S
local X = C*(1 - math.abs(math.fmod(H/60, 2) - 1))
local M = L - C/2
local R, G, B = M, M, M
if H < 60 then
R = R + C
G = G + X
elseif H < 120 then
R = R + X
G = G + C
elseif H < 180 then
G = G + C
B = B + X
elseif H < 240 then
G = G + X
B = B + C
elseif H < 300 then
R = R + X
B = B + C
elseif H < 360 then
R = R + C
B = B + X
end
ctable = {255 * R, 255 * G, 255 * B}
end
-- convert from hex
-- remove leading # (if there is one) and whitespace
c = mw.ustring.match(c, '^[%s#]*([a-f0-9]*)[%s]*$')
-- split into rgb(a)
local cs = mw.text.split(c or '', '')
if #cs == 6 then
local R = 16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[2])
local G = 16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[4])
local B = 16*tonumber('0x' .. cs[5]) + tonumber('0x' .. cs[6])
ctable = {R, G, B}
elseif #cs == 3 then
local R = 16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[1])
local G = 16*tonumber('0x' .. cs[2]) + tonumber('0x' .. cs[2])
local B = 16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[3])
ctable = {R, G, B}
elseif #cs == 8 then
local R = 16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[2])
local G = 16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[4])
local B = 16*tonumber('0x' .. cs[5]) + tonumber('0x' .. cs[6])
local A = 16*tonumber('0x' .. cs[7]) + tonumber('0x' .. cs[8])
ctable = {R, G, B, A}
elseif #cs == 4 then
local R = 16*tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[1])
local G = 16*tonumber('0x' .. cs[2]) + tonumber('0x' .. cs[2])
local B = 16*tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[3])
local A = 16*tonumber('0x' .. cs[4]) + tonumber('0x' .. cs[4])
ctable = {R, G, B, A}
else
ctable = nil
end
return ctable
end
local function blend_two_colors(args)
local color1 = color_to_rgb_table(args[1] or "#ffffff")
local color2 = color_to_rgb_table(args[2] or "#ffffff")
local ratio = number_in_range({args[3]}) or 0.5
local blend = {}
for i = 1, math.min(#color1, #color2) do
blend[i] = color1[i] * ratio + color2[i] * (1 - ratio)
end
return "rgb(" .. table.concat(blend, ", ") .. ")"
end
local function bg_colors(n)
local max_n = 100000
local colors = {
"#000000", "#003208", "#006411", "#198616", "#39a11a", "#60b71e", "#8dc722", "#b7d828", "#dbea31", "#fffb3b",
"#e9e559", "#afdb63", "#7dd06e", "#54c279", "#3ab082", "#269988", "#197f8c", "#175a85", "#193079", "#1f0266",
"#000000", "#39154d", "#742975", "#b43c51", "#dd562f", "#fa730d", "#fd9719", "#ffb834", "#ffd76b", "#ffedb1",
"#e9d982", "#f0d17c", "#efbd7d", "#e8a184", "#dd8590", "#cf719d", "#bc6cac", "#a679be", "#8c94d2", "#71b1e5",
"#9ccff1", "#cbe5d4", "#e8f2b2", "#f3ef9e", "#f4e097", "#f1cb98", "#eab7a1", "#dfa8be", "#d097ee", "#c278f0"
}
local color_index = math.floor((n % (max_n/2)) * #colors / (max_n/2)) + 1
local base_color
if n < max_n then
base_color = colors[color_index]
else
base_color = "#ffcc33"
end
local blended_color = blend_two_colors({base_color, "#ffffff", 0.5})
if n < max_n/2 then
return {
['id_bg'] = blended_color,
['info_bg'] = base_color
}
else
return {
['id_bg'] = base_color,
['info_bg'] = blended_color
}
end
end
--[=[
Light mode or dark mode based on background color
]=]
local function light_dark_class(bg)
local font = greatercontrast({bg})
if font == "#FFFFFF" then
return "darkmode"
elseif font == "#000000" then
return "lightmode"
else
return ""
end
end
--[=[
Info message
]=]--
local function info_message(args)
local n = args.n
local bot = yesno(args.bot) or false
local is_log = yesno(args.is_log) or false
local link = args.link
local username = mw.uri.encode(args.username or mw.title.getCurrentTitle().baseText, "WIKI")
local actionlink = args.actionlink
local display_n = args.display_n
local lang = args.lang
local deleted = args.deleted
local articles = args.articles
local distinct = args.distinct or args.unique
local images = args.images
local cur_images = args.cur_images or args['cur-images']
local insane = yesno(args.insane) or false
local total = yesno(args.total) or false
local action1 = "user has made"
if bot then
action1 = "bot has logged"
elseif is_log then
action1 = "user has logged"
end
local actionlink = link or "https://xtools.wmflabs.org/ec/en.wikisource.org/" .. username
local action2 = "contributions to"
if n == 1 and is_log then
action2 = "action on"
elseif is_log == "yes" then
action2 = "actions on"
elseif n == 1 then
action2 = "contribution to"
end
local actionlink_text = "[" .. actionlink .. " at least '''" .. display_n .. "''' " .. action2 .. "]"
local project_name = "Wikisource"
if lang then
project_name = "the " .. lang .. " Wikisource"
end
local message = "This " .. action1 .. " " .. actionlink_text .. " " .. project_name
if deleted then
message = message .. ", at least '''" .. deleted .. "''' of which were to pages that are now deleted"
end
if articles then
if deleted then
message = message .. " and"
else
message = message .. ","
end
message = message .. " at least '''" .. articles .. "''' of which were to articles"
end
if distinct then
if deleted or articles then
message = message .. ","
end
message = message .. " on at least '''" .. distinct .. "''' pages"
end
if images then
message = message .. ", including at least '''" .. images .. "''' images"
if cur_images then
message = message .. ", at least '''" .. cur_images .. "''' of which are still current"
end
elseif cur_images then
message = message .. ", including at least '''" .. cur_images .. "''' images which are still current"
end
if insane then
if images then
message = message .. ","
end
message = message .. " and, as a result, may be slightly insane"
end
message = message .. "."
if total then
message = message .. " [https://en.wikisource.org/w/api.php?action=query&list=users&usprop=editcount&ususers=" .. username .. " '''(total)''']"
end
return message
end
--[=[
Make userbox
]=]
function p._user_contrib(args)
args.n = args.n or args[1]
local n = math.max(tonumber(args.n) or 0, 0) -- everyone has made at least zero edits
if not yesno(args.format_n or args['format'] or true) then
args.display_n = args.n or ""
else
args.display_n = n
end
args.n = n
local id_bg = args.id_bg or args['id-bg'] or bg_colors(n).id_bg
local info_bg = args.info_bg or args['info-bg'] or bg_colors(n).info_bg
local id_s
if mw.ustring.len(args.display_n) > 4 then
id_s = 9
else
id_s = 10
end
local assignments = {
['border-c'] = args.border,
['id'] = args.display_n .. "+",
['id-c'] = id_bg,
['id-fc'] = args.id_font or args['id-font'] or greatercontrast({[1] = id_bg}),
['id-s'] = id_s,
['id-op'] = "white-space:nowrap;",
['id-class'] = "user-contrib-id plainlinks neverexpand " .. light_dark_class(id_bg),
['info'] = info_message(args),
['info-c'] = info_bg,
['info-fc'] = args.info_font or args['info-font'] or greatercontrast({[1] = info_bg}),
['info-s'] = 8,
['info-class'] = "user-contrib-info plainlinks neverexpand " .. light_dark_class(info_bg),
['float'] = args.float
}
return userbox(assignments)
end
function p.user_contrib(frame)
return p._user_contrib(getArgs(frame))
end
return p