Module:Speedy

local getArgs = require("Module:Arguments").getArgs local pageType = require("Module:Pagetype") local mbox = require("Module:Message box") local yesno = require("Module:Yesno") local button = require('Module:Clickable button 2') local preview = require('Module:If preview') local p = {} local config = mw.loadData('Module:Speedy/config') local timeAgo = require('Module:Time ago')

-- message function from Module:Documentation

local fillStringWithArgs local function message(cfgKey, valArray, expectType) --	-- Gets a message from the cfg table and formats it if appropriate.	-- The function raises an error if the value from the cfg table is not	-- of the type expectType. The default type for expectType is 'string'.	-- If the table valArray is present, strings such as $1, $2 etc. in the	-- message are substituted with values from the table keys [1], [2] etc.	-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',	-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."	-- local msg = config.messages[cfgKey] expectType = expectType or 'string' if type(msg) ~= expectType then error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)	end if not valArray then return msg end return fillStringWithArgs(msg, valArray) end

function fillStringWithArgs(text, valArray) if not valArray then return text end

local function getVal(match) match = tonumber(match) return valArray[match] or '' end

return mw.ustring.gsub(text, '$([1-9][0-9]*)', getVal) .. '' end

local function detectParameters(text) return text and mw.ustring.find(text, '$([1-9][0-9]*)') and true or false end

local function makeUnorderedList(array) local ul = mw.html.create('ul') for k,v in pairs(array) do		local li = ul:tag('li') li:wikitext(v) li:done end ul:allDone return tostring(ul) end

local function makeWikiList(array) local out = '' for k,v in pairs(array) do out = out .. '* ' .. v .. '\n' end return out end

-- Argument processing (from Module:Documentation)

local function makeInvokeFunc(funcName) return function (frame) local args = getArgs(frame, {			valueFunc = function (key, value)				if type(value) == 'string' then					value = value:match('^%s*(.-)%s*$') -- Remove whitespace.					if key == 'heading' or value ~= '' then						return value					else						return nil					end				else					return value				end			end		}) return p[funcName](args) end end

-- Miscellaneous functions related to speedy deletion

local function getDeletionEntry(code) return config.deletionCodes[code] end

local function yn(input, default) local res = yesno(input, nil) if (res == nil) then return default else return res end end

local function processDeletionArgs(iparams) local args = { deletionReasons = {}, deletionReasonsNotice = {}, entries = {}, numberOfEntries = 0, hideButton = true, highestMessage = 0, drv = false, willProvide = false, hide = false, blank = false }	local entry = nil local replaceParams = false local params = {iparams.page or mw.title.getCurrentTitle.fullText} local paramNo = 2 local skipped = true local function cleanupLeftover(v) if entry then table.insert(args.deletionReasons, ' ' .. fillStringWithArgs(entry.description, params) .. '. ' .. (entry.more and ' ' .. entry.more .. ' ' or '') .. ' ' .. message('deleteIntroCriteriaLink', {entry.code}) .. '. ') table.insert(args.deletionReasonsNotice, fillStringWithArgs(entry.description, params) .. ' (CSD ' .. entry.code .. '). ' .. (entry.additionalMessage and fillStringWithArgs(entry.additionalMessage, params) or '')) table.insert(args.entries, entry) else if (v ~= '') then table.insert(args.deletionReasons, v)				table.insert(args.deletionReasonsNotice, v)			end table.insert(args.entries, {}) end params = {iparams.page or mw.title.getCurrentTitle.fullText} paramNo = 2 args.customHeader = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customHeader or args.customHeader args.customIntro = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntro or args.customIntroDeleted args.customIntroDeleted = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntroDeleted or args.customIntroDeleted args.customCloser = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customCloser or args.customCloser args.numberOfEntries = args.numberOfEntries + 1 args.hideButton = entry and (args.hideButton and (entry.notice or 2) == 0) or false args.highestMessage = entry and ((entry.notice or 2) >= args.highestMessage) and entry.notice or args.highestMessage args.drv = entry and (args.drv or entry.drv) or args.drv args.willProvide = entry and entry.willProvide or args.willProvide args.hide = entry and (entry.hide or args.hide) or args.hide args.blank = entry and (entry.blank or args.blank) or args.blank end for k,v in ipairs(iparams) do		if type(k) == type(1) then skipped = false if (replaceParams) then local pName = fillStringWithArgs(entry and entry.inputFormat[paramNo - 1] or '$2', {iparams.page or mw.title.getCurrentTitle.fullText, v}) paramNo = paramNo + 1 table.insert(params, pName) else entry = getDeletionEntry(v) or nil replaceParams = entry and detectParameters(entry.description) or false end if not replaceParams then cleanupLeftover(v) end end end if replaceParams then cleanupLeftover('') end if skipped then args.hideButton = false end args.help = yn(iparams.help, true) args.nocat = yn(iparams.nocat, false) args.bot = yn(iparams.bot, false) args.noHeader = yn(iparams.noheader, false) args.additionalNote = iparams.additionalnote args.pageName = iparams.page args.notice = yn(iparams.notice, false) args.date = iparams.date or mw.getCurrentFrame:preprocess('') return args end

local isSubstituted = mw.isSubsting;

-- Entry point

p.main = makeInvokeFunc('_main') function p._main(params) -- get page local args = processDeletionArgs(params) local out = '' if args.notice then -- we are handling a note for the talk page, not a note for deletion if not args.pageName then return preview._warning({'No page name specified. Proceeding will do nothing.'}) end if not args.noHeader then out = out .. (args.customHeader and '== ' .. fillStringWithArgs(args.customHeader, {args.pageName}) .. ' ==' or "== " .. message("noticeHeader", {args.pageName}) .. " ==") .. '\n' end local messageType = args.highestMessage == 1 and 'welcome' or 'notice' out = out .. (message('level' .. args.highestMessage .. 'icon') ==  and  or '40px ') if mw.title.new(args.pageName).exists then -- if the page exists then show the "page may be deleted" message. if args.customIntro then out = out .. fillStringWithArgs(args.customIntro, {					args.pageName,					args.deletionReasonsNotice[1]				}) .. '\n\n' elseif args.numberOfEntries == 1 then out = out .. message(messageType .. 'Message', {					args.pageName,					args.deletionReasonsNotice[1]				}) .. '\n\n' elseif args.numberOfEntries > 1 then out = out .. message(messageType .. 'MessageMultiple', {					args.pageName,					'\n' .. makeWikiList(args.deletionReasonsNotice)				}) .. '\n' else out = out .. message(messageType .. 'MessageMultiple', {					args.pageName,					message("seePageForWhy")				}) .. '\n\n' end if args.customCloser then out = out .. args.customCloser else -- this ifexist checks if the page has possibly been deleted or moved -- ifexist is expensive so it may return false from time to time, even if the page exists -- fortunately this is handled in the message by saying "appears" rather than "has been" out = out .. ''			end else -- if the page does not exist then show the "page has been deleted" message if args.customIntroDeleted then out = out .. fillStringWithArgs(args.customIntroDeleted, {					args.pageName,					args.deletionReasonsNotice[1]				}) .. '\n\n' elseif args.numberOfEntries == 1 then out = out .. message(messageType .. 'MessageDeleted', {					args.pageName,					args.deletionReasonsNotice[1]				}) .. '\n\n' elseif args.numberOfEntries > 1 then out = out .. message(messageType .. 'MessageDeletedMultiple', {					args.pageName,					'\n' .. makeWikiList(args.deletionReasonsNotice)				}) .. '\n\n' else out = out .. message(messageType .. 'MessageDeletedMultiple', {					args.pageName,					message("seePageForWhy")				}) .. '\n\n' end if args.customCloser then out = out .. args.customCloser else out = out .. message('deletedAfterMessage') if not args.hideButton then out = out .. message('closingWarning', {message('recreateWarning')}) .. ' ' end if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageShouldNotHaveBeenDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end end end return mw.getCurrentFrame:preprocess(out) else -- we are handling a deletion template message if isSubstituted then -- if substituted then just prefill parameters -- simpler than the unsubst module because this just autofills the parameters then returns the text needed for the specific deletion template local out = '' return out else local titleOfPage = mw.title.getCurrentTitle local pt = pageType._main({page = titleOfPage.fullText}) local introPrefixToUse = args.bot and 'bot' or 'delete' local mainNotice = mw.html.create('div') mainNotice:wikitext('\n') local intro = mw.html.create('span') intro:css{["font-style"] = "italic", ["font-weight"] = "bold"} if args.numberOfEntries == 1 then intro:wikitext(message(introPrefixToUse .. 'Intro', { pt, args.deletionReasons[1] }))			elseif args.numberOfEntries > 1 then intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', { pt, makeUnorderedList(args.deletionReasons) }))			else intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', { pt, message("noReasonWarning") }))			end intro:allDone mainNotice:wikitext(tostring(intro)) if args.additionalNote then mainNotice:wikitext('\n\n' .. message('additionalNote', { args.additionalNote }))			end if args.hideButton then mainNotice:wikitext('\n\n' .. message("removeNoticeNoButton", {pt})):allDone else mainNotice:wikitext('\n\n' .. 					message("removeNotice", { pt, message('removeNoticeWarning', {							titleOfPage.isTalkPage and message('checkBelow') or  .. message('visitTheTalkPage') .. 						}) })				):done local contestButton = mw.html.create('div') contestButton:css{['margin-left'] = 'auto', ['margin-right'] = 'auto', ['text-align'] = 'center'} if args.numberOfEntries == 1 then contestButton:wikitext(button.main({ message("contestButton"), class="mw-ui-progressive", url="'					})):done				else					contestButton:wikitext(button.main({						message("contestButton"),						class="mw-ui-progressive",						style="text-align:center;",						url="' })):done end mainNotice:wikitext('\n\n' .. tostring(contestButton)) mainNotice:wikitext('\n\n' .. message("deleteCloser", { pt, titleOfPage.isTalkPage and message("deleteCloserProvidedBelowNotice") or message("deleteCloserProvidedOnTalkPage") })):done if (args.help) then local templateCall = ' ' mainNotice:wikitext('\n\n' .. message('deleteNoticeTemplate', {templateCall})) end local hangOn = mw.html.create('span') if titleOfPage.talkPageTitle.exists then hangOn:addClass('sysop-show') hangOn:wikitext( 						message("hangOnAdmin", { pt, titleOfPage.isTalkPage and message("checkBelow") or message("hangOnTalkPage") })					):done else hangOn:wikitext(message('hangOn', {pt})):done end mainNotice:wikitext('\n\n' .. tostring(hangOn)) end if args.numberOfEntries == 1 then if args.entries[1].notes then mainNotice:wikitext(args.entries[1].notes):done end end local deleteReasonSummary = '' if args.numberOfEntries > 1 then deleteReasonSummary = 'Multiple criteria: ' local isFirst = true for k,v in pairs(args.entries) do deleteReasonSummary = v.code and deleteReasonSummary .. (isFirst and '' or ', ') ..  .. v.code ..  or '' isFirst = false end elseif args.numberOfEntries == 1 then deleteReasonSummary = args.entries[1].code and deleteReasonSummary ..  .. args.entries[1].code ..  or '' end if deleteReasonSummary == '' then deleteReasonSummary = 'Speedy' end local adminMessage = mw.html.create('span') adminMessage:addClass('sysop-show') adminMessage:wikitext(				message(args.bot and 'checkBot' or 'check', {args.bot and message('check', {deleteReasonSummary}) or deleteReasonSummary} )			):done mainNotice:wikitext('\n\n' .. tostring(adminMessage)) local lastEditUser = mw.getCurrentFrame:callParserFunction('REVISIONUSER', titleOfPage.fullText) local editDate = mw.getCurrentFrame:callParserFunction('#time', 'H:i, j F Y', mw.getCurrentFrame:callParserFunction('REVISIONTIMESTAMP', titleOfPage.fullText)) mainNotice:wikitext(' ' .. message('lastEdited', {  .. lastEditUser .. , ' [ ' .. editDate .. '] ',				timeAgo.main({editDate}) }))			mainNotice:wikitext('\n') mainNotice:allDone out = out .. tostring(mainNotice) -- categorize out = out ..  .. (args.nocat and ':' or ) .. 'Category:' .. message('defaultCategory') .. '' for k,v in pairs(args.entries) do				local categorizeTime = mw.getCurrentFrame:preprocess('') local currentTime = mw.getCurrentFrame:preprocess('') if currentTime + 0 >= categorizeTime + 0 then for l,w in pairs(v and v.categories or {}) do out = out ..  .. (args.nocat and ':' or ) .. 'Category:' .. w .. '' end end end local deletionBoxArgs = { type = "speedy", text = mw.getCurrentFrame:preprocess(out), style = "font-size:95%;word-break:break-word;", image = message('level' .. args.highestMessage .. 'icon') == '' and 'none' or '40px' }			local deletionBox = mbox.main('mbox', deletionBoxArgs) local blankedBox = '' local hiddenBox = '' local blanked = '' local hidden = '' if args.blank then blanked = blanked .. message('blanked') if mw.getCurrentFrame:preprocess('') + 0 >= 35 then blanked = blanked .. '  .. message('pleaseBlank') .. '				end local blankedBoxArgs = { type = "notice", text = blanked, style = "word-break:break-word;" }				blankedBox = mbox.main('mbox', blankedBoxArgs) end if args.hide then hidden = hidden .. message('hidden') local hiddenBoxArgs = { type = "notice", text = hidden, style = "word-break:break-word;" }				hiddenBox = mbox.main('mbox', hiddenBoxArgs) end if args.hide and not args.nocat then hiddenBox = hiddenBox .. ' '			end return deletionBox .. blankedBox .. hiddenBox end end end

p.makeTable = makeInvokeFunc('_makeTable') function p._makeTable(args) local usedCodes = {} local tb = mw.html.create("table") tb:addClass('wikitable') local th = tb:tag('tr') th:tag('th'):wikitext('Code'):done th:tag('th'):wikitext('Aliases'):done th:tag('th'):wikitext('Criterion'):done th:tag('th'):wikitext('Name'):done th:tag('th'):wikitext('Description'):done for k,v in pairs(config.deletionReasonsSorting) do		local entry = getDeletionEntry(v) if entry then if not usedCodes[v] then local tr = tb:tag('tr') tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(' ')):done local aliasStr = '' for _,alias in pairs(entry.aliases) do aliasStr = aliasStr .. ' '					usedCodes[alias] = true end tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(aliasStr)) tr:tag('td'):wikitext( .. entry.code .. ):done tr:tag('td'):wikitext(entry.name):done tr:tag('td'):wikitext(entry.description):done usedCodes[v] = true end end end for k,entry in pairs(config.deletionCodes) do		if not usedCodes[k] then local tr = tb:tag('tr') tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(' ')):done local aliasStr = '' for _,alias in pairs(entry.aliases) do aliasStr = aliasStr .. ' '				usedCodes[alias] = true end tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(aliasStr)) tr:tag('td'):wikitext( .. entry.code .. ):done tr:tag('td'):wikitext(entry.name):done tr:tag('td'):wikitext(entry.description):done usedCodes[k] = true end end return tostring(tb) .. '' end

p.makeTableWithExamples = makeInvokeFunc('_makeTableWithExamples') function p._makeTableWithExamples(args) local usedCodes = {} local tb = mw.html.create("table") tb:addClass('wikitable') local th = tb:tag('tr') th:tag('th'):css{position = "sticky", top = 0, left = 0}:wikitext('Codes'):done --th:tag('th'):wikitext('Criterion'):done th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion message'):done th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion notice'):done for k,v in pairs(config.deletionReasonsSorting) do		local entry = getDeletionEntry(v) if entry then if not usedCodes[v] then local tr = tb:tag('tr') local aliasStr = '' for _,alias in pairs(entry.aliases) do aliasStr = aliasStr .. ' '					usedCodes[alias] = true end tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame:preprocess(' ' .. aliasStr)):done --tr:tag('td'):wikitext( .. entry.code .. ):done tr:tag('td'):wikitext(' ' .. mw.getCurrentFrame:preprocess('' .. ' ' )):done tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(' ' .. '' .. ' ')):done usedCodes[v] = true end end end for v,entry in pairs(config.deletionCodes) do		if not usedCodes[v] then local tr = tb:tag('tr') local aliasStr = '' for _,alias in pairs(entry.aliases) do aliasStr = aliasStr .. ' '				usedCodes[alias] = true end tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame:preprocess(' \n' .. aliasStr)):done --tr:tag('td'):wikitext( .. entry.code .. ):done tr:tag('td'):wikitext(' ' .. mw.getCurrentFrame:preprocess('' .. ' ' )):done tr:tag('td'):wikitext(mw.getCurrentFrame:preprocess(' ' .. '' .. ' ')):done usedCodes[v] = true end end return tostring(tb) .. '' end

return p