Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Women in Red event

Permanently protected module
From Wikipedia, the free encyclopedia

require('strict')
local p = {}
local data = mw.loadJsonData('Wikipedia:WikiProject Women in Red/events')
local currentDate = os.date('*t')
local lang = mw.getContentLanguage()
local current_frame = mw.getCurrentFrame()
local args = current_frame.args
local yesno = require('Module:Yesno')

local format_date = function(date2)
	local month = date2.month and date2.month-1 or 0
	return date2.year*12+month
end
local currentMonth = format_date(currentDate)

local monthName = function(month, abbrev)
	local monthcode = month<10 and ('0'..tostring(month)) or tostring(month)
	local timestamp = '2024' .. monthcode .. '01000000'
	local _format = abbrev and 'M' or 'F'
	return lang:formatDate(_format, timestamp)
end

local _findLatest = function()
	local _max = 1
	for n, _ in pairs(data) do
		local val = tonumber(n) or 0
		if val<1000 and val>_max then
			_max = val
		end
	end
	return _max
end
p.findLatest = function()
	return _findLatest() + (tonumber(args.add) or 0)
end

local natural_sort = function(m, n)
-- function to sort numerically and then alphabetically according to prefix, i.e. 2 < 20 < 20a < 20b < 21
	local last_digit = function(s) -- function to find last numerical digit of a string, i.e. for "20a" it will be 2.
		local i=0
		repeat
			i = i+1
		until i==#s or s:byte(i+1)>57
		return i
	end
	m, n = tostring(m), tostring(n)
	local ldm, ldn = last_digit(m), last_digit(n)
	if ldm>=1 and ldn>=1 then -- both strings have at least one numerical digit
		if tonumber(m:sub(1,ldm)) <= tonumber(n:sub(1,ldn)) then
			return true
		elseif tonumber(m:sub(1,ldm)) > tonumber(n:sub(1,ldn)) then
			return false			
		end
	end
	return m:sub(ldm+1) <= n:sub(ldn+1)
end

local value = function(n, field, default)
	local data2 = data[n] or data[tonumber(n)] -- json keys automatically encoded as numbers, for some reason
	if data2 and data2[field] and data2[field]~='' then
		return data2[field]
	else
		return default
	end
end

local makeCat = function(cat, key, test)
	local key2 = key and not test and ('|' .. key) or ''
	return '[[' .. (test and ':' or '') .. 'Category:' .. cat .. key2 .. ']]'
end

local collapse = function(title, text)
	return current_frame:expandTemplate{title = 'Collapsed top', args = {
			bg = 'transparent',
			width = '80%',
			[1] = title
		}} ..  text .. '</table></div>'
end

p.main = function()
	local ret = value(args[1], args[2], args[3] or '')
	if args.hash=='no' and type(ret)=='string' then
		ret = ret:gsub('#', '')
	end
	if ret and type(ret)=='string' and ret:sub(1,1)=='#' then -- prevent # character from starting a list
		ret = mw.text.nowiki(ret)
	end
	return ret
end

local getDates = function(n, endDefaultsToStart)
	local start2 = value(n, 'start') or {
		month = value(n, 'month'),
		year = value(n, 'year')
	}
	local end2 = value(n, 'end')
	if not end2 and endDefaultsToStart then
		end2 = start2
	end
	return start2, end2
end

local render_date = function(event, sentence, abbrev)
	local start2, end2 = getDates(event)
	local ret = ''
	if sentence then
		ret = end2 and 'from ' or 'in '
	end
	ret = ret .. (start2.month and monthName(start2.month, abbrev) or '')
	if not end2 or start2.year~=end2.year then
		ret = ret .. (start2.month and ' ' or '') .. tostring(start2.year)
	end
	if end2 then
		local to
		if sentence then
			to = ' to '
		else
			if start2.year==end2.year then
				to = '–' -- en dash
			else
				to = ' – ' -- spaced en dash
			end
		end
		ret = ret .. to .. monthName(end2.month, abbrev) .. ' ' .. tostring(end2.year)
	end
	local sortkey = os.time{
		year = start2.year or 0,
		month = start2.month or 1,
		day = start2.month and 2 or 1
	}
	return ret, sortkey
end
p.date = function()
	local event = tonumber(args[1])
	if data[event] then
		local date, _ = render_date(
			event,
			yesno(current_frame.args.sentence or 'false'),
			yesno(current_frame.args.abbrev or 'false')
		)
		return date
	end
end

p.invitation = function()
	local event = tonumber(args[1])
	if not event then
		local page = mw.title.getCurrentTitle().text
		event = tonumber(page:match('WikiProject Women in Red/Meetup/(%d+)'))
	end
	if data[event] then
		local start, _ = getDates(event)
		local anchor = start.month and ('#' .. monthName(start.month) .. ' ' .. start.year) or ''
		return '[[Wikipedia:WikiProject Women in Red/Outreach/' .. start.year 
			.. anchor
			.. '|WikiProject Women in Red/Outreach/' .. start.year
			.. ']]'
	end
end

local name = function(meetup)
	local series = value(meetup, 'series')
	local name2 = value(meetup, 'name')
	local out = series or ''
	if series and name2 then
		out = out .. ': '
	end
	out = out .. (name2 or '')
	return out
end
p.name = function()
	return mw.text.nowiki(name(args[1]))
end

p.nested = function()
-- Function to produce year for nested display in project banner
	local year = value(args[1], 'year')
	if not year and value(args[1], 'start') and value(args[1], 'start').year==value(args[1], 'end').year then
		year = value(args[1], 'start').year
	end
	return name(args[1]) .. (year and (' (' .. year .. ')') or '')
end

p.text = function()
	local meetup = args[1]
	return '[[Wikipedia:WikiProject Women in Red/Meetup/' .. meetup
		.. '|' .. name(meetup) 
		.. ' ' .. value(meetup, 'type', 'edit-a-thon')
		.. ']] '
		.. render_date(meetup, true, false)
end

local link = function(n, name2)
	local label = name2 and name(n) or n
	return '[[Wikipedia:WikiProject Women in Red/Meetup/' .. n .. '|' .. label .. ']]'
end

local buildTable = function(option, show)
	local meetupCell = function(n)
		local background = value(n, 'background') and ('#' .. value(n, 'background')) or nil
		return mw.html.create('th')
			:css('background-color', background)
			:css('text-align', 'center')
			:wikitext(n)
	end
	local total = 0
	option.meetup = true
	option.name = true
	option.date = true
	local header = mw.html.create('tr')
	local cols = 0
	for _, col in ipairs({'meetup', 'icon', 'series', 'name', 'date', 'image', 'template', 'wrapper', 'contrast', 'category'}) do
		if option[col] then
			cols = cols + 1
			local col2 = mw.html.create('th'):wikitext(lang:ucfirst(col))
			header:node(col2)
		end
	end
	local sorted_data = {}
	for n, _ in pairs(data) do
		table.insert(sorted_data, n)
	end
	table.sort(sorted_data, natural_sort)
	local showDate = function(meetup)
		local dates, sortkey = render_date(meetup, false, true)
		return mw.html.create('td'):attr('data-sort-value', sortkey):wikitext(dates):done()
	end
	local rows = {}
	for _, n in ipairs(sorted_data) do
		local event = data[n]
		if not show or show(n, event) then
			local row = mw.html.create('tr'):node(meetupCell(n))
			if option.icon then
				local icon = event.icon and event.icon~='' and ('[[File:' .. event.icon .. '|center|50px]]') or nil
				local cell = mw.html.create('td'):wikitext(icon)
				row:node(cell)
			end
			if option.series then
				local series = event.series and '[[:Category:WikiProject Women in Red ' .. event.series:gsub('#', '') .. ' articles|' .. event.series .. ']]' or ''
				local cell = mw.html.create('td'):wikitext(series)
				row:node(cell)
			end
			local name2 = option.series and event.name or name(n)
			local link2 = '[[Wikipedia:WikiProject Women in Red/Meetup/' .. n .. '|' .. name2 .. ']]'
			local name_cell = mw.html.create('td'):wikitext(link2)
			row:node(name_cell)
				:node(showDate(n))
			if option.image then
				local pic = event.image and event.image~='' and ('[[File:' .. event.image .. '|center|x50px|' .. (event.caption or '') .. ']]') or nil
				local cell = mw.html.create('td'):wikitext(pic)
				row:node(cell)
			end
			if option.template then
				local template = current_frame:extensionTag('code', '{{WIR|' .. n .. '}}')
				local cell = mw.html.create('td'):css('text-align', 'center'):wikitext(template)
				row:node(cell)
			end
			if option.wrapper then
				local wrapper = '{{[[:Template:WIR-' .. n .. '|WIR-' .. n .. ']]}}'
				local cell = mw.html.create('td'):css('text-align', 'center'):wikitext(wrapper)
				row:node(cell)
			end
			if option.contrast then
				local background = value(n, 'background')
				local colour, contrast
				if background then
					contrast = current_frame:expandTemplate{
						title = 'Color contrast ratio',
						args = {[1] = '#000000', [2] = background}
					}
					contrast = math.floor(contrast*10)/10
					colour = contrast<4.5 and 'FFCCCB' or contrast<=7 and 'FFFF8F' or 'D1FFBD'
				end
				local cell = mw.html.create('td')
					:css('text-align', 'center')
					:css('background-color', colour and ('#'..colour))
					:wikitext(contrast)
				row:node(cell)
			end
			if option.category then
				local cat = 'WikiProject Women in Red meetup ' .. n .. ' articles'
				local url = mw.uri.fullUrl(':Category:' .. cat, {
					action = 'edit',
					preload = 'Template:WIR category/preload',
					summary = 'Create meetup category for Women in Red'
				})
				local pagesInCat = mw.site.stats.pagesInCategory(cat)
				total = total + pagesInCat
				local catText = '[[' .. ':Category:' .. cat .. '|' .. pagesInCat .. ']]'
				if pagesInCat==0 and not mw.title.new(':Category:' .. cat).exists then
					catText = catText .. ' ([' .. tostring(url) .. ' create])'
				end
				local cell = mw.html.create('td'):css('text-align', 'center'):wikitext(catText)
				row:node(cell)
			end
		table.insert(rows, tostring(row))
		end
	end
	local footer
	if option.category then
		footer = mw.html.create('tr')
			:tag('th'):attr('colspan', cols-1):wikitext('Total'):done()
			:tag('th'):css('text-align', 'center'):wikitext(tostring(total)):done()
		end
	return mw.html.create('table')
		:addClass('wikitable'):addClass('sortable')
		:node(header)
		:node(table.concat(rows))
		:node(footer)
end
p.list = function()
	local first = tonumber(args.first) or 1
	local last = tonumber(args.last) or 9999
	if args.latest then
		last = _findLatest()
		first = last - tonumber(args.latest) + 1
	end
	local option = {}
	for key, val in pairs(args) do
		option[key] = yesno(val)
	end
	return buildTable(
		option,
		function(n, _)
			return natural_sort(first,n) and natural_sort(n,last)
		end
	)
end

local eventStatus = function(event, date2)
--function to decide whether an event is upcoming, current, ongoing, recently completed or past
	local month = currentMonth
	if date2 then
		month = format_date(date2)
	end
	local start2 = format_date(event['start'] or {
		year = event.year,
		month = event.month or 1
	})
	local end2 = format_date(event['end'] or {
		year = event.year,
		month = event.month or 12
	})
	if end2<month-1 then -- event ended before previous month
		return 'past'
	elseif end2==month-1 then -- event ended on previous month
		return 'recent'
	elseif start2==month then -- event has started on current month
		return 'current'
	elseif start2==month+1 then -- event starts after current month
		return 'upcoming'
	elseif start2<month then -- event starts before current month
		return 'ongoing'
	end
end

local searchEvents = function(date2)
	local events = {current = {}, recent = {}, ongoing = {}, upcoming = {}, past = {}}
	for n, event in pairs(data) do
		local status = eventStatus(event, date2)
		if status then
			table.insert(events[status], n)
		end
	end
	table.sort(events.past, natural_sort)
	return events
end

p.current_events = function(frame)
	local date2 = currentDate
	if frame.args[1] then
		date2 = getDates(frame.args[1])
	elseif frame.args.year and frame.args.year~='' then
		date2 = {
			year = tonumber(frame.args.year),
			month = tonumber(frame.args.month) or 1
		}
	end
	local mClickableButton = require('Module:Clickable button').main
	local button = function(text, colour)
		return mClickableButton{
			[1] = text,
			color = 'white',
			style = colour and ('background-color: #' .. colour)
		}
	end
	local links = function(text, events, extra)
		local list = {}
		if events[1] or extra then
			for _, n in ipairs(events) do
				table.insert(list, button(
					'[[Wikipedia:WikiProject Women in Red/Meetup/' .. n .. '|' .. name(n) .. ']]',
					value(n, 'background')
				) .. ' ')
			end
			if extra then
				table.insert(list, button(extra))
			end
			return mw.html.create('tr')
				:tag('td'):wikitext(text .. ': '):done()
				:tag('td'):wikitext(table.concat(list)):done()
		end
	end
	local events = searchEvents(date2)
	local ret = mw.html.create('table')
		:node(links(frame.args.recent or 'Recently completed', events.recent))
		:node(links(frame.args.current or monthName(date2.month) .. ' ' .. date2.year, events.current))
		:node(links(frame.args.ongoing or 'Ongoing initiatives', events.ongoing))
		:node(links(frame.args.upcoming or 'Upcoming events', events.upcoming, '[[Wikipedia:WikiProject Women in Red/Ideas|Ideas]]'))
	return tostring(ret)
end

p.events = function()
-- function to produce automated list of events on Wikipedia:WikiProject Women in Red/Events
	local ret = mw.html.create('div')
	local ec = searchEvents()
	local links = function(class)
		if not ec[class] or not args[class] then
			return nil
		end
		ret:tag('h3'):wikitext(args[class]):done()
		if ec[class][1] then
			ret:tag('ul')
			for _, n in ipairs(ec[class]) do
				local date2 = render_date(n, false, false)
				if date2==tostring(tonumber(date2)) then
					date2 = date2 .. ' year-long ' .. value(n, 'type', 'editathon')
				end
				ret:tag('li'):wikitext(date2 .. ': ' .. link(n, true)):done()
			end
		else
			ret:tag('p'):css('font-style', 'italic'):wikitext('None to display.'):done()
		end
	end
	links('ongoing')
	links('current')
	links('recent')
	links('upcoming')
	links('past')
	return tostring(ret)
end

p.event_list = function()
-- function to produce bulleted list of events for Template:Women in Red navigation
	local ec = searchEvents()
	local class = args[1]
	if not ec[class] then
		return nil
	end
	local ret = mw.html.create('ul')
	for _, n in ipairs(ec[class]) do
		ret:tag('li'):wikitext(link(n, true)):done()
	end
	return tostring(ret)
end

p.event_list_by_year = function()
-- function to produce bulleted list of past events for Template:Women in Red navigation
	local ret = mw.html.create('ul')
	local year = args[1] and tonumber(args[1]) or 2023
	local events = {}
	for n, event in pairs(data) do
		if (event.year==year or (event.start and event.start.year==year)) and eventStatus(event)=='past' then
			local type = event.type or 'edit-a-thon'
			if not events[type] then
				events[type] = {}
			end
			local month = value(n, 'month', 0)
			if not events[type][month] then
				events[type][month] = {}
			end
			table.insert(events[type][month], n)
		end
	end
	for type, eventsOfType in pairs(events) do
		ret:wikitext(lang:uc(type)..'S: ')
		for _, n in ipairs(eventsOfType[0] or {}) do
			ret:tag('li'):wikitext(link(n, true)):done()
		end
		for month = 1, 12 do
			if eventsOfType[month] then
				local sublist = mw.html.create('ul')
				for _, n in ipairs(eventsOfType[month]) do
					sublist:tag('li'):wikitext(link(n, true)):done()
				end
				ret:tag('li'):wikitext(monthName(month, true) .. ': '):node(sublist):done()
			end
		end
	end
	return tostring(ret)
end

p.category_header = function(frame)
	local pargs = frame:getParent().args
	local test = pargs.test
	local page = mw.title.getCurrentTitle().text
	local n = test and pargs.n or page:match('WikiProject Women in Red meetup (%w+) articles')
	local year = test and pargs.year or page:match('WikiProject Women in Red (%d+) articles')
	local year2 = test and pargs.year2 or page:match('WikiProject Women in Red in (%d+)')
	local series = test and pargs.series or page:match('WikiProject Women in Red (.+)')
	local cats, text, image_right, empty, nav
	local image_left = 'Women in Red logo.svg'
	local makeCatL = function(cat, key)
		return makeCat(cat, key, test)
	end
	if n then
		text = 'This category contains talk pages of articles which were created or improved'
			.. ' during the <b>[[Wikipedia:WikiProject Women in Red/Meetup/'
			.. n .. '|' .. name(n) .. ' ' .. value(n, 'type', 'edit-a-thon') .. ']]</b>'
			.. ' hosted by the [[Wikipedia:WikiProject Women in Red|Women in Red project]] '
			.. render_date(n, true, false) .. '.'
		cats = {makeCatL('WikiProject Women in Red articles by meetup', n)}
		local start2, end2 = getDates(n, true)
		for y = start2.year, end2.year do
			table.insert(cats, makeCatL('WikiProject Women in Red ' .. y .. ' articles', n))
		end
		if value(n, 'series') then
			table.insert(cats, makeCatL('WikiProject Women in Red ' .. value(n, 'series'):gsub('#', '') .. ' articles', n))
		end
		empty = mw.site.stats.pagesInCategory('WikiProject Women in Red meetup ' .. n .. ' articles')==0
		local future = false
		if start2.year>currentDate.year then
			future = true
		elseif start2.year==currentDate.year then
			if start2.month and start2.month>=currentDate.month then
				future = true
			end
		end
		image_left = value(n, 'icon', 'Women in Red logo.svg')
		image_right = value(n, 'image') and '[[File:' .. value(n, 'image') .. '|80px|' .. (value(n, 'caption') or '') .. ']]'
		empty = empty and future
		nav = tonumber(n) and true
	elseif year then -- for categories such as Category:WikiProject Women in Red 2025 articles
		year = tonumber(year)
		local inYear = function(_, event)
			if event.start and event['end'] then
				return event.start.year<=year and event['end'].year>=year
			else
				return event.year==year
			end
		end
		text = 'This category contains talk pages of articles which were created or improved in '
			.. year .. ' as part of the [[Wikipedia:WikiProject Women in Red|Women in Red project]].'
			.. collapse(
				'Articles improved during ' .. year,
				tostring(buildTable({category = true}, inYear))
			)
		cats = {
			makeCatL('WikiProject Women in Red in ' .. year, 'Articles'),
			makeCatL('WikiProject Women in Red articles by year', year)
		}
		nav = true
	elseif year2 then -- for categories such as Category:WikiProject Women in Red in 2025
		year2 = tonumber(year2)
		text = 'This category contains all meetups of the [[Wikipedia:WikiProject Women in Red|Women in Red project]] in ' .. year2 .. '.'
		cats = {
			makeCatL('Wikipedia meetups in ' .. year2, 'Women in Red'),
			makeCatL('WikiProject Women in Red by year', year2)
		}
		nav = true
	elseif series then
		series = series:gsub('%sarticles', ''):gsub('1day1woman', '#1day1woman')
		text = 'This category contains talk pages of articles which were created or improved as part of the ' .. series
			.. ' series of meetups hosted by the [[Wikipedia:WikiProject Women in Red|Women in Red project]].'
			.. collapse(
				'Articles improved during ' .. series .. ' events',
				tostring(buildTable(
					{category = true},
					function(_, event) return event.series==series end
				))
			)
		cats = {makeCatL('WikiProject Women in Red articles by series', series)}
	end
	local out = {frame:expandTemplate{title = 'cmbox', args = {
		image = '[[File:' .. image_left .. '|40px]]',
		text = text or '',
		imageright = image_right
	}}}
	table.insert(out, not(pargs.test) and nav and frame:expandTemplate{
		title = 'Category series navigation',
		args = {min = '1'}
	} or '')
	table.insert(out, frame:expandTemplate{title = 'CatAutoTOC'})
	table.insert(out, empty and frame:expandTemplate{
		title = 'Possibly empty category',
		args = {[1] = 'This category should be populated soon.'}
	} or '')
	table.insert(out, table.concat(cats or {}) or '')
	return table.concat(out)
end

p.addDateCategories = function()
	local n = tonumber(args[1])
	local test = yesno(args.test)
	local cats = {}
	local start2, end2 = getDates(n, true)
	local start_id, end_id = format_date(start2), format_date(end2)
	for year = start2.year, end2.year do
		table.insert(cats, makeCat('WikiProject Women in Red in ' .. year, n, test))
	end
	if start2.month and start_id+2>=end_id then -- event spans at most three months
		for monthid = start_id, end_id do -- add monthly categories
			local year = math.floor(monthid/12)
			local month = monthid - year*12 + 1
			table.insert(cats, makeCat('Wikipedia meetups in ' .. monthName(month) .. ' ' .. year, 'Women in Red', test))
		end
	else -- event spans more than three months, just use yearly categories
		for year = start2.year, end2.year do
			table.insert(cats, makeCat('Wikipedia meetups in ' .. year, 'Women in Red', test))
		end
	end
	if value(n, 'type')=='contest' then
		table.insert(cats, makeCat('Wikipedia competitions', test))
	end
	return table.concat(cats)
end

p.pinterest = function()
-- Function to generate the correct link to pinterest based on the month of the start of the event
	local event = tonumber(current_frame.args[1])
	if data[event] then
		local start = getDates(event)
		local month = monthName(start.month or 1)
		local label = month .. '-' .. start.year .. '-editathons'
		return '[https://www.pinterest.com/wikiwomeninred/' .. label .. ' ' .. label .. ']'
	end
end

p.planning = function()
-- Function to track progress when creating new monthly events
	local sections = {
		{title = 'Coordinator(s)', parameter = 'coordinator'},
		{title = 'Ongoing', default = 'ongoing'},
		{title = 'New'},
		{title = 'MassMessage sender', parameter = 'sender'},
		{title = 'Invitation'},
		{title = 'Announcements'},
		{title = 'Tip'},
		{title = 'Logo(s)', parameter = 'logo'}
	}
	local pargs = current_frame:getParent().args
	local okay = function(prog)
		local ready = prog and prog~='' and prog:lower()
		if ready then
			return '[[File:' .. (yesno(ready) and 'Green check' or 'Red x') .. '.svg|13px|link=]]'
		end
	end
	local caption
	local default = {}
	if pargs.month and tonumber(pargs.month) and pargs.year and tonumber(pargs.year) then
		caption = mw.html.create('caption'):wikitext('Planning for ' .. monthName(tonumber(pargs.month)) .. ' ' .. pargs.year)
		local events = searchEvents{month=pargs.month, year=pargs.year}
		default.ongoing = {}
		for _, event in ipairs(events.ongoing) do
			table.insert(default.ongoing, {n=event, stat='1'})
		end
	end
	local tab = mw.html.create('table'):addClass('wikitable'):node(caption)
	for _, sect in ipairs(sections) do
		local items = default[sect.default] or {}
		local s = pargs[sect.parameter or sect.title:lower()]
		if items~={} and s then
			for item in mw.text.gsplit(s, '%s*;%s*') do
				local new_item = {
					all = item:match('^([^<]*)'),
					n = item:match('^%s*(%d%d%d)'),
					stat = item:match('<(%w*)>')
				}
				if new_item.n and data[tonumber(new_item.n)]==nil then
					new_item.n = nil
				end
				table.insert(items, new_item)
			end
		end
		for i, item in ipairs(items) do
			local row = mw.html.create('tr')
			if i==1 then
				row:tag('th'):attr('rowspan', tostring(#items)):wikitext(lang:ucfirst(sect.title)):done()
			end
			if item.n then
				row:tag('td'):css('width', '40px'):css('text-align', 'center'):wikitext(item.n):done()
				row:tag('td'):wikitext(link(item.n, true)):done()
			else
				row:tag('td'):attr('colspan', '2'):wikitext(item.all):done()
			end
			row:tag('td'):wikitext(okay(item.stat)):done()
			tab:node(row)
		end
	end
	return tostring(tab)
end

return p