Moduł:Navbox

Z VatoWiki
Wersja z dnia 22:07, 27 sie 2021 autorstwa pl>Paweł Ziemian (sprawdzam czy pionowy szablon nawigacyjny jest umieszczony na początku artykułu)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Przejdź do nawigacji Przejdź do wyszukiwania

Dokumentacja dla tego modułu może zostać utworzona pod nazwą Moduł:Navbox/opis

require ("Moduł:No globals")

local res = mw.loadData('Moduł:Navbox/res')

local function encodeClass(name)
	local t = mw.getContentLanguage():lcfirst(name)
	local s = mw.ustring.gsub(t, ".", { ['ł'] = 'l', ['Ł'] = 'L', ['_'] = ' '}) -- wstępne zmiany ł->l i _-> spacja
	local p = mw.ustring.gsub(s, "%p+", "-") -- znaczki nieliterowe na minusy
	local q = mw.ustring.gsub(p, "%s+", "_") -- odstępy na podkreślenia
	local r = mw.ustring.toNFD(q) -- wyodrębnij diakrytyki
	local e = string.gsub(r,"[\127-\255]+",'') -- usuń to czego nie ma w ASCII
	return e
end

local function getPrinter(name)
	local printer = res.printers[name]
	return printer and require(printer) or nil
end

local function printSubTree(container, tree, currentPrinter, printlog)

	local function printList(container, node, collapsibleContent)
		local mwCCC = collapsibleContent and 'mw-collapsible-content' or nil
		if #node <= 0 then
			if printlog then
				printlog.printedLeafs = (printlog.printedLeafs or 0) + 1
			end
			local leaf
			if (currentPrinter.leaf == 'table') and mwCCC then
				-- zwijanie tabelki i skórka timeless się gryzą
				leaf = container:tag('div')
					:addClass(mwCCC)
					:tag(currentPrinter.leaf)
			elseif currentPrinter.leaf then
				leaf = container:tag(currentPrinter.leaf)
					:addClass(mwCCC)
			else
				leaf = container
					:addClass(mwCCC)
			end
			if currentPrinter.update then currentPrinter.update(leaf, true, node, printlog) end
			leaf
				:addClass(node.odd())
				:addClass(node.even())
				:newline()
				:wikitext(node.get(res.arg.list.name))
				:newline()
		else
			local subprinter = getPrinter(node.peek(res.arg.list.name))
			if subprinter then
				node.use(res.arg.list.name)
			else
				subprinter = getPrinter(true)
			end
			local subtag = subprinter.root or subprinter.leaf or 'div'
			local subtree = ((subtag == 'table') and mwCCC)
				and container:tag('div'):addClass(mwCCC):tag(subtag) -- zwijanie tabelki i skórka timeless się gryzą
				or container:tag(subtag):addClass(mwCCC)
			if currentPrinter.update then currentPrinter.update(subtree, false, node, printlog) end
			printSubTree(subtree, node, subprinter, printlog)
		end
	end
	
	currentPrinter.print(container, tree, printList, printlog)
end

local function argsService(templateContext)
	local args = {}

	local function add(k, v, prefix)
		if v and #v > 0 then
			args[k] = v
		end
	end

	local peekName = function(name)
		if not args[name] and templateContext and templateContext.aliases then
			local alias = templateContext.aliases[name]
			if alias and args[alias] then
				return alias
			end
		end
		
		return name
	end

	local function peek(name)
		return args[peekName(name)]
	end
	
	local function get(name)
		return peek(peekName(name)) or (res.aux.missingArgNamePrefix..name..res.aux.missingArgNameSuffix)
	end
	
	local function dump()
		return mw.text.jsonEncode(args)
	end
	
	return {
		add = add,
		peek = peek,
		use = peek,
		get = get,
		dump = dump,
	}
end

local function loadArgsTree(args, printlog, templateContext)

	local dynamicArgs = {}
	for k, v in pairs(res.arg) do
		if templateContext and templateContext.aliases then
			local alias = templateContext.aliases[v.name]
			if v.dynamic and alias then dynamicArgs[alias] = v.dynamic end
		end
		if v.dynamic then dynamicArgs[v.name] = v.dynamic end
	end

	local splitArgName = function(name)
		if type(name) ~= "string" then
			--mw.logObject(name, "to nie jest tekst")
			return false, nil
		end

		local prefix, suffix = mw.ustring.match(name, "^(.-)([1-9][%.0-9]*)$")
		if not prefix or not dynamicArgs[prefix] then
			--mw.logObject(name, "wzór nie pasuje lub nierozpoznany")
			return false, nil
		end
		
		local keys = mw.text.split(suffix,'.',true)
		for i = 1, #keys do
			keys[i] = tonumber(keys[i])
			if not keys[i] or (keys[i] == 0) then
				--mw.logObject({name, keys, i, keys[i]}, "wzór ma błędy")
				return false, nil
			end
		end

		return prefix, keys
	end
	
	local updateIndex = function(node, k)
		node.index = node.index or {}
		for i, v in ipairs(node.index) do
			if v == k then
				return
			end
		end
		
		table.insert(node.index, k)
		table.sort(node.index)
	end
	
	local argsTree = not printlog
		and argsService(templateContext)
		or require('Moduł:Navbox/diag').argsService(templateContext)
	local argsTree_add = argsTree.add
	argsTree.add = nil
	local argsTree_tree = nil

	local tree = function()
		if not argsTree_tree or (#argsTree_tree <= 0) then
			return nil
		end
		
		local function createNode(v)
			
			local peekName = function(name)
				if not v.address then
					return nil
				end
				
				if argsTree.peek(name..v.address) then
					return name..v.address
				end
				
				if templateContext and templateContext.aliases then
					local alias = templateContext.aliases[name]
					if alias and peek(alias..v.address) then
						return alias..v.address
					end
				end
				
				return nil
			end

			local node = {
				address = function() return v.address and "a"..string.gsub(v.address, '%.', '_') or nil end,
				peek = function(name)
					return peekName(name)
						and argsTree.peek(peekName(name))
						or (templateContext and templateContext.defaults and templateContext.defaults[name])
				end,
				use = function(name) return peekName(name) and argsTree.use(peekName(name)) or nil end,
				get = function(name) return peekName(name) and argsTree.get(peekName(name)) or nil end,
				odd = function() return v.odd and res.class.odd or nil end,
				even = function() return v.even and res.class.even or nil end,
			}
			
			local function calculateTest()
				local pattern = mw.loadData("Moduł:Navbox/title").wikilinkPattern
				local s1 = argsTree.peek(res.arg.group.name..v.address)
				if s1 and mw.ustring.match(s1, pattern) then return true end
				local s2 = argsTree.peek(res.arg.list.name..v.address)
				if s2 and mw.ustring.match(s2, pattern) then return true end
				for i, n in ipairs(node) do
					if n.test(pattern) then return true end
				end
				return false
			end
			
			local testCache

			node.test = function()
				if testCache == nil then
					testCache = calculateTest()
				end
				
				return testCache
			end
			
			return node
		end
		
		local function buildTree(buffer, tree)
			for i, v in ipairs(tree.index) do
				local data = tree[v]
				local node = createNode(data)
				table.insert(buffer, node)
				if data.index then
					buildTree(node, data)
				end
			end
		end
		
		local rootNode = createNode({})
		buildTree(rootNode, argsTree_tree)
		return rootNode
	end

	local function analyzeArg(k, v, prefix, keys)
		argsTree_add(k, v, prefix)
		if prefix and v and (#v > 0) then
			argsTree_tree = argsTree_tree or {}
			local node = argsTree_tree
			for i = 1, #keys do
				local k = keys[i]
				local child = node[k]
				updateIndex(node, k)
				if not child then
					child = {
						address = node.address and (node.address..'.'..tostring(k)) or tostring(k),
					}
					node[k] = child
				end
				
				node = child
			end
		end
	end

	for k, v in pairs(args) do
		local prefix, keys = splitArgName(k)
		local json, subargs
		if (prefix == res.arg.list.name) and v and (#v > 0) then
			json, subargs = pcall(mw.text.jsonDecode, v)
		end
		if json then
			--analyzeArg(k, '* szablon *', prefix, keys)
			for vk, vv in pairs(subargs) do
				local vprefix, vkeys = splitArgName(vk)
				if not vprefix and (vk == res.arg.list.name) then
					vprefix = vk
					vkeys = {}
				end
				if vprefix and prefix then
					local newKeys = {}
					for j, u in ipairs(keys) do table.insert(newKeys, u) end
					for j, u in ipairs(vkeys) do table.insert(newKeys, u) end
					local n = {}
					for j, u in ipairs(newKeys) do table.insert(n, tostring(u)) end
					local name = vprefix..table.concat(n, '.')
					analyzeArg(name, vv, vprefix, newKeys)
				end
			end
		else
			analyzeArg(k, v, prefix, keys)
		end
	end
	
	if templateContext and getPrinter(templateContext.list) then
		analyzeArg(res.arg.list.name, templateContext.list, nil, nil)
	end
	
	if argsTree_tree then
		local tree = argsTree_tree

		local function loadLeafNodes(tree, buffer, level)
			if not tree.index then
				table.insert(buffer, tree)
			else
				local n = level
				for i, v in ipairs(tree.index) do
					local k = loadLeafNodes(tree[v], buffer, level + 1)
					if k > n then
						n = k
					end
				end
				
				level = n
			end
			
			return level
		end

		local buffer = {}
		local levels = loadLeafNodes(tree, buffer, 0)
		for i, v in ipairs(buffer) do
			v.odd = (i % 2) == 1
			v.even = (i % 2) == 0
		end

		if printlog then
			printlog.levels = levels
			printlog.leafs = #buffer
		end
	end
	
	argsTree.tree = tree

	--mw.logObject(printlog, 'printlog')
	--mw.logObject(argsTree, 'argsTree')
	return argsTree
end

local function drawMiniNavBar(container, title, tags)
	local pde = container:tag('ul')
		:addClass('tnavbar')
		:addClass('noprint')
		:addClass('plainlinks')
		:addClass('hlist')
	-- p
	local p = pde:tag('li'):wikitext("[[", title.nsText, ':', title.text, '|')
	p:tag('span'):attr('title', res.navbar.p):wikitext(tags.p)
	p:wikitext(']]')
	-- d
	if not title.isTalkPage then
		local tt = title.talkPageTitle
		local d = pde:tag('li'):wikitext("[[", tt.nsText, ':', tt.text, '|')
		d:tag('span'):attr('title', res.navbar.d):wikitext(tags.d)
		d:wikitext(']]')
	end
	-- e
	pde:tag('li')
		:attr('title', res.navbar.e)
		:wikitext('[', title:fullUrl("action=edit"), ' ', tags.e, ']')
	-- U
	if mw.title.equals(title, mw.title.getCurrentTitle()) and (title.namespace == 10) then
		local fullpagenamee = mw.uri.encode(title.fullText, "WIKI")
		pde:tag('li')
			:attr('title', res.navbar.U)
			:wikitext('[https://tools.wmflabs.org/templatetransclusioncheck/index.php?lang=pl&name=', fullpagenamee, ' ', tags.U, ']')
	end
end

local function splitCustomClasses(customClasses, navboxName)
	if not customClasses then
		return {}
	end
	
	local result = {}
	local cc = mw.text.split(customClasses,"%s+")
	for i, v in ipairs(cc) do
		result[v] = res.validExtraClasses[v] ~= nil
	end
	
	-- pionowy szablon tylko na początku artykułu
	if result.pionowy and navboxName then
		local title = mw.title.getCurrentTitle()
		if title and title.namespace == 0 then
			local content = title:getContent()
			if content then
				local navboxStart = mw.ustring.find(content, "{{%s*"..mw.ustring.gsub( navboxName, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" ).."%s*}}")
				if navboxStart then
					local firstSectionStart = mw.ustring.find(content, "\n==.-==[ \t]*\n")
					if firstSectionStart and (firstSectionStart < navboxStart) then
						mw.logObject(result, "splitCustomClasses KASUJĘ pionowy bo nie jest na początku artykułu")
						result.pionowy = false
					end
				end
			end
		end
	end
	
	return result
end

local function makeContainer(args, title, contentTag, printlog)
	local templateClassName = (title and (title.namespace > 0) and not title.isTalkPage)
		and encodeClass(title.namespace == 10 and title.text or title.fullText)
		or nil
	local builder = mw.html.create('div')
		:addClass(res.class.navbox)
		:addClass('do-not-make-smaller')
	local classes = splitCustomClasses(args.use(res.arg.class.name), args.peek(res.arg.name.name))
	for c, valid in pairs(classes) do
		builder:addClass((valid and res.validExtraClasses[c]) and c or nil)
	end
	if title and mw.title.equals(title, mw.title.getCurrentTitle()) or (classes[templateClassName] ~= nil) then
		builder:addClass(templateClassName and res.class.name..templateClassName or nil)
	end
	builder:wikitext(mw.getCurrentFrame():extensionTag( 'templatestyles', '', {src = "Szablon:Navbox/styles.css"}))
	local privateCSS = false
	if classes[templateClassName] ~= nil then
		privateCSS = tostring(title.fullText.."/styles.css")
		builder:wikitext(mw.getCurrentFrame():extensionTag( 'templatestyles', '', {src = privateCSS}))
	end
	if not classes.pionowy then
		builder:addClass('mw-collapsible'):attr('data-expandtext', res.aux.expandText):attr('data-collapsetext', res.aux.collapseText)
		
		local collapse = function()
			if args.peek(res.arg.collapsible.name) == res.arg.collapsible.collapsed then
				return 'mw-collapsed'
			end
			
			if mw.title.getCurrentTitle().namespace ~= 10 then
				return 'autocollapse'
			end
			
			return nil
		end
		
		if title then
			builder:addClass(not mw.title.equals(title, mw.title.getCurrentTitle()) and collapse() or nil)
			drawMiniNavBar(builder, title, res.navbar.mini)
		else
			builder
				:addClass(mw.title.getCurrentTitle().namespace ~= 10 and collapse() or nil)
				:tag('span'):addClass(res.navbar.fake)
		end
	end
	builder:tag('div')
		:addClass(res.class.caption)
		:wikitext(args.get(res.arg.title.name))
	local content
	if not args.peek(res.arg.above.name) and not args.peek(res.arg.below.name) and not args.peek(res.arg.before.name) and not args.peek(res.arg.after.name) and not classes.pionowy then
		if contentTag == 'table' then -- zwijanie tabeli gryzie się ze skórką timeless
			content = builder:tag('div')
				:addClass('mw-collapsible-content')
				:tag(contentTag)
		else
			content = builder:tag(contentTag)
				:addClass('mw-collapsible-content')
		end
	elseif not args.peek(res.arg.above.name) and not args.peek(res.arg.below.name) and not classes.pionowy then
		local flex = builder:tag('div')
			:addClass('mw-collapsible-content')
			:addClass(res.class.flex)
		if args.peek(res.arg.before.name) then flex:tag('div'):addClass(res.class.before):newline():wikitext(args.get(res.arg.before.name)):newline() end
		content = flex:tag(contentTag)
		if args.peek(res.arg.after.name) then flex:tag('div'):addClass(res.class.after):newline():wikitext(args.get(res.arg.after.name)):newline() end
	else
		local content1 = builder:tag('div')
			:addClass('mw-collapsible-content')
		if args.peek(res.arg.above.name) then content1:tag('div'):addClass(res.class.hlist):addClass(res.class.above):newline():wikitext(args.get(res.arg.above.name)):newline() end
		local flex = (not classes.pionowy and ((args.peek(res.arg.before.name) or args.peek(res.arg.after.name)))) and content1:tag('div'):addClass(res.class.flex) or content1
		if args.peek(res.arg.before.name) then flex:tag('div'):addClass(res.class.before):newline():wikitext(args.get(res.arg.before.name)):newline() end
		content = flex:tag(contentTag)
		if args.peek(res.arg.after.name) then flex:tag('div'):addClass(res.class.after):newline():wikitext(args.get(res.arg.after.name)):newline() end
		if (title and classes.pionowy) or args.peek(res.arg.below.name) then
			local below = content1:tag('div'):addClass(res.class.hlist):addClass(res.class.below)
			if args.peek(res.arg.below.name) then
				below:newline():wikitext(args.get(res.arg.below.name)):newline()
			end
			if title and classes.pionowy then
				if args.peek(res.arg.below.name) then
					below:wikitext("----"):newline()
				end
				drawMiniNavBar(below, title, res.navbar.short)
			end
		end
	end

	content:addClass(res.class.main)
	for c, valid in pairs(classes) do
		content:addClass((valid and not res.validExtraClasses[c]) and c or nil)
	end
	
	if printlog then
		printlog.pionowy = classes.pionowy
		printlog.templateClassName = templateClassName
		printlog.privateCSS = privateCSS
		for c, valid in pairs(classes) do
			if not valid and (c ~= templateClassName) then
				local u = printlog.unknownClasses or {}
				table.insert(u, c)
				printlog.unknownClasses = u
			end
		end
	end
	
	return builder, content
end

local function createNavbox(args, title, printlog)
	local rootPrinter = getPrinter(args.peek(res.arg.list.name))
	if rootPrinter then
		args.use(res.arg.list.name)
	end
	local rootTree = args.tree()
	local rootTag = 'div'
	if rootTree then
		rootPrinter = rootPrinter or getPrinter(true)
		rootTag = rootPrinter and (rootPrinter.root or rootPrinter.leaf) or 'div'
	end
	local builder, content = makeContainer(args, title, rootTag, printlog)
	if rootTree then
		printSubTree(content, rootTree, rootPrinter or getPrinter(true), printlog)
	elseif not rootPrinter and args.peek(res.arg.list.name) then
		if printlog then printlog.notree = true end
		content
			:newline()
			:wikitext(args.get(res.arg.list.name))
			:newline()
	else
		if printlog then printlog.example = true end
		local exampleTree = {{
			address = function() return res.class.address.."1" end,
			peek = function(name) return nil end,
			use = function(name) return nil end,
			get = function(name) return args.get(name.."1") end,
			odd = function(name) return nil end,
			even = function(name) return nil end,
		}}
		printSubTree(content, exampleTree, rootPrinter or getPrinter(true), printlog)
	end

	return not printlog and tostring(builder) or require('Moduł:Navbox/diag').diagnosticView(builder, args, printlog)
end

return {
	
["Navbox"] = function(frame)
	local title = mw.title.new(frame:getParent():getTitle())
	local printlog = mw.title.equals(title,mw.title.getCurrentTitle()) and {} or nil
	local args = loadArgsTree(frame.args, printlog)
	return createNavbox(args, title, printlog)
end,

["Template"] = function(frame)
	mw.log('\n== '..frame:getParent():getTitle()..' ==\n')
	-- specjalne traktowanie nazwy
	local name = frame:getParent().args[res.arg.name.name]
	local expectedTitle = (name and (#name > 0)) and mw.title.new(name, "Template") or nil
	local printlog = false
	if expectedTitle then
		local currentTitle = mw.title.getCurrentTitle()
		if (currentTitle.namespace == expectedTitle.namespace)
		and (currentTitle.namespace ~= 0) -- bez artykułów
		and (currentTitle.namespace ~= 2) -- bez brudnopisów
		and not currentTitle.isTalkPage
		and (currentTitle.subpageText ~= res.aux.docSubpageText) -- bez opisów
		and (not expectedTitle.exists or not require('Moduł:Navbox/diag').verifyTemplateName(currentTitle, expectedTitle, frame:getParent():getTitle())) then
			printlog = {}
			printlog.badName = true
		elseif mw.title.equals(currentTitle, expectedTitle) then
			printlog = {}
		end
	elseif name and (#name > 0) then
		-- tutaj nie ma żadnych możliwości sprawdzenia czy to jest szablon
		-- czy jego transkluzja
		local currentTitle = mw.title.getCurrentTitle()
		if (currentTitle.namespace ~= 0) and not currentTitle.isTalkSpace then
			printlog = {}
			printlog.badName = true
		end
	end
	
	local status, templateContext = pcall(mw.text.jsonDecode, frame.args.context or '[]')
	if not status then
--		mw.logObject(frame.args.context, templateContext)
		templateContext = {}
	end
--	mw.logObject(templateContext, 'templateContext')
	
	local args = loadArgsTree(frame:getParent().args, printlog, templateContext)
	if args.peek(res.arg.name.name) then args.use(res.arg.name.name) end
	--mw.logObject(mw.text.jsonDecode(args.dump()), 'args.dump()')
	if printlog and templateContext.list then
		printlog.useTemplateCategories = true
	end
	
	local navbox = args.peek(res.arg.name.name)
		or args.peek(res.arg.title.name)
		or args.peek(res.arg.class.name)
		or args.peek(res.arg.above.name)
		or args.peek(res.arg.below.name)
		or args.peek(res.arg.before.name)
		or args.peek(res.arg.after.name)
	if navbox then
--		mw.log("----> createNavbox")
		return createNavbox(args, expectedTitle, printlog)
	end
	
--		mw.log("----> dump")
	return args.dump()
end,

}