Перейти к содержанию

Амодуль:External links

Аматериал Авикипедиа аҟынтә - зхы иақәиҭу аенциклопедиа

This module is designed for implementing templates to display external links in an English Wikipedia article, using the properties defined in that article's Wikidata item. It was based on the Norwegian Wikipedia module no:Modul:External links.

The first test implementation is Template:Sports links (based on no:Mal:Sportslenker). The sports-related link properties and formats are specified in Module:External links/conf/Sports (based on no:Modul:External links/conf/Sport).

Other subject-based implementations at the Norwegian Wikipedia include:

{{#invoke:External links|function_name}}


local data = require( 'Module:External links/data' )
-- Localizable part
-- Please note that labels for websites and catalogs are taken from Wikidata (i.e. Wikidata label)

-- Feel free to correct labels and categories here
local categoryTemplateEmpty = 'Авикипедиа:Ашаблон «ВС» ҭацәуп'
local categoryCustomColor = 'Авикипедиа:Ашаблон «ВС» иастандарттәым аԥштәы аманы'
local templateLink = 'ВС'
local templateTitle = 'Адәныҟатәи аресурсқәа рахь азхьарԥшқәа'
local wikidataLink = 'Авикидырраҿтәи аелемент ахь аиасра'
local refLabels = {
	Q1406 = 'Windows',
	Q10677 = 'PS1',
	Q10680 = 'PS2',
	Q10683 = 'PS3',
	Q16338 = 'ПК',
	Q135321 = 'FDS',
	Q170323 = 'DS',
	Q170325 = 'PSP',
	Q172742 = 'NES',
	Q182172 = 'GameCube',
	Q183259 = 'SNES',
	Q184839 = 'N64',
	Q188642 = 'GBA',
	Q188808 = 'PS Vita',
	Q203597 = '3DS',
	Q5014725 = 'PS4',
	Q19610114 = 'Switch',
	Q63184502 = 'PS5',
}

-- The language codes that should always be displayed even if they have normal rank and a claim with another language and preferred rank exists
local preferredLanguage = 'Q7737' -- russian

local templateColorName = 'цвет'
-- Some projects have "named" colors, defined by templates
local function colorByTitle( frame, colorTitle )
	local templateName = 'Цвет/' .. colorTitle
	local templateTitle = mw.title.makeTitle( 'Template', templateName )
	if not templateTitle or not templateTitle.exists then
		return false
	end
	return frame:expandTemplate{ title = templateName }
end

-- Non-localizable part (not need to localize )
local moduleNavbox = require( 'Module:Navbox' )
local moduleNavbar = require( 'Module:Navbar' )._navbar -- can be changed to a regular one
local moduleLanguages -- accessed if necessary

local propertyQualifiers = {
	P553 = {
		P554 = 'url',
	},
	P1343 = {
		P805 = 'iw',
		P248 = 'iw', -- deprecated, fallback for P805
		P953 = 'url',
		P854 = 'url', -- deprecated, fallback for P953
	},
}

local p = {}

-- Helper functions
local function replace( str, pattern, repl )
    pattern = mw.ustring.gsub( pattern, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1" ) -- escape pattern
    repl = mw.ustring.gsub( repl, "[%%]", "%%%%" ) -- escape replacement
    repl = mw.ustring.gsub( repl, " ", "%%%%20" ) -- escape replacement
    return mw.ustring.gsub( str, pattern, repl )
end


-- Render functions
local function renderList( elements )
	if #elements == 0 then
		return ''
	end

	return '*' .. table.concat( elements , '\n*' ) .. '\n'
end

local function renderLabel( params )
	if type( params ) == 'string' then
		return params
	end

	local qid = params[ 1 ]
	local default = params[ 2 ]

	if #params >= 3 then
		local label = params[ 3 ]
		local link = mw.wikibase.sitelink( qid )
		if link then
			return '[[' .. link .. '|' .. label .. ']]'
		end
		local title = mw.wikibase.label( qid ) or default
		if title ~= label then
			return '<span title="' .. title .. '" style="border-bottom: 1px dotted; cursor: help;">' .. label .. '</span>'
		end
	end

	return mw.wikibase.label( qid ) or default
end

local function renderLink( resourceData, label, formatter, idAsLabel )
	if resourceData.itemId == nil then
		return '<span class="error">' .. label .. ': Не удаётся определить элемент</span>[[Категория:Статьи с ошибкой в шаблоне Внешние ссылки]]'
	end

	local link
	if not formatter then
		link = resourceData.itemId
		idAsLabel = false
	elseif type( formatter ) == 'string' then
		link = replace( formatter, '$1', resourceData.itemId )
	elseif type ( formatter ) == 'function' then
		link = formatter( resourceData.itemId, resourceData.qualifiers )
	end
	
	-- "Label: ID" without link
	if not link or link == '' then
		return renderLabel( label ) .. ':&nbsp;' .. resourceData.itemId
	end

	local resourceLabel = resourceData.itemId
	if not idAsLabel then
		resourceLabel = renderLabel( label )
	end

	local linkFirstChar = mw.ustring.sub( link, 1, 1 )
	if linkFirstChar == ':' then
		return '[[' .. link .. '|' .. resourceLabel .. ']]'
	end

	return '[' .. link .. ' ' .. resourceLabel .. ']'
end

local function renderLangRef( languages )
	local result = ''
	if languages and #languages > 0 then
		if moduleLanguages ~= false then -- not false, but maybe nil
			if mw.title.makeTitle( 'Module', 'Languages' ).exists
					and mw.title.makeTitle( 'Module', 'Languages/data' ).exists
					and mw.title.makeTitle( 'Module', 'Wikidata/Language-codes' ).exists then
				moduleLanguages = require( 'Module:Languages' )
			else
				moduleLanguages = false
			end
		end
		
		if moduleLanguages then
			for langIndex, language in pairs( languages ) do
				result = result .. '&nbsp;' .. moduleLanguages.getWikidataRefHtml( language )
			end
		end
	end

	return result
end

local function renderRefs( refs )
	local result = ''
	if refs and #refs > 0 then
		for _, ref in ipairs( refs ) do
			result = result .. '&nbsp;<span class="ref-info">(' .. ref .. ')</span>'
		end
	end

	return result
end

local function renderLinkWithRef( resourceData, label, formatter, idAsLabel )
	local link = renderLink( resourceData, label, formatter, idAsLabel )
	if link ~= '' then
		link = link .. renderLangRef( resourceData.languages )
		link = link .. renderRefs( resourceData.refs )
	end
	return link
end


-- Data fetching functions
local function getValueFromSnak( snak )
	if not snak.datavalue then 
		return 
	end
	if snak.datavalue.type == 'wikibase-entityid' then
		return snak.datavalue.value.id
	end
	if snak.datavalue.type == 'monolingualtext' then
		return snak.datavalue.value.text
	end
	return snak.datavalue.value
end

local function getQualifierSingleValue( statement, qualifierName )
	if not statement or not statement.qualifiers or not statement.qualifiers[ qualifierName ] then
		return nil
	end

	for qualifierIndex, qualifier in pairs( statement.qualifiers[ qualifierName ] ) do
		if qualifier.datavalue and qualifier.datavalue.type and qualifier.datavalue.value then
			return getValueFromSnak( qualifier )
		end
	end

	return nil
end

local function getQualifierValues( statement )
	if not statement or not statement.qualifiers then
		return {}
	end

	local result = {}

	for qualifierName, qualifiers in pairs( statement.qualifiers ) do
		for _, qualifier in pairs( qualifiers ) do
			if qualifier.datavalue and qualifier.datavalue.type and qualifier.datavalue.value then
				if not result[ qualifierName ] then
					result[ qualifierName ] = {}
				end
				table.insert( result[ qualifierName ], getValueFromSnak( qualifier ) )
			end
		end
	end

	return result
end

local function contains( tableStructure, value )
	if not tableStructure or not value then
		return true
	end
	for index, line in pairs( tableStructure ) do
		if line == value then
			return true
		end
	end
	return false
end

local function filterByRank( resourceDatas )
	-- itemId, languages. rank = rank

	local hasPreffered = false
	for index, resourceData in pairs( resourceDatas ) do
		if resourceData.rank == 'preferred' then
			hasPreffered = true
		end
	end

	if not hasPreffered then
		return resourceDatas
	end

	local result = {}
	for index, resourceData in pairs( resourceDatas ) do
		if resourceData.rank == 'preferred' or contains( resourceData.languages, preferredLanguage ) then
			table.insert( result, resourceData )
		end
	end

	return result
end

local function getLinkData( statement, qualifier, project )
	local rank = statement.rank or 'normal'
	if rank == 'deprecated' or not statement.mainsnak.datavalue then
		return nil
	end

	local itemId
	if qualifier then
		itemId = getQualifierSingleValue( statement, qualifier )
	else
		itemId = statement.mainsnak.datavalue.value
	end

	if itemId and project then
		itemId = mw.wikibase.getSitelink( itemId, project )
	end

	if not itemId then
		return nil
	end

	local qualifiers = getQualifierValues( statement )
	local languages = qualifiers[ 'P407' ]
	if not languages then
		languages = {}
	end

	local refs = {}
	if qualifiers[ 'P400' ] then
		for _, refQid in ipairs( qualifiers[ 'P400' ] ) do
			local refLabel = refLabels
			if refLabels[ refQid ] then
				refLabel = refLabels[ refQid ]
			else
				refLabel = mw.wikibase.label( refQid ) or refQid
			end
			if refLabel ~= refQid then
				if #qualifiers[ 'P400' ] > 3 then
					refLabel = refLabel .. ' +&nbsp;' .. ( #qualifiers[ 'P400' ] - 1 ) .. '&nbsp;платформ'
					table.insert( refs, refLabel )
					break
				end

				table.insert( refs, refLabel )
			end
		end
	end

	return {
		itemId = itemId,
		qualifiers = qualifiers,
		languages = languages,
		refs = refs,
		rank = rank,
	}
end

local function collectLinks( configuration, entityId, separateLabel )
	-- Create rows
	local elements = {}
	local data = {}

	for _, params in pairs( configuration ) do
		local resourceId = params
		local propertyId = resourceId[ 2 ]
		local qid
		if string.match( propertyId, '^P%d+:Q%d+$' ) then
			local parts = mw.text.split( propertyId, ':', true )
			propertyId = parts[ 1 ]
			qid = parts[ 2 ]
		end

		local claims = mw.wikibase.getBestStatements( entityId, propertyId ) or {}
		for _, statement in pairs( claims ) do
			local linkData
			if not qid then
				linkData = getLinkData( statement )
			elseif getValueFromSnak( statement.mainsnak ) == qid then
				for qualifierId, qualifierType in pairs( propertyQualifiers[ propertyId ] ) do
					local project = nil
					if qualifierType == 'iw' then
						project = params.project
					end
					linkData = getLinkData( statement, qualifierId, project )
					if linkData then
						break
					end
				end
			end

			if linkData then
				if not data[ resourceId ] then
					data[ resourceId ] = {}
				end
				table.insert( data[ resourceId ], linkData )
			end
		end
	end

	for resourceId, resourceDatas in pairs( data ) do
		data[ resourceId ] = filterByRank( resourceDatas )
	end

	for _, params in pairs( configuration ) do
		local resourceId = params
		local resourceDatas = data[ resourceId ]
		if resourceDatas ~= nil then
			local links = {}
			for _, resourceData in pairs( resourceDatas ) do
				if separateLabel then
					-- Label: ID1, ID2
					table.insert( links, renderLinkWithRef( resourceData, '', params[ 3 ], true ) )
				else
					-- Label · Label
					local label = params[ 1 ]
					table.insert( elements, renderLinkWithRef( resourceData, label, params[ 3 ] ) )
				end
			end
			if separateLabel and #links then
				local result = renderLabel( params[ 1 ] ) .. ': ' .. table.concat( links, ', ' )
				table.insert( elements, result )
			end
		end
	end

	return elements
end


function p.render( frame )
	local colorArg = ''
	local byTemplate
	local entityId = nil
	if frame ~= nil then
		local parentArgs = frame:getParent().args
		colorArg = parentArgs[ templateColorName ] or parentArgs[ 'color' ] or parentArgs[ 1 ] or ''
		if parentArgs[ 'from' ] and parentArgs[ 'from' ] ~= '' then
			entityId = string.upper( parentArgs[ 'from' ] )
		elseif parentArgs[ 'd' ] and parentArgs[ 'd' ] ~= '' then
			entityId = string.upper( parentArgs[ 'd' ] )
		else
			entityId = mw.wikibase.getEntityIdForCurrentPage()
		end
		if colorArg ~= '' then
			local firstChar = mw.ustring.sub( colorArg, 1, 1 )
			if firstChar ~= '#' then
				byTemplate = colorByTitle( frame, colorArg )
				if byTemplate then
					colorArg = byTemplate
				end
			end
		end
	end
	
	if not entityId then
		return ''
	end

	local navboxData = {
		name  = 'External links',
		title = templateTitle,
		titlestyle = 'display:none',
		state = 'plain',
		bodyclass = 'hlist',
		bodystyle = 'padding-top:1px',
	}
	if colorArg and colorArg ~= '' then
		navboxData.groupstyle = 'background: ' .. colorArg .. ';'
	end

	local rowIndex = 1

	for _, groupData in pairs( data ) do
		local isAuthorityControl = groupData.isAuthorityControl
		local groupLabel = groupData.label
		local groupList = groupData.list
		local groupElements = collectLinks( groupList, entityId, isAuthorityControl )
		if #groupElements > 0 then
			navboxData[ 'group' .. rowIndex ] = groupLabel
			navboxData[ 'list' .. rowIndex ] = renderList( groupElements )

			if isAuthorityControl then
				if #groupElements > 5 then
					navboxData[ 'group' .. rowIndex ] = nil
					local templateStyles = frame:extensionTag{
						name = 'templatestyles',
						args = { src = 'Шаблон:Навигационная таблица/styles.css' }
					}
					local collapsibleNavbox = moduleNavbox._navbox( {
						title = groupLabel,
						list1 = navboxData['list' .. rowIndex],
						border = 'subgroup',
						navbar = 'plain',
						state = 'collapsed',
						titleclass = 'ts-navbox-plaintitle',
						bodyclass = 'authoritycontrol',
						titlestyle = navboxData.groupstyle,
						bodystyle = 'text-align: left;',
					} )
					navboxData[ 'list' .. rowIndex ] = templateStyles .. collapsibleNavbox
				end
			end

			rowIndex = rowIndex + 1
		end
	end

	if rowIndex == 1 then
		if mw.title.getCurrentTitle().namespace == 0 then
			return '[[Category:' .. categoryTemplateEmpty .. ']]'
		end
		return ''
	end

	local buttons = moduleNavbar({ templateLink }) ..
		'&nbsp;[[File:OOjs UI icon edit-ltr-progressive.svg|14px|' .. wikidataLink .. '|link=d:'
		.. (entityId or mw.wikibase.getEntityIdForCurrentPage()) .. '#identifiers]]'
	if navboxData[ 'group1' ] then
		navboxData[ 'group1' ] = '<div style="padding: 0 35px 0 0; width: 100%;">' ..
			'<div class="skin-invert-image" style="float: left;">' .. buttons .. '</div>&nbsp;&nbsp;' ..
			navboxData[ 'group1' ] .. '</div>'

	else
		navboxData[ 'group1' ] = '<div style="padding: 0; width: 100%;">' .. buttons .. '</div>'
	end

	local out = moduleNavbox._navbox( navboxData )
	
	if colorArg and colorArg ~= '' and not byTemplate then
		out = out .. '[[Category:' .. categoryCustomColor .. ']]'
	end
	
	return out
end

return p