Module:Clickable button/sandbox
Tools
Actions
General
Print/export
In other projects
| This is themodule sandbox page forModule:Clickable button (diff). |
| Thismodule is rated asready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned onhelp pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed throughsandbox testing rather than repeated trial-and-error editing. |
| This Lua module is used in system messages, and onapproximately 1,130,000 pages, or roughly 2% of all pages. Changes to it can cause immediate changes to the Wikipedia user interface. To avoid major disruption and server load, any changes should be tested in the module's/sandbox or/testcases subpages, or in your ownmodule sandbox. The tested changes can be added to this page in a single edit. Please discuss changes on thetalk page before implementing them. |
| This module depends on the following other modules: |
This module implements the{{Clickable button}} template.
To use this module from wikitext, you should normally use the{{Clickable button}} template. However, it can also be used with the syntax{{#invoke:Clickable button|main|arguments}}. Please see the template page for a list of available parameters.
To use this module from other Lua modules, first load the module.
localmClickableButton=require('Module:Clickable button')
You can then generate a button using the.main function.
mClickableButton.main(args)
Theargs variable should be a table containing the arguments to pass to the module. To see the different arguments that can be specified and how they affect the module output, please refer to the{{Clickable button}} template documentation.
--- @module 'Clickable button'--- Creates clickable Codex button.------ Outputs wikitext to render the [button component](https://doc.wikimedia.org/codex/latest/components/demos/button.html)--- from the [Codex design system for Wikimedia](https://doc.wikimedia.org/codex/latest).--- Options to:--- - include an icon or create an icon-only button--- - target a URL or a wikilink--- - set the weight, size and state--- - create a dummy or disabled button--- - add custom CSS classes and inline styles--- - include ARIA attributes for accessibility.------ Dummy buttons are disabled by default. Includes helper functions for URL parsing and cleaning,--- and adding tracking categories. Intended for use in templates and other modules.--- Supports legacy parameters. To add icons, see CSS file links in `C`.------ @diagnostic disable: duplicate-doc-field--- @class args: frame Template arguments.--- @field label? string Visible text label.--- @field link? string Target wikilink.--- @field url? string Target external URL.--- @field icon? string Name of the icon to display as found in CSS file after the class's icon prefix, i.e. `search` for `cdx-demo-css-icon--search`.--- @field weight? string Visual weight of the button.--- @field size? string Size of the button.--- @field action? string Action type of the button.--- @field disabled? boolean|string Whether the button is disabled/greyed out. `true` if: `link` = `'no'` or `false`, or `disabled` = `'1'` or `true`.--- @field nocat? boolean|string If `true`, suppresses tracking categories. Additional category, if defined, will still be added.--- @field category? string An additional category to add.--- @field aria-label? string The ARIA label for accessibility DOM.--- @field class? string Custom CSS classes for the button. Do _not_ nest in "".--- @field style? string Custom inline CSS styles. Do _not_ nest in "".--- @field arialabel? string (alias for aria-label)--- @field aria_label? string (alias for aria-label)--- @field text string (alias for label)--- @field ['1']? string Positional argument 1 (alias for link, can be label too if label is not defined).--- @field ['2']? string Positional argument 2 (alias for label).--- @field color? string Legacy color parameter.--- @field private categories? string|boolean Categories to add.--- @field private ariaDisabled? boolean Internal flag indicating if the button should be functionally disabled to ARIA.--- @field private oldClassMatched string|boolean Internal flag for outdated classes if used.--- @field private isUrl boolean Whether the target is a URL.--- @field private errorText string|nil Internal string used both as error indicator, and error message text.--- @field private tblClasses table Classes for the button span tag.--- @field private pageTitleObject mw.title Title object of the current page.--- @field private linkTitleObject mw.title Title object of the target wikilink.--- @field private frame frame The current frame.--- @field private rawArgs table Arguments passed to the module before parsing.--- @field private parsedArgs table Parsed arguments.--- @field private iconSpan mw.html Icon span element for the button.require('strict')localM={}-- If your wiki uses non-ASCII/UTF-8 characters in any input text, then replace use of "string.lower" with "mw.ustring.lower". NOTE: "mw.ustring.lower" may be _much_ slower but respects Unicode codepoints rather than just bytes.local_lower=string.lowerlocalgetArgs=require('Module:Arguments').getArgslocalcheckForUnknowns=require('Module:Check for unknown parameters')._checklocal_gsub=mw.ustring.gsublocal_mw_lower=mw.ustring.lower-- Still loaded, as instances where Unicode support is required use it.local_tonumber=tonumberlocal_format=mw.ustring.formatlocal_type=typelocal_table_insert=table.insert--- Requires @wikimedia/codex (check [[Special:Version]]).--- @todo Check not in User/Draft namespaces.--- @todo Is checkForUnknowns checking validity of input?--- @todo Check if being subst'd via {{subst:#invoke:}} by checking mw.isSubsting() then output template call not the subst, i.e. unsubst.--- @todo Check verbose output with mw.dumpObject( type.object ).--- @todo Check knownArgs.localC={--- 'Constants'lowercaseArgs={--- Arguments whose inputs are case-insensitive, and are converted to lowercase.['action']=true,['color']=true,['weight']=true,['size']=true,['icon']=true,},knownArgs={--- Valid argument keys.'class','color','weight','size','icon','link','action','url','disabled','label','aria-label','nocat','text','1','2','url','errorText','arialabel','aria_label',checkpositional='y',--- Other options for unknown parameters check.ignoreblank='y',unknown='[[Category:Pages using Module:Clickable button with unknown parameters|_VALUE_]]',preview='<span class="error" style="font-size:inherit;"><strong>Preview warning:</strong>'..'Using undocumented parameter(s): "_VALUE_".</span>',},wrapperTemplates={--- Wrapper templates that only require reading from `parentFrame()`. Positional arguments using template parameters (e.g., `{{{var|}}}`) are ignored, as `currentFrame()` is not used. Improves performance by avoiding argument checks in both frames.'Template:Clickable button','Template:Clickable button/sandbox','Template:Cdx-button','Template:Cdx-button/sandbox',},trackingCategories={--- Tracking category pagenames with namespace.dummyButton='Category:Pages using clickable dummy button',disabledButton='Category:Pages using disabled button',externalLinks='Category:Pages using clickable button with external links',outdatedClasses='Category:Pages using clickable button with outdated classes',errors='Category:Errors reported by Module:Clickable button',unknownParams='Category:Pages using Module:Clickable button with unknown parameters',},unknownArgsPreviewText='<span class="error"><strong>Preview warning:</strong>'..--- Preview warning text for unknown arguments.' Using undocumented parameter(s): "_VALUE_".</span>',noAriaLabelWarningText='<span class="error" style="font-size:inherit;">'..--- No ARIA-label preview warning text.'<strong>Preview warning:</strong> A button without a visible label needs an ['..'[WAI-ARIA|ARIA]] label, please define it using "aria-label".</span>',labelLengthWarningText='<span class="error" style="font-size:inherit;">'..--- "Visible label is too long" preview warning text.'<strong>Preview warning:</strong> A button label should ideally be shorter th'..'an 38 characters, see [[en:Template:Clickable button/doc#Button label length|'..'documentation]].</span>',noArgsWarningText='<span class="error" style="font-size:inherit;">'..--- No arguments preview warning text.'<strong>Preview warning:</strong> No parameters were passed to clickable button.</span>',baseCSS='Template:Clickable button/styles.css',--- Base CSS file for button styles.iconsCSS='Template:Clickable button/icons.css',--- CSS file for button icons.buttonDefaults={--- Default values for button optionsweight='normal',size='medium',action='default',},cssClasses={-- CSS class prefixes for button.base='cdx-button',disabled='cdx-button--fake-button--disabled',wordWrap='cdx-button--word-wrap',enabled='cdx-button--fake-button--enabled',iconOnly='cdx-button--icon-only',shortLabel='cdx-button--short-label',icon='cdx-button__icon',iconPrefix='cdx-demo-css-icon--',sizePrefix='cdx-button--size-',weightPrefix='cdx-button--weight-',samePage='cdx-button--same-page',actionPrefix='cdx-button--action-',fakeButton='cdx-button--fake-button',},labelLimits={maxLength=38,minLength=3},--- Label length limits.excludedNamespaces={'User','Draft'},--- Namespace exclusions for tracking categories.legacyClassSets={progressive={--- Aliases for CSS class: `.progressive`.['blue']=true,['green']=true,['ui-button-green']=true,['ui-button-blue']=true,['mw-ui-constructive']=true,['mw-ui-progressive']=true,['progressive']=true,},destructive={--- Aliases for CSS class: `.destructive`.['red']=true,['ui-button-red']=true,['mw-ui-destructive']=true,['destructive']=true,},},booleanMap={-- Explicit true values['yes']=true,['y']=true,['true']=true,['t']=true,['on']=true,['1']=true,['enable']=true,['enabled']=true,-- Explicit false values['no']=false,['n']=false,['false']=false,['f']=false,['off']=false,['0']=false,['disable']=false,['disabled']=false,},defaultResponse=nil,}--- Allows for consistent treatment of boolean-like wikitext input.------ Uses lookup table for efficiency, unlike [[Module:Yesno]] which uses chained if-elseif statements.--- - Returns `nil` if input is `nil`.--- - Checks for boolean type and returns as-is.--- - For strings, looks up a normalized (lowercased) value in a lookup table (`C.booleanMap`).--- - If not found, attempts to convert to a number: returns `true` for `1`, `false` for `0`.--- - If still unrecognized, returns `defaultResponse` (or a constant fallback; default: `nil`).--- @param value any Value to evaluate as truthy or falsy.--- @param defaultResponse? any Value to return if input is unrecognized, i.e. neither truthy/falsy. Defaults to nil.--- @return any valueBoolean Boolean true if truthy, or false if falsy, or nil if nil. defaultResponse or nil if input is unrecognized.functionM.yesno(value,defaultResponse)ifvalue==nilthenreturnnilendlocalvalueType=_type(value)ifvalueType=='boolean'thenreturnvalueelseifvalueType=='string'thenlocallookupResult=C.booleanMap[_lower(value)]-- Unicode doesn't matter here.iflookupResult~=nilthenreturnlookupResultend-- Not found in lookup table. Fallback to numeric check.end-- Numeric check works for both numbers and numeric strings.-- Numeric 1 is truthy, and 0 is falsy.localnumber=_tonumber(value)ornilifnumber==1thenreturntrueelseifnumber==0thenreturnfalseend-- Not 1 or 0, fallback to defaultResponse.ifnotdefaultResponsethendefaultResponse=C.defaultResponseendreturndefaultResponseend--- Parse a wikilink and return its component parts.------ @class linkData, table--- @field pageName string? The pagename part, with namespace if present--- @field sectionHeading string? The section heading after `#`--- @field displayText string? Display text after pipe `|`--- @field isSectionLink boolean Whether wikilink is a section-only link in current page, i.e. `[[#Section]]`.--- @param wikilinkText string|nil Wikitext to parse.--- @return linkData|nil wikilink Components of wikilink, or nil if invalid.localfunctionparseWikilink(wikilinkText)-- @class wikilink: table<string, any>-- @field pageName string The pagename with namespace, if present-- @field sectionHeading string The section heading-- @field displayText string Display text, as given or as generated-- @field isSectionLinkOnly boolean Whether wikilink is a section-only link in current page, i.e. `[[#Section]]`-- @param wikilinkText string|nil Wikitext to parse.-- @return table<string, string>|nil wikilink Components of wikilink, or nil if invalid.ifnotwikilinkTextorwikilinkText==''thenreturnnilend-- Remove outer square brackets if present: `[[:Help:Foo#Bar|Flog]]` → `Help:Foo#Bar|Flog`wikilinkText=_gsub(wikilinkText,'^%[%[','')wikilinkText=_gsub(wikilinkText,'%]%]$','')-- Remove initial colon if presentwikilinkText=wikilinkTextandstring.match(wikilinkText,'^:?(.*)')-- Remove initial colon if present.-- Split on pipe `|` to separate link from display textlocallink,displayText=wikilinkText,wikilinkTextandstring.match(wikilinkText,'^(.-)|(.*)$')wikilinkText=linkorwikilinkText-- Split link on hash/pound sign `#` to separate page from sectionlocalpageName,sectionHeading=wikilinkText,wikilinkTextandstring.match(wikilinkText,'^(.-)#(.*)$')localisSectionLink=falseifnotpageNameandsectionHeadingthenisSectionLink=true-- It is a section link to current page, i.e. `[[#Bar]]`.pageName=nil-- pageName = FORMAT('#%s', sectionHeading)elseifnotpageNameandnotsectionHeadingthenisSectionLink=falsepageName=wikilinkTextelseifpageNameandnotsectionHeadingthenisSectionLink=falsesectionHeading=nilpageName=wikilinkTextendifnotdisplayTextandsectionHeadingandpageNamethendisplayText=_format('%s § %s',pageName,sectionHeading)elseifnotdisplayTextandsectionHeadingandnotpageNamethendisplayText=_format('§ %s',sectionHeading)elseifnotdisplayTextandnotsectionHeadingandpageNamethendisplayText=pageNameendreturn{pageName=pageName,sectionHeading=sectionHeading,displayText=displayText,isSectionLink=isSectionLink,}end--- Safely creates a [mw.uri object](lua://mw.uri) from a string, returning `nil` if invalid.--- See [mw.uri in Lua reference manual](https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.uri).------ @param s string The URL to check.--- @return mw.uri|nil uri The URI of the given URL.localfunctionsafeUri(s)localsuccess,uri=pcall(function(s)returnmw.uri.new(s)end)ifsuccessthenreturnurielsereturnnilendend--- Attempts to extract and normalize a URL from a string.------ @param extract string String from which the URL must be obtained.--- @return string|nil url The raw URL.localfunctionextractUrl(extract)localurl=extracturl=_gsub(url,'^([Hh]?[Tt]?[Tt]?[Pp]?[Ss]?:/*)(.+)','https://%2')localuri=safeUri(url);ifurianduri.hostthenreturnurlendreturnnilend--- Cleans and encodes a URL, generates a display label (domain-only if no label provided),--- and adds word break opportunities for better display.------ @param url string The URL--- @param text? string|nil The display text for the URL if one must not be generated--- @return string|nil url The URL, returns nil if URL invalid--- @return string|nil text The display text for the URLlocalfunction_url(url,text)-- @TODO cache some of these values.url=mw.text.trim(urlor'')text=mw.text.trim(textor'')ifurl==''ornoturlthenreturnnil,textend-- If the URL contains any unencoded spaces, encode them,-- because MediaWiki will otherwise interpret a space as the end of the URL.url=_gsub(url,'%s',function(s)returnmw.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 errorurl=_gsub(url,'#$','')url=_gsub(url,'%?$','')-- If it's an http(s) URL without the double slash, fix it.url=_gsub(url,'^[Hh][Tt][Tt][Pp]([Ss]?):(/?)([^/])','http%1://%3')localuri=safeUri(url)-- Handle URLs without a protocol or who are protocol-relative.-- e.g., www.example.com/foo or www.example.com:8080/foo, and //www.example.com/fooifuriand(noturi.protocolor(uri.protocolandnoturi.host))andurl:sub(1,2)~='//'thenurl='http://'..urluri=safeUri(url)endiftext==''ornottextthenifurithen-- Generate clean domain-only text (e.g., "en.wikipedia.org")localhost=_lower(uri.hostor'')-- Remove www. prefix for cleaner displayhost=_gsub(host,'^www%.','')-- For URLs like "http://en.wikipedia.org/wiki/Article_Name"-- Only want en.wikipedia.orgtext=host-- Add port if present and not standardifuri.portanduri.port~=80anduri.port~=443thentext=text..':'..uri.portend-- Add word break opportunities for better display. Add `<wbr>` before `_/.-#` sequences. This entry _must_ be the first. `<wbr/>` has a `/` in it, you know.text=_gsub(text,'(/+)','<wbr/>%1')text=_gsub(text,'(%.+)','<wbr/>%1')-- _Disabled_ for now.---- text = gsub(text,"(%-+)","<wbr/>%1")text=_gsub(text,'(%#+)','<wbr/>%1')text=_gsub(text,'(_+)','<wbr/>%1')else-- URL is badly-formed, so just display whatever was given.text=urlendendreturnurl,textend--- Strips HTML/wikilink markup, ensures protocol, and returns a cleaned URL and display label.--- Cleans and normalises a URL string.------ @param url string Raw URL.--- @param text string Optional display text.--- @return string|nil cleanUrl Cleaned URL for linking.--- @return string|nil displayText Display label for the URL.functionM.url(url,text)locallocalUrl=urllocalUrl=localUrlorextractUrl(localUrl)orextractUrl(text)or''-- Strip out HTML tags and wikilink bracketslocalUrl=_gsub(localUrl,'<[^>]*>','')or''localUrl=_gsub(localUrl,'[%[%]]','')or''-- Handle common URL prefixes and ensure proper protocollocalUrl=_gsub(localUrl,'^[Ww][Ww][Ww]%.','http://www.')or''-- Process the URL and generate labellocalcleanUrl,displayText=_url(localUrl,text)-- Enhanced label generation for URLs - domain-only formatifcleanUrlandnottextthenlocaluri=safeUri(cleanUrl)ifurianduri.hostthen-- Generate clean domain label (e.g., "en.wikipedia.org")displayText=_lower(uri.host)-- Remove 'www.' prefix for cleaner displaydisplayText=_gsub(displayText,'^www%.','')endendreturncleanUrl,displayTextend--- Generate tracking categories.--- Checks for unknown parameter use and validates input arguments.------ @param oldClassMatched string|nil Whether the parser matched any legacy classes in input.--- @param rawArgs table Raw arguments passed to the module.--- @return string categories Category wikitext.localfunctionrenderTrackingCategories(category,class,nocat,link,url,disabled,oldClassMatched,rawArgs)localcategories=''class=_type(class)=='string'and_lower(class)or''--- Don't add categories if `nocat==true` or `category` is falsy,--- but still add any custom category passed in.ifcategoryandcategory~=''andM.yesno(category)~=falsethen-- Extract category name if in wikilink format like [[:Category:Foo Bar|Display]]localparsed=parseWikilink(category)ifparsedandparsed.pageNamethencategories=_format('[[%s]]',parsed.pageName)endendifM.yesno(nocat)==truethenreturncategorieselseifM.yesno(category)==falsethen-- Legacy `category=no`.returncategoriesend--- Add categories for outdated classes, dummy buttons, disabled buttons,--- and external links.do--- Dummy button is:--- - Clickable (i.e. not disabled visually)--- - No target link and no URL--- - Gives feedback it'll do something, but does nothing.--- All matches to if-statements below should all have `ariaDisabled == true`,--- and therefore `aria-disabled = true`.if(notlinkor(M.yesno(link)==false))-- Checks for falsy or `link == 'no'`andnoturlandnotdisabledthencategories=_format('%s [[%s]]',categories,C.trackingCategories.dummyButton)end--- Disabled button is:---- Greyed out (`data.disabled == true`).---- Also disabled to accessibility API (`aria-disabled = true`).ifdisabledthencategories=_format('%s [[%s]]',categories,C.trackingCategories.disabledButton)endifclassandoldClassMatchedthencategories=_format('%s [[%s]]',categories,C.trackingCategories.outdatedClasses)endifurlthencategories=_format('%s [[%s]]',categories,C.trackingCategories.externalLinks)endend--- Check for unknown parameters and add appropriate categorieslocalunknownParamCategories=checkForUnknowns(C.knownArgs,rawArgs)or''categories=categories..unknownParamCategoriesreturncategoriesend------ Renders the wikitext span tags for the button.------ @class mw.html: table MediaWiki DOM document content model based on HTML and RDFa.--- @param data args table Arguments table.--- @param iconSpan mw.html|nil Icon span element for the button.--- @param isUrl boolean Whether target is URL--- @param ariaDisabled boolean Whether button is disabled for ARIA API.--- @param categories string Categories for the button.--- @param errorText string|nil Internal string used both as error indicator, and error message text.--- @param tblClasses table--- @return string link Wikitext span tags for the button.localfunctionrenderLink(data,iconSpan,isUrl,ariaDisabled,categories,errorText,tblClasses)--- @type mw.html: Span tag that creates the button.localdisplaySpan=mw.html.create('span')--- @type string|nil Custom CSS style attributes for parent span node (not including plainlinks span tag if URL used).localstyleAttributes=_type(data.style)=='string'anddata.styleornil--- @future Additional ARIA attributes for button. If implement 'fake' button for use in collapsible/accordion component, don't forget to declare:--- displaySpan:attr('aria-haspopup', 'true') --- displaySpan:attr('aria-expanded', 'false')--- Classes, ARIA `role` and `aria-label`, and `style` attributes for button span tag.for_,aClassinipairs(tblClassesor{})dodisplaySpan:addClass(aClass)enddisplaySpan:attr('role','button')ifdata.aria_labelthendisplaySpan:attr('aria-label',data.aria_label)endifstyleAttributesthendisplaySpan:attr('style',styleAttributes)endificonSpan~=''thendisplaySpan:node(iconSpan)endifdata.labelthendisplaySpan:wikitext(data.label)end--- @type string Wikilink that wraps around button wikitext.locallinkifdata.disabledorariaDisabledthen-- `aria-disabled` attribute for no-link/dummy buttons.-- `aria-disabled` attribute for disabled buttons.displaySpan:attr('aria-disabled','true')link=_format('%s %s',tostring(displaySpan),categoriesor'')elsedisplaySpan:attr('aria-disabled','false')ifisUrlthenlink=_format('<span class="plainlinks">[%s %s]</span> %s',data.url,tostring(displaySpan),categoriesor'')elseifisUrl==falseanddata.linkthenlink=_format('[[:%s|%s]] %s',data.link,tostring(displaySpan),categoriesor'')else-- `isUrl` should be `nil` to get here, or data.link is nil.-- Dummy/disabled button.link=_format('%s %s',tostring(displaySpan),categoriesor'')endendiferrorTextthen--- Generate error message when viewed in preview mode of an edit.--[[ --- If previewing an edit displays first argument, otherwise second.--- @class ifPreview--- @field main function--- @type ifPreviewlocal ifPreview = require('Module:If preview') ]]ifM.yesno(data.nocat)~=truethen-- Don't add category if `nocat=true`link=_format('%s [[%s]]',link,C.trackingCategories.errors)end-- Add error message to the link if viewing in preview mode.mw.addWarning(errorText)endreturnlinkend--- Parses arguments from old template parameters. For backward compatibility.--- Subfunction of parseParameters() for efficiency.--- @param color? string `color` argument.--- @param class? string `class` argument.--- @param action? 'progressive'|'destructive'|'default'|string `action` argument.--- @return string class String with class that did not match, likely custom class(es).--- @return string action Returns action resolved.--- @return string|nil matched Value of matched class if any of the arguments matched.localfunctioncheckColorAndClass(color,class,action)localactionValue=(_type(action)=='string'andaction)or''color=(_type(color)=='string'andcolor)or''class=(_type(class)=='string'and_lower(class))or''ifcolor==''andclass==''thenreturn'',actionValue,nilend-- Resolve action, check against set constants.foractionName,setinpairs(C.legacyClassSets)doifset[color]andnotC.legacyClassSets[actionName][actionValue]thenreturnclass,actionName,actionValue-- Found `color`.endifset[class]andnotC.legacyClassSets[actionName][actionValue]thenreturn'',actionName,actionValue-- Found `class`.endifset[actionValue]thenreturnclass,actionName,actionValue-- Found `action`.endend-- No match.returnclass,'',nilend--- Constructs the attributes for the wikitext/HTML elements.--- @param parsedArgs args Parsed arguments.--- @param ariaDisabled boolean Whether button is disabled for ARIA API.--- @return args data Data, such as attributes, ready to be assembled.--- @return mw.html|nil iconSpan--- @return boolean isUrl--- @return boolean ariaDisabled--- @return string|nil oldClassMatched--- @return string|nil errorText Internal string used as both an indicator of an error, and error message text.--- @return table tblClasses--- @return mw.title pageTitleObjectlocalfunctionmakeLinkData(parsedArgs,ariaDisabled)localdata={}localtblClasses={C.cssClasses.base,C.cssClasses.fakeButton}localiconSpan=nillocalisUrl=false--- @type string|nillocalerrorText=nillocalisSamePage=falselocalpageTitleObject=mw.title.getCurrentTitle()--- @todo do i need string check -- type(parsedArgs.icon) == 'string'data.icon=parsedArgs.iconornildata.disabled=parsedArgs.disabled-- Decide link vs. URL vs. none-- URL has priority over link if both provided.ifparsedArgs.urlthenisUrl=truelocalgeneratedLabel-- Process URL with enhanced cleaning and label generationdata.url,generatedLabel=M.url(parsedArgs.url,parsedArgs.label)-- Use provided label or fall back to derived label.data.label=parsedArgs.labelorgeneratedLabelelseifparsedArgs.linkthenisUrl=falsedata.link=parsedArgs.linkdata.label=parsedArgs.label-- Same-page detectionlocallinkTitleObject=mw.title.new(data.link)iflinkTitleObjectandpageTitleObjectthenisSamePage=(linkTitleObject.fullText==pageTitleObject.fullText)endelseifnotparsedArgs.urlandnotparsedArgs.linkthendata.label=parsedArgs.label-- Dummy button as has no link or URL.endlocalclass,action,oldClassMatched=checkColorAndClass(parsedArgs.color,parsedArgs.class,parsedArgs.action)localweight=_type(parsedArgs.weight)=='string'andparsedArgs.weightorC.buttonDefaults.weightlocalsize=_type(parsedArgs.size)=='string'andparsedArgs.sizeorC.buttonDefaults.size_table_insert(tblClasses,C.cssClasses.actionPrefix..action)_table_insert(tblClasses,C.cssClasses.weightPrefix..weight)_table_insert(tblClasses,C.cssClasses.sizePrefix..size)if(classandclass~='')then_table_insert(tblClasses,class)-- Custom class.data.class=classendifdata.disabledthen_table_insert(tblClasses,C.cssClasses.disabled)else_table_insert(tblClasses,C.cssClasses.enabled)endmw.log('Debug classes: '..table.concat(tblClasses,' '))mw.log('Debug action: '..(actionor'nil'))mw.log('Debug label: '..(data.labelor'nil'))-- Cannot check length earlier as value changes above.locallabelLength=(_type(data.label)=='string'andmw.ustring.len(data.label))or0ifdata.labelandlabelLength>C.labelLimits.maxLengththen_table_insert(tblClasses,C.cssClasses.wordWrap)end--- @TODO Check if current page is the target link, if so, make button darker.--- @TODO Must still actually use this in the CSS file.ifisSamePagethen_table_insert(tblClasses,C.cssClasses.samePage)endifdata.iconthen-- Store until end of module for icons CSS output logic.iconSpan=mw.html.create('span')iconSpan:addClass(C.cssClasses.icon)iconSpan:addClass(_format('%s%s',C.cssClasses.iconPrefix,data.icon))iconSpan:attr('aria-hidden','true')ifnotdata.labelthen-- Icon-only button, add extra class for styling._table_insert(tblClasses,C.cssClasses.iconOnly)endend-- Label length checks.ifdata.labeltheniflabelLength>C.labelLimits.maxLengththenerrorText=C.labelLengthWarningTextelseiflabelLength<C.labelLimits.minLengththen_table_insert(tblClasses,C.cssClasses.shortLabel)endendlocalhasNoLabel=notdata.labelandnotparsedArgs.aria_label-- Error if no aria-label and no visible label, for any non-disabled button-- (whether it has a link/URL or is a dummy button)ifhasNoLabelandnotparsedArgs.disabledthenerrorText=errorTextand_format('%s %s',errorText,C.noAriaLabelWarningText)orC.noAriaLabelWarningTextenddata.aria_label=parsedArgs.aria_labelreturndata,iconSpan,isUrl,ariaDisabled,oldClassMatched,errorText,tblClasses,pageTitleObjectend--- Parses the module's arguments for backward compatibility.--- Validates module arguments and returns parsed arguments.--- With deprecated parameters from old templates and modules.--- @param rawArgs args table Module arguments.--- @return args parsedArgs Parsed arguments.--- @return boolean ariaDisabled Whether button is disabled for ARIA API.localfunctionparseParameters(rawArgs)--- It's weird that we may make a link a label, but if we truly--- only got positional argument `1`, then that would mean it's--- intentional to make both the link and label the same.--- `label` value priority: `label` > `text` > `2` > `1`rawArgs.label=rawArgs.labelorrawArgs.textorrawArgs[2]orrawArgs[1]ornilrawArgs.disabled=M.yesno(rawArgs.disabled)or(M.yesno(rawArgs.link)==false)orfalserawArgs.link=rawArgs.linkorrawArgs[1]ornilrawArgs[1]=nil-- Remove positional rawArgs after assigningrawArgs[2]=nilifrawArgs.disabledthen-- If disabled, must not generate link. Usually doesn't but in case.rawArgs.link=nilrawArgs.url=nilendlocalparsedLink=rawArgs.linkandparseWikilink(rawArgs.link)--- @TODO double check next five lines of code-- If had no label, give autogenerated label.rawArgs.label=rawArgs.labelor(parsedLinkandparsedLink.displayText)-- Try assign newly cleaned link. Fallback if needed.rawArgs.link=(parsedLinkandparsedLink.pageName)orrawArgs.linkifrawArgs.link==''thenrawArgs.link=nil-- Invalid wikilink, remove it.endifrawArgs.linkandparsedLinkthen-- Fallback to displayText if there was any in wikilink, or `Foo § Bar` or just pagename.rawArgs.label=rawArgs.labelorparsedLink.displayTextornilrawArgs.link=parsedLink.pageNameorrawArgs.linkornilifrawArgs.link==''then-- If no link leftover, remove it.rawArgs.link=nilendifparsedLink.pageNameandparsedLink.sectionHeadingthenrawArgs.link=_format('%s#%s',parsedLink.pageName,parsedLink.sectionHeading)elseifparsedLink.isSectionLinkandparsedLink.sectionHeadingthenrawArgs.link=_format('#%s',parsedLink.sectionHeading)elseifparsedLink.pageNamethenrawArgs.link=parsedLink.pageNameelserawArgs.link=nilendend--- `aria-disabled = true` if no link whatsoever, always. Make dummy button. But for accessibility,--- ARIA must know it won't do anything.localariaDisabled=falseifrawArgs.disabledor(notrawArgs.linkandnotrawArgs.url)thenariaDisabled=trueend--- @TODO _OPTION_ to forcefully disable dummy buttons by setting:---- rawArgs.disabled = trueifrawArgs.labelthen--- @TODO refactor: decide if we want to allow [[ or ]] in label, and if so, how to handle it.--[=[ -- Plain search if [[ or ]] present, to wrap <nowiki> tags.if string.find(rawArgs.label, '[[', 1, true) or string.find(rawArgs.label, ']]', 1, true) thenrawArgs.label = GSUB(rawArgs.label, '%[%[', '')rawArgs.label = GSUB(rawArgs.label, '%]%]', '')if rawArgs.label == '' then -- If no label leftover, remove it.rawArgs.label = nilendend ]=]rawArgs.label=mw.text.nowiki(rawArgs.label)elserawArgs.label=nilendrawArgs.nocat=M.yesno(rawArgs.nocat)-- Normalize ARIA label keysrawArgs.aria_label=rawArgs.aria_labelorrawArgs['aria-label']orrawArgs.arialabelrawArgs['aria-label']=nilrawArgs.arialabel=nilreturnrawArgs,ariaDisabledend--- Interface for other Lua modules.--- Function can be called by other Lua modules to generate wikitext.--- Note: Does not render CSS files or pre-process arguments like `M.main()`.------ @param rawArgs args Module's arguments.--- @return string data Wikitext that renders button, without CSS files.functionM._main(rawArgs)localparsedArgs,ariaDisabled=parseParameters(rawArgs)localdata,iconSpan,isUrl,oldClassMatched,errorText,tblClasses,pageTitleObjectdata,iconSpan,isUrl,ariaDisabled,oldClassMatched,errorText,tblClasses,pageTitleObject=makeLinkData(parsedArgs,ariaDisabled)localisExcludedNamespace=falsefor_,namespaceinipairs(C.excludedNamespaces)do-- Don't add tracking categories in excluded namespaces.ifpageTitleObject.nsText==namespacethenisExcludedNamespace=trueparsedArgs.nocat=true-- Redundant, but whatever.breakendendlocalcategories--- @type stringifnotisExcludedNamespacethencategories=renderTrackingCategories(parsedArgs.category,data.class,parsedArgs.nocat,data.link,data.url,data.disabled,oldClassMatched,rawArgs)endreturnrenderLink(data,iconSpan,isUrl,ariaDisabled,categories,errorText,tblClasses)end--- Module entry point. Interface for templates and modules.------ Pre-processes arguments, inserts CSS files, and renders the button.--- @usage Called via the `{{#invoke: Clickable button | main }}` parser function.--- @param frame args Frame object passed by the MediaWiki parser.--- @return string|nil wikitextOutput Wikitext for insertion on a wiki page.functionM.main(frame)localrawArgs=getArgs(frame,{wrappers=C.wrapperTemplates,valueFunc=function(key,value)-- Custom formatting function for arguments.value=mw.text.trim(value)-- Remove whitespace.ifnotvalueorvalue==''then-- Remove blank arguments.returnnilendifC.lowercaseArgs[key]then-- Convert to lowercase.return_mw_lower(value)elsereturnvalueendend,})-- Return empty string, and preview warning if no arguments supplied.dolocalhasInput=falsefor_,vinpairs(rawArgs)doifvandv~=''thenhasInput=truebreakendendifnothasInputthenmw.addWarning(C.noArgsWarningText)return''endendlocaloutput=M._main(rawArgs)localoutputCSS=frame:extensionTag('templatestyles','',{-- Insert CSS files into the output.src=C.baseCSS,-- Duplicates are de-duplicated by Parsoid.})ifrawArgs.iconthenoutputCSS=_format('%s%s',outputCSS,frame:extensionTag('templatestyles','',{src=C.iconsCSS,}))endreturn_format('%s%s',outputCSS,output)endreturnM