Moduuli:Eras

Jedipediasta, vapaasta Tähtien sota-tietosanakirjasta tänään, 30. heinäkuuta 2025
Siirry navigaatioonSiirry hakuun

Tämän moduulin ohjeistuksen voi tehdä sivulle Moduuli:Eras/ohje

  1 -- <nowiki>
  2 
  3 -------------------------------------------------------------------------------
  4 --                              Module:Eras
  5 --
  6 -- This module renders the icons in the top-right corner of articles, as well
  7 -- as the Canon and Legends tabs for pages with a Canon/Legends counterpart.
  8 -- It also formats the page title with {{DISPLAYTITLE}}. It is a rewrite of
  9 -- [[Template:Eras]].
 10 -------------------------------------------------------------------------------
 11 
 12 local DEBUG_MODE = false -- if true, errors are not caught
 13 
 14 -------------------------------------------------------------------------------
 15 -- Icon data
 16 -------------------------------------------------------------------------------
 17 
 18 --[[
 19 -- This table stores data for all the icons displayed in the top-right. It can
 20 -- have the following fields:
 21 -- * image - the icon image name, minus any "File:" prefix (required).
 22 -- * tooltip - the icon tooltip (optional).
 23 -- * link - the page to link from the icon (optional).
 24 -- * category - a category to go with the icon, minus any "Category:" prefix
 25 --     (optional).
 26 -- * protectionAction - for protection icons, an action such as "edit" or "move"
 27 --     to check (optional).
 28 -- * protectionLevel - for protection icons, the protection level to check,
 29 --     such as "sysop". If the page doesn't have the right protection level
 30 --     it is put in a tracking category and the icon is not displayed
 31 --     (optional).
 32 -- Note: this is just a convenient place to store the data. The subtables are
 33 -- accessed from the code manually, so adding new subtables won't automatically
 34 -- add new icons, and removing subtables may break things.
 35 --]]
 36 
 37 local iconData = {
 38 	can = {
 39 		image = 'Eras-canon-edit.png',
 40 		tooltip = 'Tämän artikkelin aihe kuuluu kaanoniin.',
 41 		link = 'Kaanon'
 42 	},
 43 	leg = {
 44 		image = 'LegendsBanner.png',
 45 		tooltip = 'Tämän artikkelin aihe kuuluu Legendsiin.',
 46 		link = 'Star Wars Legends'
 47 	},
 48 	dotj = {
 49 		image = "Era-dotj.png",
 50 		tooltip = "Tämän artikkelin aihe esiintyi jedien nousun aikakaudella."
 51 	},
 52 	tor = {
 53 		image = "Era-tor.png",
 54 		tooltip = "Tämän artikkelin aihe esiintyi Vanhan tasavallan aikakaudella."
 55 	},
 56 	thr = {
 57 		image = "Era-thr.png",
 58 		tooltip = "Tämän artikkelin aihe esiintyi Uljaan tasavallan aikakaudella."
 59 	},
 60 	fotj = {
 61 		image = "Era-fotj.png",
 62 		tooltip = "Tämän artikkelin aihe esiintyi jedien tuhon aikakaudella."
 63 	},
 64 	rote = {
 65 		image = "Era-reb.png",
 66 		tooltip = "Tämän artikkelin aihe esiintyi Imperiumin aikakaudella."
 67 	},
 68 	aor = {
 69 		image = "Era-aor.png",
 70 		tooltip = "Tämän artikkelin aihe esiintyi kapinan aikakaudella."
 71 	},
 72 	tnr = {
 73 		image = "Era-tnr.png",
 74 		tooltip = "Tämän artikkelin aihe esiintyi Uuden tasavallan aikakaudella."
 75 	},
 76 	rofo = {
 77 		image = "Era-dotj.png",
 78 		tooltip = "Tämän artikkelin aihe esiintyi Ensimmäisen ritarikunnan aikakaudella."
 79 	},
 80 	cnjo = {
 81 		image = "Era-cnjo.png",
 82 		tooltip = "Tämän artikkelin aihe esiintyi uuden jediritarikunnan aikakaudella."
 83 	},
 84 	pre = {
 85 		image = "Era-btr.png",
 86 		tooltip = "Tämän artikkelin aihe esiintyi Tasavaltaa edeltävällä aikakaudella.",
 87 		link = "Tasavaltaa edeltävä aikakausi"
 88 	},
 89 	btr = {
 90 		image = "Era-btr.png",
 91 		tooltip = "Tämän artikkelin aihe esiintyi Tasavaltaa edeltävällä aikakaudella.",
 92 		link = "Tasavaltaa edeltävä aikakausi"
 93 	},
 94 	old = {
 95 		image = "Era-old.png",
 96 		tooltip = "Tämän artikkelin aihe esiintyi Vanhan Tasavallan aikakaudella.",
 97 		link = "Vanhan Tasavallan aikakausi"
 98 	},
 99 	imp = {
100 		image = "Era-imp.png",
101 		tooltip = "Tämän artikkelin aihe esiintyi Imperiumin nousun aikakaudella.",
102 		link = "Imperiumin nousun aikakausi"
103 	},
104 	reb = {
105 		image = "Era-reb.png",
106 		tooltip = "Tämän artikkelin aihe esiintyi kapinallisten aikakaudella.",
107 		link = "Kapinallisten aikakausi"
108 	},
109 	new = {
110 		image = "Era-new.png",
111 		tooltip = "Tämän artikkelin aihe esiintyi Uuden tasavallan aikakaudella.",
112 		link = "Uuden tasavallan aikakausi (tosimaailma)"
113 	},
114 	njo = {
115 		image = "Era-njo.png",
116 		tooltip = "Tämän artikkelin aihe esiintyi uuden jediritarikunnan aikakaudella.",
117 		link = "Uuden jediritarikunnan aikakausi"
118 	},
119 	lgc = {
120 		image = "Era-leg.png",
121 		tooltip = "Tämän artikkelin aihe esiintyi perinnön aikakaudella.",
122 		link = "Perinnön aikakausi"
123 	},
124 	inf = {
125 		image = "30px-Era-inf.png",
126 		tooltip = "Tämän artikkelin aihe on osa Star Wars Infinitiesiä.",
127 		link = "Infinities"
128 	},
129 	real = {
130 		image = "Era-real.png",
131 		tooltip = "Tämä artikkeli kertoo todellisesta asiasta tai henkilöstä.",
132 		link = "Luokka:Tosimaailma"
133 	},
134 	ss = {
135 		image = "FeaturedIcon.png",
136 		tooltip = "Tämä on suositeltu sivu.",
137 		link = "Jedipedia:Suositeltu sivu",
138 		category = "Suositellut sivut"
139 	},
140 	ess = {
141 		image = "EntinenSuositeltu.png",
142 		tooltip = "Tämä on entinen suositeltu sivu.",
143 		link = "Jedipedia:Suositeltu sivu"
144 	},
145 	ha = {
146 		image = "GoodIcon.png",
147 		tooltip = "Tämä on hyvä artikkeli.",
148 		link = "Jedipedia:Hyvä artikkeli",
149 		category = "Hyvät artikkelit"
150 	},
151 	eha = {
152 		image = "30px-FormerGAicon.png",
153 		tooltip = "Tämä on entinen hyvä artikkeli.",
154 		link = "Jedipedia:Hyvä artikkeli"
155 	},
156 	vkp = {
157 		image = "Queicon.png",
158 		tooltip = "Tällä artikkelin nimellä ei ole virallista käännöstä.",
159 		link = "Jedipedia:Virallisen käännöksen puuttuminen",
160 		category = "Virallisten käännösten puutteessa olevat artikkelit"
161 	},
162 	fprot = {
163 		protectionAction = "edit",
164 		protectionLevel = "sysop",
165 		image = "Padlockicon3.png",
166 		tooltip = "Tämä sivu on suojattu muokkauksilta.",
167 		link = "Jedipedia:Suojauskäytäntö"
168 	},
169 	sprot = {
170 		protectionAction = "edit",
171 		protectionLevel = "autoconfirmed",
172 		image = "Padlockicon2.png",
173 		tooltip = "Tämä sivu on osittain suojattu muokkauksilta.",
174 		link = "Jedipedia:Suojauskäytäntö"
175 	},
176 	mprot = {
177 		protectionAction = "move",
178 		protectionLevel = "sysop",
179 		image = "Padlockicon4.png",
180 		tooltip = "Tämä sivu on suojattu siirroilta.",
181 		link = "Jedipedia:Suojauskäytäntö"
182 	},
183 	uprot = {
184 		protectionAction = "upload",
185 		protectionLevel = "sysop",
186 		image = "Padlockicon6.png",
187 		tooltip = "Tämä tiedosto on suojattu tallennuksilta.",
188 		link = "Jedipedia:Suojauskäytäntö"
189 	}
190 }
191 
192 -------------------------------------------------------------------------------
193 -- Helper functions
194 -------------------------------------------------------------------------------
195 
196 -- Find whether the specified page exists. We use pcall to catch errors if we
197 -- are over the expensive parser function count limit, or a number of other
198 -- juicy errors. This function increases the expensive parser function count
199 -- for every new page called.
200 local function exists(page)
201 	local success, title = pcall(mw.title.new, page)
202 	return success and title and title.exists or false
203 end
204 
205 local function isRedirect(page)
206 	local success, title = pcall(mw.title.new, page)
207 	return success and title and title.isRedirect or false
208 end
209 
210 -------------------------------------------------------------------------------
211 -- Eras class
212 -------------------------------------------------------------------------------
213 
214 -- The eras class does all of the heavy lifting in the module. We use a class
215 -- rather than normal functions so that we can avoid passing lots of different
216 -- values around for each different function.
217 
218 local Eras = {}
219 Eras.__index = Eras -- Set up inheritance for tables that use Eras as a metatable.
220 
221 -- This function makes a new eras object. Here we set all the values from the
222 -- arguments and do any preprocessing that we need.
223 function Eras.new(args, title)
224 	local obj = setmetatable({}, Eras) -- Make our object inherit from Eras.
225 	obj.title = title or mw.title.getCurrentTitle()
226 
227 	-- Set object structure
228 	obj.categories = {}
229 
230 	-- Näytettävän otsikon parametrit
231 	obj.noDisplayTitle = args.notitle
232 	if args.title or args.title2 then
233 		obj.displayTitleBase = args.title
234 		obj.displayTitleParen = args.title2
235 	else
236 		obj.displayTitleBase = args.otsikko
237 		obj.displayTitleParen = args.otsikko2
238 	end
239 
240 	-- Set hidden status
241 	obj.isHidden = args.hide
242 
243 	-- Set notoc value
244 	obj.hideToc = args.notoc
245 
246 	-- Jatkumon määrittäminen (parametri "tyyppi")
247 	if args.tyyppi then
248 		local override = args.tyyppi:lower()
249 		if override ~= 'kaanon' and override ~= 'legends' then
250 			obj:raiseError("jos parametri 'tyyppi' on määritetty, " ..
251 				"sen on oltava 'kaanon' tai 'legends'")
252 		end
253 		obj.continuityOverride = override
254 	end
255 
256 	-- Kaanon- ja Legends-nimet artikkelille
257 	obj.legendsArticle = args.legends
258 	obj.canonArticle = args.kaanon
259 
260 	-- Kuvakkeiden tiedot.
261 	do
262 		local icons = {}
263 		for _, v in ipairs(args) do
264 			local t = iconData[string.lower(v)]
265 			if t then
266 				icons[string.lower(v)] = t
267 			else
268 				-- Annettua kuvaketta ei ole tiedoissa, joten
269 				-- lisätään virheluokka.
270 				obj.hasBadParameter = true
271 			end
272 		end
273 		obj.icons = icons
274 	end
275 
276 	return obj
277 end
278 
279 -- Ilmoittaa virheestä. Jos DEBUG_MODE on false, tässä olevat virheet
280 -- huomataan funktiossa p._main.
281 function Eras:raiseError(msg)
282 	local level
283 	if DEBUG_MODE then
284 		level = nil
285 	else
286 		level = 0 -- Suppress module name and line number in the error message.
287 	end
288 	error(msg, level)
289 end
290 
291 -- Add a category, to be rendered at the very end of the template output.
292 function Eras:addCategory(cat, sort)
293 	table.insert(self.categories, {category = cat, sortKey = sort})
294 end
295 
296 -- Shortcut method for getting an icon data subtable.
297 function Eras:getIconData(code)
298 	return self.icons[code]
299 end
300 
301 -- Päättyykö nykyinen otsikko /Kaanon.
302 function Eras:hasCanonTitle()
303 	return self.title.text:find('/Kaanon$')
304 end
305 
306 -- Päättyykö nykyinen otsikko /Legends.
307 function Eras:hasLegendsTitle()
308 	return self.title.text:find('/Legends$')
309 end
310 
311 -- Returns a boolean showing whether any of the icons were specified by the
312 -- user.
313 function Eras:hasAnyOfIcons(...)
314 	for i = 1, select('#', ...) do
315 		if self:getIconData(select(i, ...)) then
316 			return true
317 		end
318 	end
319 	return false
320 end	
321 
322 -- Analysoi sivun nimen ja asettaa {{DISPLAYTITLE}}:n.
323 function Eras:renderDisplayTitle()
324 	local pagename = self.title.text
325 
326 	-- Exit if we have been told not to set a title or if the title begins with
327 	-- an opening parenthesis.
328 	if self.noDisplayTitle or pagename:find('^%(') then
329 		return nil
330 	end
331 
332 	-- Find the display base and the display parentheses.
333 	local dBase = self.displayTitleBase
334 	local dParen = self.displayTitleParen
335 	if not dBase or not dParen then
336 		-- Analyse the pagename to find base part and any ending parentheses.
337 		-- /Canon is removed, and parentheses are only recognised if they are
338 		-- at the end of the pagename.
339 		local trimmedPagename = pagename:gsub('/Kaanon$', '')
340 		trimmedPagename = trimmedPagename:gsub('/Legends$', '')
341 		local base, paren = trimmedPagename:match('^(.*)%s*%((.-)%)$')
342 		if not base then
343 			base = trimmedPagename
344 		end
345 		-- Use the values we found, but only if a value has not already been
346 		-- specified.
347 		dBase = dBase or base
348 		dParen = dParen or paren
349 	end
350 
351 	-- Build the display string
352 	local display
353 	if dParen then
354 		display = string.format('%s <small>(%s)</small>', dBase, dParen)
355 	else
356 		display = dBase
357 	end
358 	if self.title.namespace ~= 0 then
359 		display = mw.site.namespaces[self.title.namespace].name .. ':' .. display
360 	end
361 
362 	-- Return the expanded DISPLAYTITLE parser function.
363 	return mw.getCurrentFrame():callParserFunction('DISPLAYTITLE', display)
364 end
365 
366 -- Renders an eras icon from the given icon data. It deals with the image,
367 -- tooltip, link, and the category, but not the protection fields.
368 function Eras:renderIcon(data)
369 	-- Render the category at the end if it exists.
370 	if data.category then
371 		self:addCategory(data.category)
372 	end
373 	-- Render the icon and return it.
374 	local ret = {}
375 	ret[#ret + 1] = '[[Tiedosto:'
376 	ret[#ret + 1] = data.image
377 	if data.tooltip then
378 		ret[#ret + 1] = '|'
379 		ret[#ret + 1] = data.tooltip
380 	end
381 	if data.link then
382 		ret[#ret + 1] = '|link='
383 		ret[#ret + 1] = data.link
384 	end
385 	ret[#ret + 1] = '|x30px]]'
386 	return table.concat(ret)
387 end
388 
389 -- Renders a protection eras icon from the given data. If the page doesn't have
390 -- the specified protection level, returns nil and adds a flag to add a
391 -- tracking category later on in processing.
392 function Eras:renderProtectionIcon(data)
393 	if not data.protectionAction then
394 		return self:renderIcon(data)
395 	end
396 	local protectionLevel = self.title.protectionLevels[data.protectionAction]
397 	protectionLevel = protectionLevel and protectionLevel[1]
398 	if protectionLevel and protectionLevel == data.protectionLevel then
399 		return self:renderIcon(data)
400 	else
401 		self.hasIncorrectProtectionIcon = true
402 	end
403 end
404 
405 -- Renders the first icon, either Canon or Legends, or nil if the continuity
406 -- couldn't be determined. This is equivalent to the previous {{Eraicon/canon}}
407 -- template.
408 function Eras:renderContinuityIcon()
409 	-- First, find what continuity to use, if any.
410 	local continuity, isUsingCategory
411 	if self.continuityOverride == 'kaanon' or self:hasAnyOfIcons('can') then
412 		continuity = 'canon'
413 		if not self:hasAnyOfIcons('real') then
414 			isUsingCategory = true
415 		end
416 	elseif self.continuityOverride == 'legends' or self:hasAnyOfIcons('leg') then
417 		continuity = 'legends'
418 		if not self:hasAnyOfIcons('real') then
419 			isUsingCategory = true
420 		end
421 	elseif self.title.namespace == 0 then
422 		if self:hasLegendsTitle() then
423 			continuity = 'legends'
424 			isUsingCategory = true
425 		elseif self:hasCanonTitle() then
426 			continuity = 'canon'
427 			isUsingCategory = true
428 		elseif self.legendsArticle then
429 			continuity = 'canon'
430 			isUsingCategory = true
431 		elseif self.canonArticle then
432 			continuity = 'legends'
433 			isUsingCategory = true
434 		elseif exists(self.title.text .. '/Legends') then
435 			continuity = 'canon'
436 			isUsingCategory = true
437 		elseif exists(self.title.text .. '/Kaanon') and not isRedirect(self.title.text .. '/Kaanon') then
438 			continuity = 'legends'
439 			isUsingCategory = true
440 		elseif self:hasAnyOfIcons('dotj', 'tor', 'thr', 'fotj', 'rote', 'aor',
441 			'tnr', 'rofo', 'cnjo')
442 		then
443 			continuity = 'canon'
444 			if self:hasAnyOfIcons('real') then
445 				isUsingCategory = false
446 			else
447 				isUsingCategory = true
448 			end
449 		elseif self:hasAnyOfIcons('pre', 'btr', 'old', 'imp', 'reb', 'new',
450 			'njo', 'lgc')
451 		then
452 			continuity = 'legends'
453 			if self:hasAnyOfIcons('real') then
454 				isUsingCategory = false
455 			else
456 				isUsingCategory = true
457 			end
458 		elseif self:hasAnyOfIcons('inf') then
459 			continuity = 'legends'
460 			isUsingCategory = false
461 		elseif self:hasAnyOfIcons('real') then
462 			isUsingCategory = false
463 		else
464 			continuity = 'canon'
465 			isUsingCategory = true
466 		end
467 	end
468 
469 	-- Generate the icon data and make the icon.
470 	if continuity == 'canon' then
471 		local data = iconData['can']
472 		if isUsingCategory then
473 			data.category = 'Kaanonartikkelit'
474 		end
475 		return self:renderIcon(data)
476 	elseif continuity == 'legends' then
477 		local data = iconData['leg']
478 		if isUsingCategory then
479 			data.category = 'Legends-artikkelit'
480 		end
481 		return self:renderIcon(data)
482 	end
483 end
484 
485 -- Renders the icons that respond to a publishing era.
486 function Eras:renderPublishingIcons()
487 	local ret = {}
488 	local codes = {'dotj', 'tor', 'thr', 'fotj', 'rote', 'aor',	'tnr', 'rofo', 'cnjo', 
489 		'pre', 'btr', 'old', 'imp', 'reb', 'new', 'njo', 'lgc', 'inf'}
490 	for _, code in ipairs(codes) do
491 		local data = self:getIconData(code)
492 		if data then
493 			ret[#ret + 1] = self:renderIcon(data)
494 		end
495 	end
496 	return table.concat(ret)
497 end
498 
499 -- Renders other icons, e.g. featured article status and protection status.
500 function Eras:renderNonPublishingIcons()
501 	local ret = {}
502 	local codes = {'real', 'ss', 'ess', 'ha', 'eha', 'vkp'}
503 	for _, code in ipairs(codes) do
504 		local data = self:getIconData(code)
505 		if data then
506 			ret[#ret + 1] = self:renderIcon(data)
507 		end
508 	end
509 	local protectionCodes = {'fprot', 'sprot', 'mprot', 'uprot'}
510 	for _, code in ipairs(protectionCodes) do
511 		local data = self:getIconData(code)
512 		if data then
513 			ret[#ret + 1] = self:renderProtectionIcon(data)
514 		end
515 	end
516 	return table.concat(ret)
517 end
518 
519 -- Render all the icons and eclose them in a surrounding div tag.
520 function Eras:renderIcons()
521 	local icons = {}
522 	icons[#icons + 1] = self:renderContinuityIcon()
523 	icons[#icons + 1] = self:renderPublishingIcons()
524 	icons[#icons + 1] = self:renderNonPublishingIcons()
525 	icons = table.concat(icons)
526 
527 	local root = mw.getCurrentFrame():extensionTag{ name = "indicator", content = icons, args = { name = "eras"} }
528 
529 	return tostring(root)
530 end
531 
532 -- Renders the Canon and Legends tabs for articles that are in both
533 -- continuities.
534 function Eras:renderCanonTab()
535 	-- Tarkkailuluokat sivujen siirtoa varten
536 	if self:hasCanonTitle() then
537 		self:addCategory('Siirrettävät kaanonartikkelit')
538 	elseif exists(self.title.text .. '/Kaanon') and not isRedirect(self.title.text .. '/Kaanon') then
539 		self:addCategory('Siirrettävät Legends-artikkelit')
540 	end
541 
542 	if self.isHidden or self.title.namespace ~= 0 then
543 		-- Exit if we have been explicitly hidden or if we are not in the main
544 		-- namespace.
545 		return nil
546 	end
547 
548 	-- Find the page type, canon title, and legends title.
549 	local pageType, canonTitle, legendsTitle
550 	if self.legendsArticle then
551 		pageType = 'canon'
552 		canonTitle = self.title.text
553 		legendsTitle = self.legendsArticle
554 	elseif self.canonArticle then
555 		pageType = 'legends'
556 		canonTitle = self.canonArticle
557 		legendsTitle = self.title.text
558 	elseif self:hasCanonTitle() then
559 		pageType = 'canon'
560 		canonTitle = self.title.text
561 		legendsTitle = canonTitle:match('^(.*)/Kaanon$') or canonTitle
562 	elseif self:hasLegendsTitle() then
563 		pageType = 'legends'
564 		legendsTitle = self.title.text
565 		canonTitle = legendsTitle:match('^(.*)/Legends$') or legendsTitle
566 	elseif exists(self.title.text .. '/Legends') then
567 		pageType = 'canon'
568 		legendsTitle = self.title.text .. '/Legends'
569 		canonTitle = self.title.text
570 	elseif exists(self.title.text .. '/Kaanon') and not isRedirect(self.title.text .. '/Kaanon') then
571 		pageType = 'legends'
572 		canonTitle = self.title.text .. '/Kaanon'
573 		legendsTitle = self.title.text
574 	else
575 		-- Could not determine that the article has both a Canon and a Legends
576 		-- version, so exit.
577 		return nil
578 	end
579 
580 	-- Add categories.
581 	if pageType == 'canon' then
582 		if self:hasCanonTitle() and exists(legendsTitle) == false then
583 			self:addCategory('Kaanonartikkelit, joiden Legends-vastine puuttuu')
584 			return nil
585 		else
586 			self:addCategory('Kaanonartikkelit, joista on vastine Legendsissä')
587 		end
588 	elseif pageType == 'legends' then
589 		if self:hasLegendsTitle() and exists(canonTitle) == false then
590 			self:addCategory('Legends-artikkelit, joiden kaanonvastine puuttuu')
591 			return nil
592 		else
593 			self:addCategory('Legends-artikkelit, joista on vastine kaanonissa')
594 		end
595 	else
596 		self:addCategory('Outliers')
597 	end
598 
599 	-- Make the table root.
600 	local root = mw.html.create('table')
601 	root
602 		:attr('id', 'canontab')
603 		:css('text-align', 'center')
604 		:css('padding', '0')
605 		:css('margin', '0 0 5px 0')
606 		:css('border-left', '0')
607 		:css('border-right', '0')
608 		:css('border-top', '0')
609 		:css('border-bottom', '7px solid #002e54')
610 		:css('border-spacing', '0')
611 		:css('border-collapse', 'collapse')
612 		:css('width', '100%')
613 		:css('vertical-align', 'top')
614 
615 	local row = root:tag('tr')
616 
617 	-- This makes one Canon/Legends cell. Having this as a function rather than
618 	-- doing it with chaining allows us to avoid putting the same code in twice.
619 	local function makeCell(id, color, image, link, tooltip)
620 		local cell = mw.html.create('td')
621 		cell
622 			:attr('id', id)
623 			:css('padding', '5px 0 5px 0')
624 			:css('background-color', color)
625 			:css('line-height', '0.95em')
626 			:css('font-size', '150%')
627 			:css('font-weight', 'bold')
628 			:css('width', '20px')
629 			:css('vertical-align', 'top')
630 			:css('border-top-left-radius', '5px')
631 			:css('border-top-right-radius', '5px')
632 			:wikitext(string.format(
633 				' [[Tiedosto:%s|link=%s|%s]]',
634 				image, link, tooltip
635 			))
636 		return cell
637 	end
638 
639 	local foregroundColor = '#002e54'
640 	local backgroundColor = '#d8e9fc'
641 
642 	-- Make the canon cell.
643 	do
644 		local id = 'canontab-canon'
645 		local link = canonTitle
646 		local color, image, tooltip
647 		if pageType == 'canon' then
648 			color = foregroundColor
649 			image = 'Tab-canon-white-edit.png'
650 			tooltip = 'Tämä on artikkelin kaanonversio.'
651 		else
652 			color = backgroundColor
653 			image = 'Tab-canon-black-edit.png'
654 			tooltip = "Paina tästä lukeaksesi artikkelin kaanonversiota."
655 		end
656 		row:node(makeCell(id, color, image, link, tooltip))
657 	end
658 
659 	-- First separator cell
660 	row:tag('td')
661 		:attr('id', 'canontab-separator1')
662 		:css('width', '3px')
663 		:wikitext('&nbsp;')
664 	
665 	-- Make the legends cell
666 	do
667 		local id = 'canontab-legends'
668 		local link = legendsTitle
669 		local color, image, tooltip
670 		if pageType ~= 'canon' then -- is a Legends page
671 			color = foregroundColor
672 			image = 'Tab-legends-white.png'
673 			tooltip = 'Tämä on artikkelin Legends-versio.'
674 		else -- is a Canon page
675 			color = backgroundColor
676 			image = 'Tab-legends-black.png'
677 			tooltip = "Paina tästä lukeaksesi artikkelin Legends-versiota."
678 		end
679 		row:node(makeCell(id, color, image, link, tooltip))
680 	end
681 	
682 	-- Second separator cell
683 	row:tag('td')
684 		:attr('id', 'canontab-separator2')
685 		:css('width', '3000px')
686 		:wikitext('&nbsp;')
687 	
688 	return tostring(root)
689 end
690 
691 -- Render all the categories that were specified using Eras:addCategory or with
692 -- category flags.
693 function Eras:renderCategories()
694 	local fullPagename = self.title.prefixedText
695 	if fullPagename == 'Malline:Eras' or fullPagename == 'Malline:Eraicon' then
696 		-- Exit if we are on a blacklisted page.
697 		return nil
698 	end
699 
700 	local pagename = self.title.text
701 
702 	-- Lisää yhden luokan.
703 	local function renderCategory(cat, sort)
704 		return string.format(
705 			'[[%s:%s|%s]]', 'Luokka', cat, sort or pagename
706 		)
707 	end
708 
709 	local ret = {}
710 
711 	-- Render categories from Eras:addCategory
712 	for i, t in ipairs(self.categories) do
713 		ret[i] = renderCategory(t.category, t.sortKey)
714 	end
715 
716 	-- Render categories from category flags.
717 	if self.hasBadParameter then
718 		ret[#ret + 1] = renderCategory(
719 			'Sivut, joissa on virheellisiä parametrejä mallineessa Eras'
720 		)
721 	end
722 	if self.hasIncorrectProtectionIcon then
723 		ret[#ret + 1] = renderCategory(
724 			'Sivut, joissa on virheellisiä suojausmerkintöjä'
725 		)
726 	end
727 
728 	return table.concat(ret)
729 end
730 
731 -- Lisää __NOTOC__ tarvittaessa
732 function Eras:renderNotoc()
733 	if self.hideToc then
734 		return '__NOTOC__'
735 	end
736 	return nil
737 end
738 
739 -- This method is called when the tostring function is used on the Eras object.
740 -- (This works in a similar fashion to Eras.__index above.) It calls all the
741 -- top-level render methods and returns the final output.
742 function Eras:__tostring()
743 	local ret = {}
744 	ret[#ret + 1] = self:renderDisplayTitle()
745 	ret[#ret + 1] = self:renderIcons()
746 	ret[#ret + 1] = self:renderCanonTab()
747 	ret[#ret + 1] = self:renderCategories()
748 	ret[#ret + 1] = self:renderNotoc()
749 	return table.concat(ret)
750 end
751 
752 -------------------------------------------------------------------------------
753 -- Exports
754 -------------------------------------------------------------------------------
755 
756 local p = {}
757 
758 -- This function is the entry point from other Lua modules.
759 function p._main(args)
760 	-- Define a function to call from pcall so that we can catch any errors
761 	-- and display them to the user rather than the cryptic "Script error".
762 	-- (It's not so cryptic if you click on it to see the error message, but
763 	-- not so many users know to do that.)
764 	local function getErasResult ()
765 		local erasObj = Eras.new(args)
766 		return tostring(erasObj)
767 	end
768 
769 	-- Get the result. We only catch errors if debug mode is set to false.
770 	local success, result
771 	if DEBUG_MODE then
772 		success = true
773 		result = getErasResult()
774 	else
775 		success, result = pcall(getErasResult)
776 	end
777 
778 	-- Return the result if there were no errors, and a formatted error message 
779 	-- if there were.
780 	if success then
781 		return result
782 	else
783 		return string.format(
784 			'<strong class="error">[[Malline:Eras]]-virhe: %s.</strong>' ..
785 			'[[Luokka:Sivut, joissa on virheellisiä parametrejä mallineessa Eras]]',
786 			result -- this is the error message
787 		)
788 	end
789 end
790 
791 -- This is the function accessed from wikitext. It must be accessed through
792 -- a template. The template should transclude only the text
793 -- "{{#invoke:Eras|main}}", and then when that template is used, any arguments
794 -- passed to it are magically sent through to the module.
795 function p.main(frame)
796 	local args = {}
797 	for k, v in pairs(frame:getParent().args) do
798 		v = v:match('^%s*(.-)%s*$') -- trim whitespace
799 		if v ~= '' then
800 			args[k] = v
801 		end
802 	end
803 	return p._main(args)
804 end
805 
806 return p
807 
808 -- </nowiki>
809 -- [[Luokka:Ulkoasumallineet|{{PAGENAME}}]]