Module:Sister project links

Safer nicotine wiki Tobacco Harm Reduction
Revision as of 09:09, 7 September 2021 by Richardpruen (talk | contribs) (1 revision imported)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Sister project links/doc

-- 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 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 sisterName = {
	wikt="Wiktionary",
	c="Wikimedia Commons",
	n="Wikinews",
	q="Wikiquote",
	s="Wikisource",
	b="Wikibooks",
	voy="Wikivoyage",
	v="Wikiversity",
	d="Wikidata",
	species="Wikispecies",
	m="Meta-Wiki",
	mw="MediaWiki"}

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"}

-- 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
	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 sisterLink(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 ~= nil 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

-- Function to add one sister to sister list
-- Arguments:
--   sisterList = accumulated list of sisters
--   args = arguments for this sister (see p._sisterLink above)
--   tracking = tracking table
local function oneSister(sisterList,args,tracking)
	local link = sisterLink(args,tracking)
	if link then
		table.insert(sisterList, link)
	end
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")
	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:css("border","1px solid #aaa")
	container:css("padding",0)
	container:css("background","#f9f9f9")
	container:cssText(args.style)
	-- Div for text header
	local header = container:tag('div')
	header:css("clear",args.collapsible and "both")
	header:css("padding","0.75em 0")
	header:css("text-align","center")
	-- pagename in bold as part of header
	local pagename = header:tag('b')
	pagename:css("display","block")
	pagename:wikitext(args.display or args[1])
	local headerText = "at Wikipedia's [[Wikipedia:Wikimedia sister projects|"
	headerText = headerText..'<span id="sister-projects">sister projects</span>]]'
	header:wikitext(headerText)
	-- start the unordered list element here
	local ul = container:tag('ul')
	ul:addClass(args.collapsible and "mw-collapsible-content")
	ul:css("border-top","1px solid #aaa")
	ul:css("padding","0.75em 0")
	ul:css("width","217px")
	ul:css("margin","0 auto")
	-- pass ul element back to main, so sister links can be added
	return ul
end

local function createSisterBox(sisterList,args)
	container = createContainer(args)
    for i, link in ipairs(sisterList) do
	  local li = container:tag('li')
	  li:css("min-height","31px")
	  -- html element for 27px-high logo
	  local logoSpan = li:tag('span')
	  logoSpan:css("display","inline-block")
	  logoSpan:css("width","31px")
	  logoSpan:css("line-height","31px")
	  logoSpan:css("vertical-align","middle")
	  logoSpan:css("text-align","center")
	  logoSpan:wikitext("[[File:"..logo[link.prefix].."|27x27px|middle|link=|alt=]]")
	  -- html element for link
	  local linkspan = li:tag('span')
	  linkspan:css("display","inline-block")
	  linkspan:css("margin-left","4px")
	  linkspan:css("width","182px")
	  linkspan:css("vertical-align","middle")
	  local linkText = "[["..link.prefix..":"..link.link.."|"..link.information .."]] from "..sisterName[link.prefix]
	  linkspan:wikitext(linkText)
    end
    return container:allDone()
end


function p._main(args)
	local titleObject = mw.title.getCurrentTitle()
	-- Default title/search string is PAGENAME
	args[1] = args[1] or titleObject.text
	-- Canonicalize all sister links (handle yes/no/empty)
	for _, k in pairs({"wikt","c","commons","n","q","s","b","voy",
		                "v","d","species","species_author","m","mw"}) 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
    -- Fill in args.qid with current qid (if available)
    args.qid = args.qid or mw.wikibase.getEntityIdForCurrentPage()
	args.qid = args.qid and args.qid:upper()
	-- Initialize tracking categories if main namespace
	local tracking = nil
	if titleObject.namespace == 0 then
		tracking = {}
	end
    local sisterList = {}
	-- WIKTIONARY
	oneSister(sisterList,{args.wikt,auto=args.auto,qid=args.qid,default=true,
		             sisterPrefix="wikt",search="Search/"..args[1],
		             information="Definitions"},tracking)
    -- COMMONS
    --     use [[Module:Commons link]] to determine best commons link
	local cLink = (not args.commonscat) and commonsLink._hasGallery(args.qid)
	                 or commonsLink._hasCategory(args.qid)
	local commonsPage = mergeArgs({args.c,args.commons})
	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]
	oneSister(sisterList,{commonsPage,auto=args.auto,qid=args.qid,
					 default=true,sitelink=cLink,
					 sisterPrefix="c",search=commonsSearch,information="Media"},
					 tracking)
	-- WIKINEWS
	oneSister(sisterList,{args.n,auto=args.auto,qid=args.qid,default=true,
					 sisterPrefix="n",search="Search/"..args[1],
					 information="News"},tracking)
	-- WIKIQUOTE
	oneSister(sisterList,{args.q,auto=args.auto,qid=args.qid,default=true,
					 sisterPrefix="q",search="Search/"..args[1],
					 information="Quotations"},tracking)
	-- WIKISOURCE
	if args.author and args.s[1] then
		args.s[1] = "Author:"..args.s[1]
	end
	oneSister(sisterList,{args.s,auto=args.auto,qid=args.qid,default=true,
					 sisterPrefix="s",search="Search/"..args[1],
					 information="Texts"},tracking)
	-- WIKIBOOKS
	oneSister(sisterList,{args.b,auto=args.auto,qid=args.qid,default=true,
		             sisterPrefix="b",search="Search/"..args[1],
		             information="Textbooks"},tracking)
    -- WIKIVOYAGE
    -- Information="guide" when we can be assured that search cannot be called
    local voyInfo = (args.voy[1] or args.voy[2] == nil) and "guides" or "information"
    oneSister(sisterList,{args.voy,auto=args.auto,qid=args.qid,default=args.auto,
    	             sisterPrefix="voy",search="Search/"..args[1],
    	             information="Travel "..voyInfo},tracking)
    -- WIKIVERSITY
    oneSister(sisterList,{args.v,auto=args.auto,qid=args.qid,default=true,
    	             sisterPrefix="v",search="Search/"..args[1],
    	             information="Resources"},tracking)
    -- WIKIDATA
    oneSister(sisterList,{args.d,qid=args.qid,default=false,sisterPrefix="d",
    	             sitelink=args.qid,search="ItemByTitle/enwiki/"..args[1],
    	             information="Data"},tracking)
    -- WIKISPECIES
    oneSister(sisterList,{mergeArgs({args.species,args.species_author},1),auto=args.auto,
    	             qid=args.qid,default=args.auto,
    	             sisterPrefix="species",search="Search/"..args[1],
    	             information=(args.species[2] and "Taxons")
    	                          or (args.species_author[2] and "Authorship") 
    	                          or "Taxons",},tracking)
    if args.species[2] and args.species_author[1] then 
    	-- If species_author is explicitly defined, and species is used, then make second row
    	oneSister(sisterList,{args.species_author,auto=args.auto,qid=args.qid,
    		             default=args.auto,
    	                 sisterPrefix="species",search="Search/"..args[1],
    	                 information="Authorship"},tracking)
    end
    -- META
    oneSister(sisterList,{args.m,qid=args.qid,default=false,
    	             sisterPrefix="m",search="Search/"..args[1],
    	             information="Discussions",},tracking)
    -- MEDIAWIKI
    oneSister(sisterList,{args.mw,qid=args.qid,default=false,
    	             sisterPrefix="mw",search="Search/"..args[1],
    	             information="Documentation",},tracking)
    local box = mw.html.create('div')
    if #sisterList == 1 then
    	-- Use  single sister box instead of multi-sister box
    	local sister = sisterList[1]
    	local frame = mw.getCurrentFrame()
    	local link =  "[["..sister.prefix..":"..sister.link.."|<b><i>"..(args.display or args[1]).."</i></b>]]"
    	box:wikitext(sideBox({position=args.position,
    		image="[[File:"..logo[sister.prefix].."|40x40px|class=noviewer|alt=|link=]]",
    		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
	-- Append tracking categories to container div
	-- Alpha ordering is by sister prefix
	if tracking ~= nil then
		for k, v in pairs(tracking) do
			box:wikitext("[[Category:"..trackingType[k].."|"..v.."]]")
	    end
	end
	return 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 tostring(p._main(args))
end

-- Lua entry point for generate one sister link
function p._sisterlink(args)
	args[1] = canonicalize(args[1])
	args.auto = canonicalize(args.auto)[2]
	args.qid = args.qid or mw.wikibase.getEntityIdForCurrentPage()
	args.qid = args.qid and args.qid:upper()
	local link = sisterLink(args,nil)
	return "[["..link.prefix..":"..link.link.."|"..link.information .."]] from "..sisterName[link.prefix]
end

-- Template entry point for generating one sister link
function p.link(frame)
	local args = getArgs(frame,{frameOnly=false,parentOnly=false,parentFirst=false})
	return p._sisterlink(args)
end

return p