Module:Plain sister
Documentation for this module may be created at Module:Plain sister/doc
require('strict')
local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
-- table of site data
local sites = {
-- interwiki prefix: parameter, label and site id (for Wikidata)
['w'] = {'wikipedia', 'Wikipedia article', 'enwiki'},
['c'] = {'commons', 'Commons gallery', 'commonswiki'},
['c:Category'] = {'commonscat', 'Commons category', 'commonswiki'},
['q'] = {'wikiquote', 'quotes', 'enwikiquote'},
['n'] = {'wikinews', 'news', 'enwikinews'},
['wikt'] = {'wiktionary', 'definition', 'enwiktionary'},
['b'] = {'wikibooks', 'textbook', 'enwikibooks'},
['v'] = {'wikiversity', 'course', 'enwikiversity'},
['wikispecies'] = {'wikispecies', 'taxonomy', 'specieswiki'},
['voy'] = {'wikivoyage', 'travel guide', 'enwikivoyage'},
['d'] = {'wikidata', 'Wikidata item', 'wikidatawiki'},
['m'] = {'meta', 'Meta', 'metawiki'}
}
-- sites is display order (keyed as above)
local sites_in_order = {'w', 'c', 'c:Category', 'q', 'n', 'wikt', 'b', 'v', 'wikispecies', 'voy', 'd', 'm'}
-- some properties are not wanted from certain transitive links
-- for example, the P921 (main topic) should not add the Commons category
-- this is a map of WD property -> WD site ID keys
local transitiveLinkBlacklist = {
P921 = {'commonswiki', 'wikiquote', 'wikinews', 'wiktionary', 'wikiversity', 'wikivoyage', 'meta'},
}
--------------------------------------------------------------------------------
-- Get the item associated with the current page, or specified by the 'wikidata'
-- parameter (of either the module invocation, or the parent template).
-- @return mw.wikibase.entity
local function getItem(args)
local item = nil
-- Firstly, see if the calling tempate or module has a "wikidata" argument.
if args.wikidata then
item = mw.wikibase.getEntity(args.wikidata)
end
-- Failing that just use the current page's item.
if item == nil then
item = mw.wikibase.getEntity()
end
return item
end
--------------------------------------------------------------------------------
-- Get the page title of the first sitelink found on the target item for the
-- given property.
-- @return string|nil
local function getFirstSitelink(item, property, sitename)
local statements = item:getBestStatements(property)
if #statements > 0 then
-- Go through each 'edition of' statement.
for _, statement in pairs(statements) do
-- datavalue is missing if set to "unknown value"
if statement['mainsnak']['datatype'] == 'wikibase-item'
and statement['mainsnak']['datavalue'] then
local otherItemId = statement['mainsnak']['datavalue']['value']['id']
local sitelink = mw.wikibase.getSitelink(otherItemId, sitename)
-- If the parent has the required sitelink, return it.
if sitelink ~= '' and sitelink ~= nil then
-- mw.log(sitename, property, sitelink)
return sitelink
end
end -- if
end
end
return nil
end
local function listContains(list, item)
for _, v in pairs(list) do
if v == item then
return true
end
end
return false
end
local function transitivePropertyBlacklisted(prop, wdSitelinkKey)
-- reject prop/key pairs that we don't want
local blacklisted = transitiveLinkBlacklist[prop] and
listContains(transitiveLinkBlacklist[prop], wdSitelinkKey)
return blacklisted
end
function p.getLinks(args)
local item = getItem(args)
local links = {}
-- Build all the wikitext links.
for prefix, site in pairs(sites) do
local val = nil
local wd_sitelink_key = site[3]
local arg_name = site[1]
-- Allow overriding of individual sitelinks.
if args[arg_name] then
val = args[arg_name]
end
if not val and wd_sitelink_key ~= '' and item then -- fetch it from wikidata
val = item:getSitelink(wd_sitelink_key)
if wd_sitelink_key == 'wikidatawiki' and item.id then
val = item.id
elseif wd_sitelink_key == 'commonswiki' and val then -- we have link to commons
local catFlag = (#val>9 and string.sub(val, 1, 9) == 'Category:')
if (arg_name == 'commonscat' and catFlag==false) or (arg_name=='commons' and catFlag==true) then
val = nil -- link is to a wrong namespace so let's nuke it
elseif (arg_name =='commonscat' and catFlag==true) then
val = string.sub(val,10) -- trim 'Category:' from the front
end
end
end
-- Commons gallery.
if not val and arg_name == 'commons' and item then
local statements = item:getBestStatements('P935') -- get commons gallery page from P935 property
if statements[1] and statements[1].mainsnak.datavalue then
val = statements[1].mainsnak.datavalue.value
end
end
-- Commons category.
if not val and arg_name == 'commonscat' and item then
local statements = item:getBestStatements('P373') -- get commons category page from P373 property
if statements[1] and statements[1].mainsnak.datavalue then
val = statements[1].mainsnak.datavalue.value
end
end
-- edition or translation of (P629)
-- category's main topic (P301)
-- Wikimedia portal's main topic (P1204)
-- main subject (P921)
if item then
for _,prop in pairs({ 'P629', 'P301', 'P1204', 'P921' }) do
if not val and not transitivePropertyBlacklisted(prop, wd_sitelink_key) then
local workSitelink = getFirstSitelink(item, prop, wd_sitelink_key)
if workSitelink ~= nil then
val = workSitelink
break
end
end
end
end
if val then
links[prefix] = val
end
end
-- tidy up redundancies in the WD data
-- strip redundant commons category prefix
if links['c:Category'] then
links['c:Category'] = links['c:Category']:gsub('^Category:', '')
end
-- the gallery is exactly the same as the category, so just keep the category
if links['c'] and links['c:Category']
and ('Category:' .. links['c:Category']) == links['c'] then
links['c'] = nil
end
return links
end
--------
local function construct_sisicon_span(args)
return mw.html.create('span')
:addClass('sisicon')
:wikitext('[[File:' .. args.image .. '|frameless|18px|link=' .. args.link .. '|alt=' .. args.alt .. ']]')
end
-- Get an HTML list of all links to all sister projects.
function p._interprojectPart(args)
local item = getItem(args)
local link_data = p.getLinks(args)
local links = {}
-- iterate the links in the desired order and construct Wikitext links
for k, v in pairs(sites_in_order) do
if link_data[v] then
local display = sites[v][2]
local target = v .. ':' .. link_data[v]
table.insert(links, '[[' .. target .. '|' .. display .. ']]')
end
end
if #links == 0 then -- links table length is zero
return nil
end
return mw.html.create('li')
:addClass('sisitem')
:node(construct_sisicon_span({
image = 'Wikimedia-logo.svg',
link = 'Special:sitematrix',
alt = 'Sister Projects.'
}))
:wikitext('[[Special:sitematrix|sister projects]]: ' .. table.concat(links, ', ') .. '.')
end
function p.interprojectPart(frame)
return p._interprojectPart(getArgs(frame))
end
local function slashed_list_to_links(itemString, prefix)
local items = mw.text.split(itemString, '%s*/%s*', false)
local itemLinks = {}
for _, item in pairs(items) do
table.insert(itemLinks, '[[' .. (prefix or '') .. item .. '|' .. item .. ']]')
end
return table.concat(itemLinks, ', ')
end
function p._plain_sister(args)
local current_frame = mw.getCurrentFrame()
local current_title = mw.title.getCurrentTitle()
local pagename = current_title.text
local item = getItem(args)
-- construct list
local ul_list = mw.html.create('ul'):addClass('plainSister')
if yesno(args.disambiguation) then
local dabItem = '<span class="dabicon">[[File:Disambiguation.svg|frameless|17px|link=WS:STYLE#Disambiguation.2C_versions_and_translations_pages|alt=Style Guide for disambiguation, version and translation pages.]]</span>'
.. 'Search for titles <span class="selfreference">[[Special:Search/intitle:"' .. pagename .. '"|containing]]</span> or '
if current_title:inNamespaces(14) then
dabItem = dabItem .. '[[Special:Categories/' .. pagename .. '|beginning]]'
elseif current_title:inNamespaces(0) then
dabItem = dabItem .. '[[Special:PrefixIndex/' .. current_title.fullText .. '|beginning]]'
else
dabItem = dabItem .. '[[Special:PrefixIndex/' .. current_title.fullText .. '|beginning (in ' .. current_title.nsText .. 's)]]'
end
dabItem = dabItem .. ' with: "' .. pagename .. '."'
ul_list:tag('li')
:addClass('dabitem')
:wikitext(dabItem)
end
local show_textinfo = args.textinfotitle or yesno(args.textinfo or args.edition)
if show_textinfo then
local edition_title
if args.textinfotitle then
edition_title = mw.title.new(args.textinfotitle)
else
edition_title = current_title
end
ul_list:tag('li')
:addClass('sisitem')
:node(construct_sisicon_span({
image = 'Information_icon.svg',
link = 'Template:Textinfo',
alt = 'Documentation for the TextInfo template.',
}))
:wikitext('[[' .. edition_title.talkNsText .. ':' .. edition_title.text .. '|information about this edition]].')
end
local portal = args.portal or args.portals
if portal then
ul_list:tag('li')
:addClass('sisitem')
:node(construct_sisicon_span({
image = 'Wikisource-logo.svg',
link = 'Portal:Portals',
alt = 'Related Portals.'
}))
:wikitext('[[Portal:Portals|related portals]]: ' .. slashed_list_to_links(portal, 'Portal:') .. '.')
end
local related_author = args.related_author or args.related_authors or args['related-author'] or args['related-authors']
if related_author then
ul_list:tag('li')
:addClass('sisitem')
:node(construct_sisicon_span({
image = 'System-users.svg',
link = 'Wikisource:Authors',
alt = 'Related Authors.'
}))
:wikitext('[[Wikisource:Authors|related authors]]: ' .. slashed_list_to_links(related_author, 'Author:') .. '.')
end
local related_work = args.related_work or args.related_works or args['related-work'] or args['related-works']
if related_work then
ul_list:tag('li')
:addClass('sisitem')
:node(construct_sisicon_span({
image = 'Nuvola apps bookcase.svg',
link = 'Wikisource:Works',
alt = 'Related Works.'
}))
:wikitext('[[Wikisource:Works|related works]]: ' .. slashed_list_to_links(related_work) .. '.')
end
local sisters = p._interprojectPart(args)
if sisters then
ul_list:node(sisters)
end
if yesno(args.wikidataswitch) and not item then
local wdSearch = '<span class="sisicon">[[File:Wikidata-logo.svg|frameless|17px|link=Wikisource:Wikidata|alt=Wikidata.]]</span>'
.. '[[d:Special:Search/' .. pagename .. '|Search Wikidata]].'
ul_list:tag('li')
:addClass('sisitem')
:wikitext(wdSearch)
end
if not yesno(args.disambiguation) and not show_textinfo and not portal and not related_author and not related_work and not sisters and not yesno(args.wikidataswitch) then
return nil
end
return current_frame:extensionTag('templatestyles', '', {src = 'Plain sister/styles.css'}) .. tostring(ul_list)
end
function p.plain_sister(frame)
return p._plain_sister(getArgs(frame))
end
return p
-- Debug console testing:
-- =p.interprojectPart(mw.getCurrentFrame():newChild{title='nop',args={wikidata='Q23308118'}})