跳转到内容
打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
登录后可编辑和发表评论。

Module:External

来自Vocawiki

用于{{External}},站点数据在Module:External/data。

local acandy = require( 'Module:ACandy' )
local data = require( 'Module:External/data' )

local a, Raw, Fragment = acandy.a, acandy.Raw, acandy.Fragment

local wikis = data.wikis
local byAlias = data.byAlias
local languages = data.languages or {}

local p = {}

-- 参数 ------------------------------------------------------------------

-- 依次取第一个非空参数
local function arg( args, ... )
	for _, name in ipairs( { ... } ) do
		local value = args[ name ]
		if value then
			value = mw.text.trim( value )
			if value ~= '' then
				return value
			end
		end
	end
end

-- 模板调用时frame:getParent().args是惰性表:next()恒为nil,只有pairs()能遍历
local function hasArgs( args )
	for _ in pairs( args ) do
		return true
	end
	return false
end

-- 站点 ------------------------------------------------------------------

-- 站点标识或别名→数据表;大小写不敏感
local function resolve( key )
	return byAlias[ mw.ustring.lower( mw.text.trim( key or '' ) ) ]
end

-- 这些参数名是设置项,不能当站点名
local reserved = {
	wiki = true, site = true, title = true, page = true,
	label = true, text = true, note = true, lang = true, name = true,
}

-- 收集卡片
local function collect( args )
	local named = {}
	local unknown, blank = {}, {}

	-- wiki=/site=:整次调用只针对这一个站点
	local key = arg( args, 'wiki', 'site' )
	if key then
		local wiki = resolve( key )
		if not wiki then
			error( 'External:未知的外部 wiki「' .. key .. '」,见 [[Module:External/data]]。' )
		end
		return { {
			wiki = wiki,
			title = arg( args, 'title', 'page', 1, 2 ) or mw.title.getCurrentTitle().text,
		} }
	end

	-- 位置参数:第1位站点、第2位条目,条目省略时用本页页名
	local unknownPositional
	local site = arg( args, '1' )
	if site then
		local wiki = resolve( site )
		if wiki then
			named[ wiki.id ] = {
				wiki = wiki,
				title = arg( args, '2', 'title', 'page' ) or mw.title.getCurrentTitle().text,
			}
		else
			unknownPositional = site
		end
	end

	-- 具名“站点=条目”
	for name, value in pairs( args ) do
		if type( name ) == 'string' and not reserved[ name ] and not name:match( '^%d+$' ) then
			local wiki = resolve( name )
			if wiki then
				value = mw.text.trim( value )
				if value == '' then
					blank[#blank+1] = name
				else
					named[ wiki.id ] = { wiki = wiki, title = value }
				end
			else
				unknown[#unknown+1] = name
			end
		end
	end

	-- 按数据表的声明顺序出卡片
	local list = {}
	for _, wiki in ipairs( wikis ) do
		local item = named[ wiki.id ]
		if item then
			list[#list+1] = item
		end
	end

	if #list > 0 then
		return list
	end
	if unknownPositional then
		error( 'External:未知的外部wiki“' .. unknownPositional .. '”,见[[Module:External/data]]。' )
	end
	if #unknown > 0 then
		table.sort( unknown )
		error( 'External:未知的外部wiki“' .. table.concat( unknown, '”“' )
			.. '”,见[[Module:External/data]]。' )
	end
	if #blank > 0 then
		table.sort( blank )
		error( 'External:外部wiki“' .. table.concat( blank, '”“' )
			.. '”没有写条目名。' )
	end
	error( 'External:未指定外部wiki,请用“站点=条目”或“wiki=站点”。' )
end

-- 条目名与URL -----------------------------------------------------------

-- 拆出#锚点
local function splitFragment( title )
	local page, fragment = title:match( '^([^#]*)#(.*)$' )
	if page then
		return page, fragment
	end
	return title
end

-- 拆出语言前缀;只有多语言站点(维基百科)需要
local function splitLang( title, wiki )
	if wiki.defaultLang then
		local code, rest = title:match( '^([%a][%w%-]*):(.+)$' )
		if code and mw.language.isKnownLanguageTag( code:lower() ) then
			return code:lower(), mw.text.trim( rest )
		end
	end
	return nil, title
end

local function buildUrl( wiki, lang, page, fragment, args )
	local encoded = mw.uri.encode( page, wiki.encode or 'WIKI' )
	local url
	if type( wiki.url ) == 'function' then
		url = wiki.url( encoded, lang, args )
		if not url then
			return nil
		end
	elseif wiki.defaultLang then
		url = string.format( wiki.url, lang or wiki.defaultLang, encoded )
	else
		url = string.format( wiki.url, encoded )
	end
	if fragment then
		url = url .. '#' .. mw.uri.encode( fragment )
	end
	return url
end

-- 文字 ------------------------------------------------------------------

local function siteName( wiki, lang )
	local name = wiki.name
	if lang and wiki.langInName ~= false then
		name = ( languages[ lang ] or mw.language.fetchLanguageName( lang, 'zh' ) ) .. name
	end
	return name
end

-- 非中文站点的条目名不参与中文变体转换。
local function noConvert( text, lang, custom )
	if custom or not lang or lang:sub( 1, 2 ) == 'zh'
		or text:find( '|', 1, true ) or text:find( '-{', 1, true ) then
		return text
	end
	return Fragment{ Raw( '-{' ), text, Raw( '}-' ) }
end

-- 卡片 ------------------------------------------------------------------

-- 图标:文件不存在就不输出[[File:]],缺图会渲染成上传红链,把卡片拆开
local function logoExists( wiki )
	return wiki.logo and mw.title.new( 'File:' .. wiki.logo ).fileExists
end

-- 卡片内容:返回HTML字符串,整段交给{{#externallink}}当显示内容再解析一次。
local function buildContent( wiki, name, label, note, lang )
	local children = {}

	if logoExists( wiki ) then
		local classes = { 'extwiki-logo' }
		if wiki.pixelated then
			classes[#classes+1] = 'extwiki-logo--pixelated'
		end
		if wiki.rounded then
			classes[#classes+1] = 'extwiki-logo--rounded'
		end
		-- 低分辨率图标按原始尺寸取,交给CSS做像素化放大。
		-- [[File:…]]要留给解析器展开,所以是Raw
		local file = wiki.pixelated
			and ( '[[File:' .. wiki.logo .. '|link=|alt=]]' )
			or ( '[[File:' .. wiki.logo .. '|24px|link=|alt=]]' )
		children[#children+1] = a.span[ table.concat( classes, ' ' ) ]( Raw( file ) )
	end

	-- ACandy的元素只接受一个props参数,多个子节点要写成表;
	-- 字符串子节点由ACandy负责转义
	children[#children+1] = a.span['extwiki-text']{
		a.span['extwiki-label text-xs']( name .. '有' .. note .. ':' ),
		a.span{
			class = 'extwiki-title text-sm',
			lang = lang,
			dir = lang and 'auto' or nil,
			[1] = label,
		},
	}

	children[#children+1] = a.span['extwiki-go']{ ['aria-hidden'] = 'true' }

	return tostring( Fragment( children ) )
end

-- 一张卡片:single为真时label/lang/name才生效(多站点时这些参数会让所有卡片重名)
local function makeCard( frame, item, args, single )
	local wiki = item.wiki
	local lang, rest = splitLang( item.title, wiki )
	local override = single and arg( args, 'lang' )
	if override then
		lang = override:lower()
	end
	-- 固定语言站点(zhwp/enwp/jawp)没写语言前缀,但条目名仍属于该语言
	local displayLang = lang or wiki.fixedLang

	local page, fragment = splitFragment( rest )
	local url = page ~= '' and buildUrl( wiki, lang, page, fragment, args )
	if not url then
		return nil
	end

	local name = ( single and arg( args, 'name' ) ) or siteName( wiki, lang )
	local note = arg( args, 'note' ) or '相关页面'
	-- 没写label时用条目名:纯文本,交给ACandy转义;
	-- 写了label就当wikitext
	local custom = single and arg( args, 'label', 'text' )
	local label = custom and Raw( custom ) or page
	label = noConvert( label, displayLang, custom )

	return tostring( frame:callParserFunction( '#externallink', {
		url,
		buildContent( wiki, name, label, note, displayLang ),
		class = 'extwiki-card',
		title = '在' .. name .. '上查看「' .. page .. '」',
	} ) )
end

-- 入口 ------------------------------------------------------------------

function p.main( frame )
	local args = frame:getParent().args
	if not hasArgs( args ) then
		args = frame.args
	end

	local list = collect( args )
	local single = #list == 1

	-- 一次调用的所有卡片放进同一个块元素,卡片在块里并排
	local props = { class = 'extwiki', ['data-nosnippet'] = '' }
	local any = false
	for _, item in ipairs( list ) do
		local card = makeCard( frame, item, args, single )
		if card then
			any = true
			props[#props+1] = Raw( card )
		end
	end

	return any and tostring( a.div( props ) ) or ''
end

return p