Jump to content

Module:Clickable button/sandbox2

From Wikipedia, the free encyclopedia
-- [[w:en:Module:Clickable button]]
-- Builds Codex's button component. See [[wmdoc:codex/latest]].
-- Implements [[Template:Clickable button]] and others.
-- To add icons: [[Template:Clickable button/styles.css]].

-- TRACKING CATEGORIES: [[Category:Pages using clickable dummy button]]
-- [[Category:Pages using clickable button with external links]]
-- [[Category:Pages using clickable button with outdated classes]]
-- unless nocat= is true value. Adds category= any custom category regardless of nocat=.

-- DEPENDENCIES:
local yesno = require('Module:Yesno')
-- [[Special:Version]] must include @wikimedia/codex.
-- [[Template:Clickable button/styles.css]]
-- [[Module:Yesno]] [[Module:Arguments]]
-- [[Module:Check for unknown parameters]]

local p = {}

-- Cleans up and validates a URL.
-- Copied from [[en:Module:URL]] with minor modifications.
local function safeUri(s)
	local success, uri = pcall(function()
		return mw.uri.new(s)
	end)
	if success then
		return uri
	end
end

local function extractUrl(extract)
	local url = extract
	url = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])',
		'http%1://%3')
	local uri = safeUri(url);
	if uri and uri.host then
		return url
	end
end

local function _url(url, text)
	url = mw.text.trim(url or '')
	text = mw.text.trim(text or '')

	if url == '' then
		return text
	end

	-- If the URL contains any unencoded spaces, encode them, because MediaWiki will otherwise interpret a space as the end of the URL.
	url = mw.ustring.gsub(url, '%s', function(s)
		return mw.uri.encode(s,
			'PATH')
	end)

	-- If there is an empty query string or fragment ID, remove it as it will cause mw.uri.new to throw an error
	url = mw.ustring.gsub(url, '#$', '')
	url = mw.ustring.gsub(url, '%?$', '')

	-- If it's an http(s) URL without the double slash, fix it.
	url = mw.ustring.gsub(url, '^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])', 'http%1://%3')

	local uri = safeUri(url)

	-- Handle URL's without a protocol and URL's that are protocol-relative,
	-- e.g. www.example.com/foo or www.example.com:8080/foo, and //www.example.com/foo
	if uri and (not uri.protocol or (uri.protocol and not uri.host)) and url:sub(1, 2) ~= '//' then
		url = 'http://' .. url
		uri = safeUri(url)
	end

	if text == '' then
		if uri then
			if uri.path == '/' then uri.path = '' end

			local port = ''
			if uri.port then port = ':' .. uri.port end

			text = mw.ustring.lower(uri.host or '') .. port .. (uri.relativePath or '')

			-- Add <wbr> before _/.-# sequences
			text = mw.ustring.gsub(text, "(/+)", "<wbr/>%1") -- This entry MUST be the first. "<wbr/>" has a "/" in it, you know.
			text = mw.ustring.gsub(text, "(%.+)", "<wbr/>%1")
			-- text = mw.ustring.gsub(text,"(%-+)","<wbr/>%1") 	-- DISABLED for now
			text = mw.ustring.gsub(text, "(%#+)", "<wbr/>%1")
			text = mw.ustring.gsub(text, "(_+)", "<wbr/>%1")
		else -- URL is badly-formed, so just display whatever was passed in
			text = url
		end
	end

	return url, text
end

local function url(URL, TEXT)
	local s = URL or ''
	local text = TEXT or ''
	s = s or extractUrl(s) or extractUrl(text) or ''
	-- Strip out HTML tags and [ ] from URL
	s = (s or ''):gsub("<[^>]*>", ""):gsub("[%[%]]", "")
	-- Truncate anything after a space
	s = s:gsub("%%20", " "):gsub(" .*", "")
	return _url(s, text)
end

-- Renders tracking categories based on parameters.
local function renderTrackingCategories(args)
	local categories = ''
	local class = args.class and args.class:lower() or ''
	---- local check_for_unknown_parameters = require("Module:Check for unknown parameters")._check
	---- local title = mw.title.getCurrentTitle()

	-- Don't add categories if nocat=yes, but still add any custom category.
	-- Custom category passed in
	if args.category and yesno(args.nocat) == false then
		local q = args.category
		q = q:gsub('%[%[', ''):gsub('%]%]', ''):gsub('[Cc]ategory:', '')
		categories = categories .. '[[' .. 'Category:' .. q .. ']]'
	end
	if yesno(args.nocat) == true then
		return ''
	end
	mw.logObject(args, categories) -- DEBUG

	--[=[
	categories = categories .. check_for_unknown_parameters({
		checkpositional = "y",
		ignoreblank = "y",
		regexp1 = "header[%d]+",
		regexp2 = "label[%d]+",
		regexp3 = "data[%d]+[abc]?",
		regexp4 = "class[%d]+[abc]?",
		regexp5 = "rowclass[%d]+",
		regexp6 = "rowstyle[%d]+",
		regexp7 = "rowcellstyle[%d]+",
		unknown = "[[Category:Pages using infobox3cols with undocumented parameters|_VALUE_" .. title.text .. "]]",
		'class', 'color', 'weight', 'size', 'icon', 'link', 'url', 'disabled',
		'label', 'aria-label', 'arialabel', 'aria_label', 'action', 'nocat',
		'category', '1', '2'
		}, args) ]=]

	-- @TODO: whether all dummy buttons should get the dummy-category,
	-- or only those without disabled/ariaDisabled?
	if ((not args.link and not args.url
		and not args.disabled and not args.ariaDisabled)) or
		(args.link or args.url) and (not args.label) then
		-- Dummy button == no link, no url and not disabled
		-- OR link/url but no visible label
		categories = categories .. '[[Category:Pages using clickable dummy button]]'
	elseif (not args.link and not args.url
			and (args.disabled or args.ariaDisabled)) then
		-- Disabled button
		categories = categories .. '[[Category:Pages using disabled dummy button]]'
	end
	if class == 'ui-button-green'
		or class == 'ui-button-blue'
		or class == 'ui-button-red'
		or class == 'mw-ui-progressive'
		or class == 'mw-ui-destructive' then
		categories = categories .. '[[Category:Pages using clickable button with outdated classes]]'
	end
	if args.url then
		categories = categories .. '[[Category:Pages using clickable button with external links]]'
	end
	if class == 'mw-ui-constructive' then
		categories = categories .. '[[Category:Pages using clickable button with deprecated parameters]]'
	end
	mw.logObject(categories)
	return categories
end

local function renderLink(data)
	-- Build span tag
	local span = mw.html.create('span')
	for _, class in ipairs(data.classes or {}) do
		span:addClass(class)
	end
	span:attr('role', 'button')
	if data.aria_label then
		span:attr('aria-label', data.aria_label)
	end
	-- ARIA disabled attribute for disabled/no-link/dummy buttons
	if data.disabled or data.ariaDisabled then
		span:attr('aria-disabled', 'true')
	elseif data.disabled == false then
		span:attr('aria-disabled', 'false')
	end
	if data.iconSpan then
		span:node(data.iconSpan)
	end
	if data.label then
		span:wikitext(data.label)
		--[[ span:node(mw.html.create('span')
			:addClass('cdx-button--text')
			:wikitext(data.label)) ]]
	end

	local display = tostring(span)

	if data.disabled then
		return string.format('%s %s', display, data.categories)
	end
	if data.isUrl then
		---- mw.logObject(data)
		return string.format('<span class="plainlinks">[%s %s]</span> %s',
			data.url, display, data.categories)
	elseif data.isUrl == false then
		return string.format('[[%s|%s]] %s', data.link, display, data.categories)
	else
		-- No url/link provided
		return string.format('%s %s', display, data.categories)
	end
end

-- Backward compatibility for deprecated parameters
local function parseParameters(args)
	-- It's weird that we may make a link a label above, but if we truly
	-- only got 1=, then that would mean it's intentional to make both the link
	-- and label the same.
	-- Ensure label is set from priority: label= > 2= > 1=
	args.label = args.label or args[2] or args[1]

	-- Disable if link=no or disabled=1
	args.disabled = (yesno(args.link) == false)
		or yesno(args.disabled)
	args.link = args.link or args[1]

	-- Remove positional args after assigning
	args[1] = nil
	args[2] = nil

	-- Make aria-disabled=true later on if no link
	if (args.link and yesno(args.link) ~= false) or args.url then
		args.ariaDisabled = false
	else
		-- If no link whatsoever, make dummy button.
		-- But for accessibility, ARIA must know it won't do anything.
		args.ariaDisabled = true
		-- OPTION to forcefully disable dummy buttons.
		---- args.disabled = true
	end

	-- Normalize ARIA label keys
	args.aria_label = args.aria_label or args['aria-label'] or args.arialabel

	-- Determine action from old parameters color/class
	local color = type(args.color) == 'string' and args.color:lower()
	local class = type(args.class) == 'string' and args.class:lower()
	if (color == "blue"
			or color == "green"
			or class == 'ui-button-green'
			or class == 'ui-button-blue'
			or class == 'mw-ui-constructive'
			or class == 'mw-ui-progressive'
			or class == 'progressive') then
		args.action = "progressive"
		args.class = nil
	elseif (color == "red"
			or class == 'ui-button-red'
			or class == 'mw-ui-destructive'
			or class == 'destructive') then
		args.action = "destructive"
		args.class = nil
	end

	return args
end

-- Constructs attributes for the HTML elements.
local function makeLinkData(args)
	local data = {}

	-- Decide link vs. url vs. none
	-- URL has priority over link if both provided. Also, clean URL
	if args.url then
		data.isUrl = true
		-- Make pretty URL and label based on URL if no label.
		--[[ local URI = require "URI"
		local uri = URI:new(args.url)
		data.url = uri
		local label = uri:host() .. uri:path()
		if label:len() > 20 then
			label = uri:host()
		end ]]
		local label
		data.url, label = url(args.url, args.label)
		data.label = args.label or label
	elseif args.link then
		data.isUrl = false
		data.link = args.link
		data.label = args.label
	elseif not args.url and not args.link then
		-- Dummy button, no link or url
		data.label = args.label
	end

	-- @TODO: Error tracking category
	-- Error if no aria-label and no visible label
	if (not data.label and not args.aria_label
		and not args.disabled and not args.ariaDisabled) then
		error('A button without a visible label needs an [[WAI-ARIA|ARIA]] '
		.. 'label, please define it using "aria-label"', 2)
	end

	-- Classes
	data.classes = {'cdx-button', 'cdx-button--fake-button'}
	local class  = type(args.class) == 'string' and args.class:lower(args.class)
	local action = type(args.action) == 'string' and args.action:lower()
		or '' -- or 'default' by default
	local weight = type(args.weight) == 'string' and args.weight:lower()
		or '' -- or 'normal' by default
	local size   = type(args.size) == 'string' and args.size:lower()
		or '' -- or 'medium' by default

	table.insert(data.classes, 'cdx-button--action-' .. action)
	table.insert(data.classes, 'cdx-button--weight-' .. weight)
	table.insert(data.classes, 'cdx-button--size-' .. size)
	table.insert(data.classes, class) -- Custom class

	-- Disabled state
	data.disabled = args.disabled
	if data.disabled then
		table.insert(data.classes, 'cdx-button--fake-button--disabled')
	else
		table.insert(data.classes, 'cdx-button--fake-button--enabled')
	end

	-- Icon
	local icon = type(args.icon) == 'string' and args.icon:lower()
	if icon then
		data.iconSpan = mw.html.create('span')
			:addClass('cdx-icon cdx-button__icon cdx-demo-css-icon--' .. icon)
			:attr('aria-hidden', 'true')
		if not data.label then
			-- Icon-only button, add extra class for styling
			table.insert(data.classes, 'cdx-button--icon-only')
		end
	end

	-- Label length checks
	if data.label then
		if string.len(data.label) > 40 then
			error('Buttons labels should ideally be fewer than 38 characters' ..
			', see [[Template:Clickable button#Button label length|' ..
			'documentation]]', 2) -- @TODO: Preview warning only.
		end
		-- Short label min-width custom CSS adjustment per Codex documentation.
		if string.len(data.label) < 3 then
			table.insert(data.classes, 'cdx-button--short-label')
		end
	end

	data.aria_label = args.aria_label
	data.ariaDisabled = args.ariaDisabled
	---- data.nocat = args.nocat
	---- data.category = args.category

	return data
end

local function _main(args)
	---- local backupArgs = args
	local parsedArgs = parseParameters(args)
	local data = makeLinkData(parsedArgs)
	data.categories = renderTrackingCategories(parsedArgs)

	return renderLink(data)
end

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Clickable button',
		'Template:Clickable button/sandbox',
		'Template:Cdx-button'
	})

	-- Return empty string if no arguments were supplied
	local hasInput = false
	for _, v in pairs(args) do
		if v and v ~= "" then
			hasInput = true
			break
		end
	end
	if not hasInput then
		return ''
	end

	return frame:extensionTag(
		'templatestyles', '', { src = 'Template:Clickable button/styles.css' }
	) .. _main(args)
end

return p