Module: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("&#35;", "#") -- 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