Module:Sister project links

require('strict')

-- Module to create sister project link box local getArgs = require('Module:Arguments').getArgs local commonsLink = require('Module:Commons link') local sideBox = require('Module:Side box')._main local generateWarning = require('Module:If preview')._warning local p = {}

local logo = { wikt="Wiktionary-logo-v2.svg", c="Commons-logo.svg", n="Wikinews-logo.svg", q="Wikiquote-logo.svg", s="Wikisource-logo.svg", b="Wikibooks-logo.svg", voy="Wikivoyage-Logo-v3-icon.svg", v="Wikiversity logo 2017.svg", d="Wikidata-logo.svg", species="Wikispecies-logo.svg", m="Wikimedia Community Logo.svg", mw="MediaWiki-2020-icon.svg"}

local prefixList = {'wikt', 'c', 'n', 'q', 's', 'b', 'voy', 'v', 'd', 'species', 'species_author', 'm', 'mw'}

local sisterName = { wikt="Wiktionary", c="Commons", n="Wikinews", q="Wikiquote", s="Wikisource", b="Wikibooks", voy="Wikivoyage", v="Wikiversity", d="Wikidata", species="Wikispecies", m="Meta-Wiki", mw="MediaWiki"}

local sisterInfo = { wikt="Definitions", c="Media", n="News", q="Quotations", s="Texts", b="Textbooks", voy="Travel guides", v="Resources", d="Data", species="Taxa", species_author="Authorship", m="Discussions", mw="Documentation" }

local defaultSisters = { wikt=true, c=true, n=true, q=true, s=true, b=true, voy='auto', v=true, d=false, species='auto', species_author=false, m=false, mw=false }

local sisterDb = { wikt="enwiktionary", n="enwikinews", q="enwikiquote", s="enwikisource", b="enwikibooks", voy="enwikivoyage", v="enwikiversity", species="specieswiki"}

local trackingType = { wdMismatch="Pages using Sister project links with wikidata mismatch", wdNamespace="Pages using Sister project links with wikidata namespace mismatch", wdHidden="Pages using Sister project links with hidden wikidata", defaultSearch="Pages using Sister project links with default search"}

local inSandbox = mw.getCurrentFrame:getTitle:find('sandbox', 1, true)

-- Function to add "-sand" to classes when called from sandbox local function sandbox(s) return inSandbox and s.."-sand" or s end

-- Function to canonicalize string -- search for variants of "yes", and "no", and transform -- them into a standard form (like Template:YesNo) -- Argument: --  s --- input string -- Result: -- {x,y} list of length 2 --   x = nil if s is canonicalized, otherwise has trimmed s --    y = canonical form of s (true if "yes" or other, false if "no", nil if blank) local function canonicalize(s) if s == nil then return {nil, nil} end -- if s is table/list, then assume already canonicalized and return unchanged if tostring(type(s)) == "table" then return s	end s = mw.text.trim(tostring(s)) if s == "" then return {nil, nil} end local lowerS = s:lower -- Check for various forms of "yes" if lowerS == 'yes' or lowerS == 'y' or lowerS == 't' 	     or lowerS == '1' or lowerS == 'true' or lowerS == 'on' then return {nil, true} end -- Check for various forms of "no" if lowerS == 'no' or lowerS == 'n' or lowerS == 'f' 	      or lowerS == '0' or lowerS == 'false' or lowerS == 'off'then return {nil, false} end -- Neither yes nor no recognized, leave string trimmed return {s, true} end

-- Merge two or more canonicalized argument lists -- Arguments: -- argList = list of canonicalized arguments -- noAll = if true, return no when all argList is no. --          otherwise, return blank when all argList is blank local function mergeArgs(argList,noAll) local test = nil -- default, return blank if all blank if noAll then test = false -- return no if all no	end local allSame = true -- Search through string for first non-no or non-blank for _, arg in ipairs(argList) do		if arg[2] then return arg -- found non-no and non-blank, return it		end -- test to see if argList is all blank / no		allSame = allSame and (arg[2] == test) end -- if all blank / no, return blank / no	if allSame then return {nil, test} -- all match no/blank, return it	end -- otherwise, return no / blank if noAll then return {nil, nil} end return {nil, false} end -- Function to get sitelink for a wiki -- Arguments: --  wiki = db name of wiki to lookup --  qid = QID of entity to search for, current page entity by default local function getSitelink(wiki,qid) -- return nil if some sort of lookup failure return qid and mw.wikibase.getSitelink(qid,wiki) end

-- Function to generate the sister link itself -- Arguments: -- args = argument table for function --    args[1] = page to fetch --    args.default = link when blank --    args.auto = new auto mode (don't fall back to search) --    args.sitelink = wikidata sitelink (if available) --    args.qid = QID of entity --    args.search = fallback string to search for --    args.sisterPrefix = wikitext prefix for sister site --    args.information = type of info sister site contains -- tracking = tracking table local function genSisterLink(args, tracking) if args[1][2] == false or (not args.default and args[1][2] == nil) then return nil --- either editor specified "no", or "blank" (and default=no), then skip this sister end local sisterDbName = sisterDb[args.sisterPrefix] local sitelink = args.sitelink or (sisterDbName and getSitelink(sisterDbName,args.qid)) if args.auto and not sitelink and args[1][2] == nil then return nil --- in auto mode, if link is blank and no sitelink, then skip end -- fallback order of sister link: first specified page, then wikidata, then search local link = args[1][1] or sitelink or (args.search and "Special:"..args.search) if not link then return nil --- no link found, just skip end if tracking then -- update state for tracking categories if args[1][1] and sitelink then -- transform supplied page name to be in wiki-format local page = mw.ustring.gsub(args[1][1],"_"," ") page = mw.ustring.sub(page,1,1):upper..mw.ustring.sub(page,2) local pageNS = mw.ustring.match(page,"^([^:]+):") local sitelinkNS = mw.ustring.match(sitelink,"^([^:]+):") if page == sitelink then tracking.wdHidden = args.sisterPrefix elseif pageNS ~= sitelinkNS then tracking.wdNamespace = args.sisterPrefix else tracking.wdMismatch = args.sisterPrefix end -- if no page link, nor a wikidata entry, and search is on, then warn elseif not (args[1][2] or sitelink) and args.search then tracking.defaultSearch = args.sisterPrefix end end return {prefix=args.sisterPrefix, link=link, information=args.information} end

local function commonsLinks(args, commonsPage) -- use Module:Commons link to determine best commons link local cLink = (not args.commonscat) and commonsLink._hasGallery(args.qid) or commonsLink._hasCategory(args.qid) if commonsPage[1] and not mw.ustring.match(commonsPage[1]:lower,"^category:") then commonsPage[1] = (args.commonscat and "Category:" or "")..commonsPage[1] end local commonsSearch = "Search/"..(args.commonscat and "Category:" or "")..args[1] return {link=cLink, search=commonsSearch} end

-- Function to create a sister link, by prefix -- Arguments: --  prefix = sister prefix (e.g., "c" for commons) --  args = arguments for this sister (see p._sisterLink above) --  tracking = tracking table local function sisterLink(prefix, args, tracking) -- determine arguments to genSisterLink according to prefix if prefix == 'species_author' and not args.species[1] and args.species[2] and not args.species_author[1] and args.species_author[2] then return nil end local default = defaultSisters[prefix] if default == 'auto' then default = args.auto end -- Handle exceptions by prefix local search = ((prefix == 'd' and "ItemByTitle/enwiki/") or "Search/")..args[1] local sitelink = prefix == 'd' and args.qid local page = args[prefix] if prefix == 's' and args.author and page[1] then page[1] = "Author:"..page[1] end local info = sisterInfo[prefix] if prefix == 'voy' and not args.bar and not page[1] and page[2] ~= nil then info = "Travel information" end info = args.information or info if prefix == 'c' then local commons = commonsLinks(args, page) search = commons.search sitelink = commons.link end prefix = (prefix == 'species_author' and 'species') or prefix return genSisterLink({   	page,    	auto=args.auto,    	qid=args.qid,    	sitelink=sitelink,    	default=default,    	sisterPrefix = prefix,    	search=search,    	information=info}, tracking) end

-- Function to create html containers for sister project link list -- Arguments: --  args = table of arguments --     args.position: if 'left', position links to left --     args.collapsible: if non-empty, make box collapsible. If 'collapse', start box hidden --     args.style: CSS style string appended to end of default CSS --     args.display: boldface name to display local function createContainer(args) -- Outermost div (css from previous version of Template:Project sister links) local container = mw.html.create('div') container:attr("role","navigation") container:attr("aria-labelledby","sister-projects") container:addClass("metadata") container:addClass("plainlinks") container:addClass("sistersitebox") container:addClass("plainlist") container:addClass(sandbox("sister-box")) if args.position and args.position:lower == "left" then container:addClass("mbox-small-left") else container:addClass("mbox-small") end if args.collapsible then container:addClass("mw-collapsible") if args.collapsible == "collapsed" then container:addClass("mw-collapsed") end end container:cssText(args.style) -- Div for text header local header = container:tag('div') header:css("clear",args.collapsible and "both") -- pagename in bold as part of header local pagename = header:tag('b') pagename:wikitext(args.display or args[1]) local headerText = "at Wikipedia's "	headerText = headerText..' sister projects ' header:wikitext(headerText) -- start the unordered list element here local ul = container:tag('ul') ul:addClass(args.collapsible and "mw-collapsible-content") -- pass ul element back to main, so sister links can be added return ul end

local function createSisterBox(sisterList,args) local container = createContainer(args) for i, link in ipairs(sisterList) do	 local li = container:tag('li') -- html element for 27px-high logo local logoSpan = li:tag('span') logoSpan:addClass(sandbox("sister-logo")) logoSpan:wikitext("") -- html element for link local linkspan = li:tag('span') linkspan:addClass(sandbox("sister-link")) local linkText = ""..link.information .." from "..sisterName[link.prefix] linkspan:wikitext(linkText) end return container:allDone end

local function createSisterBar(sisterList,args) local nav = mw.html.create( 'div' ) nav:addClass( 'noprint') nav:addClass( 'metadata') nav:addClass( sandbox('sister-bar')) nav:attr( 'role', 'navigation' ) nav:attr( 'aria-label', 'sister-projects' ) local header = nav:tag('div') header:addClass(sandbox('sister-bar-header')) local pagename = header:tag('b') pagename:wikitext(args.display or args[1]) local headerText = " at Wikipedia's "	headerText = headerText..' sister projects :' header:wikitext(headerText) if #sisterList == 1 then header:wikitext("") end local container = nav:tag('div') container:addClass(sandbox('sister-bar-content')) for _, link in ipairs(sisterList) do		local item = container:tag('div') item:addClass(sandbox('sister-bar-item')) local logoSpan = item:tag('span') logoSpan:addClass(sandbox('sister-bar-logo')) logoSpan:wikitext("") local linkSpan = item:tag('span') linkSpan:addClass(sandbox('sister-bar-link')) linkSpan:wikitext(""..link.information .." from "..sisterName[link.prefix]) end return nav end

function p._main(args) local titleObject = mw.title.getCurrentTitle -- find qid, either supplied with args, from search string, or from current page args.qid = args.qid or mw.wikibase.getEntityIdForTitle(args[1] or "") or mw.wikibase.getEntityIdForCurrentPage args.qid = args.qid and args.qid:upper -- search string defaults to PAGENAME args[1] = args[1] or mw.wikibase.getSitelink(args.qid or "") or titleObject.text -- handle redundant "commons"/"c" prefix args.c = args.c or args.commons -- Canonicalize all sister links (handle yes/no/empty) for _, k in ipairs(prefixList) do		args[k] = canonicalize(args[k]) end -- Canonicalize general parameters for _,k in pairs({"auto","commonscat","author","bar","tracking","sandbox"}) do   	args[k] = canonicalize(args[k])[2] end -- Initialize tracking categories if main namespace local tracking = (args.tracking or titleObject.namespace == 0) and {} local sisterList = {} local prefix -- Loop through all sister projects, generate possible links for _, prefix in ipairs(prefixList) do   	local link = sisterLink(prefix, args, tracking) if link then table.insert(sisterList, link) end end local box = mw.html.create('div') if args.bar and #sisterList > 0 then box = createSisterBar(sisterList,args) elseif #sisterList == 1 then -- Use single sister box instead of multi-sister box local sister = sisterList[1] local frame = mw.getCurrentFrame local link = ""..(args.display or args[1]).."" box:wikitext(sideBox({position=args.position, image="", metadata='no', textclass='plainlist', class='plainlinks sistersitebox', text=sisterName[sister.prefix].." has "..mw.ustring.lower(sister.information).." related to "..link})) elseif #sisterList > 0 then -- else use sister box if non-empty box = createSisterBox(sisterList,args) end if #sisterList == 0 and args.auto then box:wikitext(generateWarning({"No sister project links found in Wikidata. Try auto=0"})) end -- Append tracking categories to container div -- Alpha ordering is by sister prefix if tracking then for k, v in pairs(tracking) do			box:wikitext("") end if #sisterList == 0 then box:wikitext("") end end local styleFile = 'Module:Sister project links/' styleFile = styleFile..(inSandbox and 'sandbox/' or '') styleFile = styleFile..'styles.css' return mw.getCurrentFrame:extensionTag{ name = 'templatestyles', args = { src = styleFile } } .. tostring(box)

end

-- Main entry point for generating sister project links box function p.main(frame) local args = getArgs(frame,{frameOnly=false,parentOnly=false,parentFirst=false}) return p._main(args) end

-- Lua entry point for generate one sister link function p._sisterlink(args) local prefix = args.prefix -- Canonicalize all sister links (handle yes/no/empty) for _, k in ipairs(prefixList) do		args[k] = canonicalize(args[k]) end -- Canonicalize general parameters for _,k in pairs({"auto","commonscat","author"}) do   	args[k] = canonicalize(args[k])[2] end args[1] = args[1] or mw.title.getCurrentTitle.text args.qid = args.qid or mw.wikibase.getEntityIdForCurrentPage args.qid = args.qid and args.qid:upper local link = sisterLink(prefix, args,nil) if not link then return "" end return ""..link.information .." from "..sisterName[link.prefix] end

-- Template entry point for generating one sister link function p.link(frame) local args = getArgs(frame) return p._sisterlink(args) end

return p