Module:Navbox

From Old School RuneScape Wiki
Jump to: navigation, search

Documentation for this module may be created at Module:Navbox/doc

-- <nowiki>
--
-- Implements {{navbox}}
--

local p = {}
local tnavbar = require( 'Module:Tnavbar' )
local yesno = require( 'Module:Yesno' )
local onmain = require('Module:Mainonly').on_main()
local hc = require('Module:Paramtest').has_content
local page_title = mw.title.getCurrentTitle().fullText
--
-- Helper for inserting a new row into the navbox
--
-- @param tbl {mw.html table}
-- @return tbl {mw.html table}
--
local function insertRow( tbl )
	return tbl:tag( 'tr' )
end

--
-- Creates the navbox table
--
-- @param args {table}
-- @return tbl {mw.html table}
--
local function createTbl( args )

	local tbl = mw.html.create( 'table' )

	tbl
		:addClass( yesno( args.subgroup ) and 'navbox-subgroup' or 'navbox' )
		:addClass( 'nowraplinks' )

	if not yesno( args.subgroup ) and
		( args.state == 'collapsed' or
		  args.state == 'uncollapsed' or
		  args.state == 'autocollapse' or
		  -- defaults to autocollapse
		  args.state == nil )
	then
		tbl:addClass( 'mw-collapsible' )

		if args.state == 'collapsed' then
			tbl:addClass( 'mw-collapsed' )
		elseif args.state == 'uncollapsed' then
			tbl:addClass('navbox-uncollapsed')
		end
	end

	if yesno( args.collapsible ) then
		tbl:addClass( 'navbox-collapsible' )
	end

	if args.style then
		tbl:cssText( args.style )
	end

	-- manually set collapse/expand messages
	-- bug causing the default database messages to be used
	tbl
		:attr( {
			['data-expandtext'] = 'show',
			['data-collapsetext'] = 'hide'
		} )

	return tbl
end

--
-- Wrapper for [[Module:Tnavbar]]
--
-- @param args {table}
-- @return {string}
--
local function navbar( args )
	return tnavbar._collapsible( { [1] = args.title, [2] = args.name } )
end

--
-- Creates the header (what you see when the navbox is collapsed)
--
-- @param tbl {mw.html table}
-- @param args {table}
-- @return {mw.html table}
--
local function header( tbl, args )
	local div = insertRow( tbl )
		:tag( 'th' )
			:attr( 'colspan', '2' )
			:addClass( 'navbox-title' )
			:attr( 'id' , 'navbox-title' )
				:tag( 'div' )

	-- @todo move this to site css so we can simplify this (hook off a class)
	-- to something like div:wikitext( args.name and navbar( args ) or args.title )
	-- which can be appended to the above and returned straight away
	if args.name then
		div
			:css( 'padding-right', args.state == 'plain' and '6em' or '0' )
			:wikitext( navbar( args ) )
	else
		div
			:css( 'padding-left', args.state == 'plain' and '0' or '6em' )
			:wikitext( args.title )
	end

	return div:allDone()
end

--
-- Inserts a row into the navbox
--
-- @param tbl {mw.html table}
-- @param gtitle {string}
-- @param group {string}
-- @param gtype {string}
-- @param style {string}
-- @return {mw.html table}
--
local function row( tbl, gtitle, group, gtype, style, _name, subgroup )
	local tr = insertRow( tbl )
	local td

	if gtitle then
		td = tr
			:addClass( 'navbox-group' )
			:tag( 'td' )
				:addClass( 'navbox-group-title' )
				:wikitext( gtitle )
				:done()
			:tag( 'td' )
	else
		td = tr
			:addClass( 'navbox-group' )
			:addClass( 'navbox-group-split' )
			:tag( 'td' )
				:addClass( 'navbox-group-title-hidden' )
				:attr( 'colspan', '0' )
				:css( 'display', 'none' )
				:done()
			:tag( 'td' )
				:attr( 'colspan', '2' )
	end

	--[[
	   List styling
	   This is unlikely to be implemented in the near future due to it requiring extra css to work
	   and mobile currently not supporting that css.
	   As an example, it lets you do the following instead if using {{*}} all the time
	   | group3 =
	   * {{plink|foo}}
	   * {{plink|bar}}
	   * {{plink|baz}}
	]]
	if mw.ustring.match( group, '^%s*%*' ) then
		td:newline()

		-- trim whitespace on bullets
		local spl = mw.text.split( group, '\n' )

		for i = 1, #spl do
			spl[i] = mw.text.trim( spl[i] )
		end

		group = '\n' .. table.concat( spl, '\n' )		
	end

	--local group2 = group
	--local group3 = group2
	-- analytics

	--if _name then
	--	local name = mw.ustring.gsub(_name,' ','_')
	--	for v in mw.ustring.gmatch(group,'%[%[[^%]]+%]%]') do
	--		if mw.ustring.match(v,'%[%[File:.+|link=') then
	--			local link = mw.ustring.match(v,'|link=([^%]|]+)')
	--			if link then
	--				local linkrep = mw.ustring.gsub(link,'([%%%]%[%-^$*()+?])','%%%1')
	--				local _link = mw.ustring.gsub(link,' ','_')
	--				local newfile = mw.ustring.gsub(v,'|link='..linkrep,string.format('|link=https://oldschool.runescape.wiki/w/%s?f=%s',_link,name))
	--				local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')
	--				group2 = mw.ustring.gsub(group2,w,newfile)
	--			end
	--		elseif mw.ustring.match(v,'%[%[Category:') then
				-- nothing
	--		else
	--			local link = mw.ustring.match(v,'%[%[([^%]|]+)')
	--			local txt = mw.ustring.match(v,'%|([^%]|]+)') or link

	--			local newlink = ''

				-- black links if current page
	--			if link == page_title then
	--				newlink = string.format('<b>%s</b>',txt)
	--			else
	--				local _link = mw.ustring.gsub(link or '',' ','_')
	--				newlink = string.format('[https://oldschool.runescape.wiki.com/w/%s?n=%s %s]',_link,name,txt)
	--			end
	--			local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')
	--			group2 = mw.ustring.gsub(group2,w,newlink)
	--		end
	--	end

		--[==[
			fix [[these kind]]s of [[link]]s post analytics parse
			]==]
	--	group3 = group2

	--	for v in mw.ustring.gmatch(group2,'%[https://oldschool.runescape.wiki.com/w[^%]]-%]%a') do
	--		local rep = mw.ustring.gsub(v,'%]','')
	--		rep = rep..']'
	--		local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')

	--		group3 = mw.ustring.gsub(group2,w,rep)
	--	end
	--end

	
	local subgroups
	if gtype and mw.ustring.lower( gtype ) == 'subgroup' then
		td
			:addClass( 'navbox-parent' )
			:css( {
				padding = '0',
				['border-bottom'] = '0'
			} )
		subgroups = mw.ustring.match(group, '@@[email protected]@(.-)@@[email protected]@')
		group = mw.ustring.gsub(group, '@@[email protected]@[email protected]@[email protected]@', '')
	end
	
	td
		:addClass( 'navbox-list' )
		:wikitext( group ) --group3

	if style then
		td:cssText( style )
	end
	
	if yesno(subgroup) then
		td	--:tag('span')
			--	:css('display','none'):addClass('hidden')
				:wikitext('@@[email protected]@')
				:wikitext(_dumpGroup(gtitle, gtype, group, subgroups))
				:wikitext('@@[email protected]@')
	end

	return td:allDone()
end

--
-- Inserts a footer into the navbox
--
-- @param tbl {mw.html table}
-- @param args {table}
-- @return {mw.html table}
--
local function footer( tbl, args )
	local th = insertRow( tbl )
		:tag( 'th' )
			:attr( 'colspan', '2' )
			:addClass( 'navbox-footer' )

	if args.fstyle then
		th:cssText( args.fstyle )
	end

	if mw.ustring.match( args.footer, '^%s*%*' ) then
		th:newline()

		-- trim whitespace on bullets
		local spl = mw.text.split( args.footer, '\n' )

		for i = 1, #spl do
			spl[i] = mw.text.trim( spl[i] )
		end

		args.footer = table.concat( spl, '\n' )

		th:addClass( 'navbox-list' )
	end

	th:wikitext( args.footer )

	return th:allDone()
end

--
-- Adds [[Category:Navigational templates]] to navbox template pages
--
-- @return {string}
--
local function categories()
	local title = mw.title.getCurrentTitle()
	local page = title.text
	local ns = title.nsText

	if ns == 'Template' then
		-- sort in category by pagename
		return '[[Category:Navigational templates|' .. page .. ']]'
	else
		return ''
	end

end

--
-- Adds [[Template:Navbox/doc]] to navbox template pages
--
-- @param args {table}
-- @return {string}
--
local function docs( args )
	local frame = mw.getCurrentFrame()
	local title = mw.title.getCurrentTitle()
	local base = title.baseText
	local ns = title.nsText

		-- not if a subpage of [[Template:Navbox]]
	if base ~= 'Navbox' and
		-- in template ns
		ns == 'Template' and
		-- not a navbox group within a navbox
		not yesno( args.subgroup ) and
		-- not a collapsible navbox within a navbox
		not yesno( args.collapsible ) and
		-- not if the doc argument is set to "no"
		( args.doc == nil or yesno( args.doc ) )
	then
		return frame:expandTemplate{ title = 'Navbox/doc' }
	else
		return ''
	end

end

function _dumpGroup(gtitle, gtype, group, subgroups)
	local jsg, json = pcall(mw.text.jsonEncode, _jsonGroup(gtitle, gtype, group, subgroups))
	if jsg then
		return mw.text.nowiki(json)
	end
	return ''
end
function _jsonGroup(gtitle, gtype, group, subgroups)
	local ret = {
		title = gtitle,
		type = gtype
	}
	if hc(subgroups) then
		local jsg, json = pcall(mw.text.jsonDecode, mw.text.decode(subgroups))
		if jsg then
			ret.contents = json
		end
	else
		local contents = {}
		for i,v in ipairs(mw.text.split(group, '%*')) do
			if hc(v) then
				local item = {}
				local working
				for j in mw.ustring.gmatch(v, '%[%[(.-)%]%]') do
					if not mw.ustring.find(j, 'File:', 1, true) then
						local _j = mw.text.split(j, '|', true)
						if #_j == 1 then
							item.link = _j[1]
							item.text = _j[1]
						else
							item.link = _j[1]
							item.text = _j[2]
						end
						break --only want 1 at the mo
					end
				end
				
				working = mw.ustring.match(v, '%[%[File:(.-)[|%]]')
				if working then
					item.image = working
				end
				table.insert(contents, item)
			end
		end
		ret.contents = contents
	end
	return ret
end

--
-- Tracking SMW
--
-- @param _args {table}
-- @return {string}
--
function smw(args)
	if not onmain or yesno(args.subgroup) then
		return ''
	end
	local err = mw.html.create('div')
	err:addClass('hidden'):css('display','none')
	local haserr = false
	local info = ''
	-- more SMW here at some point
	
	local smwJSON = {
		name = args.name,
		title = args.title,
		groups = {}
	}
	
	for i=1,25 do
		if not args['group'..i] then
			break
		end
		table.insert(smwJSON.groups, _jsonGroup(args['gtitle'..i], args['gtype'..i], args['group'..i]))
	end
	
	--error(mw.dumpObject(smwJSON))
	
	local jsongood, encsmwJSON = pcall(mw.text.jsonEncode, smwJSON)
	if jsongood then
		-- for easier debug, please do not remove
		encsmwJSON = mw.text.nowiki(encsmwJSON)
		local div = mw.html.create('div')
		div	:addClass('hidden navbox-data')
			:css('display', 'none')
			:wikitext('Navbox JSON: [[Navbox JSON::'..encsmwJSON..']]')
		info = mw.getCurrentFrame():preprocess(tostring(div))
	else
		haserr = true
		err:wikitext('Error setting SMW JSON, string:\n' .. mw.dumpObject(smwJSON))
	end
	if not haserr then
		err = ''
	end
	
	return tostring(info) .. tostring(err)
end


--
-- Navbox method to allow it to be called by other modules
--
-- @param _args {table}
-- @return {string}
--
function p._navbox( _args )
	local args = {}
	local wkCss = ''
	local wkDiv = ''
	local j
	
	-- preserves parser function behaviour where an empty string is considered undefined
	-- or nil in lua's case
	for k, v in pairs( _args ) do
		if v ~= '' then
			args[k] = v
		end
	end

	local tbl = createTbl( args )

	if not yesno( args.subgroup ) then
		tbl = header( tbl, args )
	end

	-- insert up to 25 rows
	for i = 1, 25 do
		j = tostring( i )

		if args['group' .. j] then
			tbl = row( tbl, args['gtitle' .. j], args['group' .. j], args['gtype' .. j], args['style' .. j], args.name, args.subgroup )
		else
			break
		end
	end

	if args.footer then
		tbl = footer( tbl, args )
	end

	tbl = tostring( tbl )

	local cats = ''
	if not yesno(args.subgroup) and not yesno(args.hidecat) then
		cats = categories()
	end
	local docs = docs( args )
	
	local smw = smw(args)

	return tbl .. cats .. docs .. smw
end

--
-- Main navbox method accessed through #invoke
--
-- @param frame {table}
-- @return {string}
--
function p.navbox( frame )
	local args = frame:getParent().args
	return p._navbox( args )
end

return p
-- </nowiki>