Modulo:ChronoSpine/render

Da Wikitrek.
Vai alla navigazione Vai alla ricerca

La documentazione per questo modulo può essere creata in Modulo:ChronoSpine/render/man

--[[
Module:ChronoSpine/render
============================

Purpose
-------
Turns the ordered group list produced by Module:ChronoSpine/group
into the actual spine wikitext, using `mw.html` (never raw string
concatenation) and the CSS classes defined in
Template:ChronoSpine/styles.css.

This module never assembles Italian text itself: every piece of
display text (a formatted date, a stardate, a group heading) comes
from Module:ChronoSpine/i18n, and every site-specific name (a year's
link target, an icon file) comes from Module:ChronoSpine/config. This
module only decides STRUCTURE -- which `<div>`s, which classes, in
which order -- never wording.

Two public functions, deliberately kept separate:
  * M.render(groups, options)   -- the main spine.
  * M.problems_notice(problems) -- a small notice + tracking category,
                                    built from Module:ChronoSpine/manual's
                                    `problems` list. Kept apart from
                                    M.render so a future adapter with a
                                    different (or no) notion of
                                    "problem" is never forced through
                                    this exact path; the top-level
                                    module (Step 5) is what actually
                                    concatenates the two outputs.

What this version deliberately does NOT do yet (see the Step 4 design
discussion for the full reasoning on each):
  * `comprimi` (collapsing empty-year runs) -- would require
    synthesising placeholder groups, which Module:ChronoSpine/group
    explicitly does not do.
  * Any entry collapsing/`espandi` handling -- every entry in every
    group is rendered in full, always, for now.
  * `layout = tracce` -- silently falls back to the one layout this
    module knows how to draw.
  * The reale-mode "missing Italian date" fallback/footer marker --
    needs a per-entry flag the current entry contract does not have
    (that only arrives with the SMW adapter, Step 7).
--]]

local M = {}

local i18n = require("Module:ChronoSpine/i18n")
local config = require("Module:ChronoSpine/config")

--[[
is_hollow(e)
-------------
Private helper. An entry gets the hollow, dashed dot (§5 of the Step 4
design discussion) when it is explicitly marked "announced" (reale or
narrativo alike -- treated as a generic entry attribute, not a
reale-only concept), OR when it is an uncertain-date entry.

Checking `e.incerta` directly (rather than the group's `.kind`) is
deliberate and simpler: an entry's own `incerta` flag already tells
us exactly what Module:ChronoSpine/group used to decide whether it
went into the trailing "Data incerta" group in the first place -- the
two can never disagree, so there is no need to thread the group's
kind down into this function separately.
--]]
local function is_hollow(e)
	return e.stato == "annunciato" or e.incerta
end

--[[
resolve_icon_wikitext(categoria)
-----------------------------------
Private helper. Looks up `categoria` in Module:ChronoSpine/config's
M.category_icons.

Parameters:
  categoria (string|nil)

Returns:
  (string|nil) -- the icon's ready-to-use wikitext, or nil if
                  `categoria` is nil, or is set but not found in the
                  config table. Either way, render_entry() below
                  leaves `.chronospine-entry-icon` empty in that case,
                  and styles.css draws a plain CSS dot there instead --
                  a typo in an editor's `categoria=` value degrades
                  gracefully rather than producing a broken link.
--]]
local function resolve_icon_wikitext(categoria)
	if not categoria then
		return nil
	end
	return config.category_icons[categoria]
end

--[[
render_entry(parent, e, tempo)
---------------------------------
Private helper. Appends one `.chronospine-entry` node (icon + body:
date, stardate, label, note, source) to `parent`.

Parameters:
  parent (table)       -- an mw.html node (the group's container) to
                           append this entry's node to.
  e      (table)       -- one normalised entry (Module:ChronoSpine/entry).
  tempo  (string|nil)  -- "reale" or "narrativo"; controls whether the
                           stardate line is shown at all (narrativo only,
                           per the project briefing).
--]]
local function render_entry(parent, e, tempo)
	local entry_node = parent:tag("div"):addClass("chronospine-entry")
	if is_hollow(e) then
		entry_node:addClass("chronospine-entry--hollow")
	end

	local icon_node = entry_node:tag("div"):addClass("chronospine-entry-icon")
	local icon_wikitext = resolve_icon_wikitext(e.categoria)
	if icon_wikitext then
		icon_node:wikitext(icon_wikitext)
	end

	local body = entry_node:tag("div"):addClass("chronospine-entry-body")

	-- An `incerta` entry has no anno/mese/giorno at all (per
	-- Module:ChronoSpine/entry's contract) -- there is nothing to
	-- format, so the date line is omitted entirely rather than
	-- calling i18n.format_date on an empty date.
	if not e.incerta then
		body:tag("div"):addClass("chronospine-entry-date"):wikitext(i18n.format_date(e))
	end

	if tempo == "narrativo" and e.stellare then
		body:tag("div"):addClass("chronospine-entry-stardate")
			:wikitext(i18n.format_stardate(e.stellare))
	end

	body:tag("div"):addClass("chronospine-entry-label"):wikitext(e.voce)

	if e.nota then
		body:tag("div"):addClass("chronospine-entry-note"):wikitext(e.nota)
	end

	if e.fonte then
		body:tag("div"):addClass("chronospine-entry-source"):wikitext(e.fonte)
	end
end

--[[
render_group(parent, group, options)
---------------------------------------
Private helper. Appends one `.chronospine-group` node (heading marker
+ its entries) to `parent`.

Year auto-linking: only when this group is a normal "anno"-mode group
with a year, AND Module:ChronoSpine/config has a link pattern for the
current `tempo` ("Eventi %d" for reale, "Timeline %d" for narrativo --
see that module). Anything else (a different `raggruppa`, the
`incerta` group, or a future `tempo` value not in the config table)
falls back to plain, unlinked heading text.

`stagione` mode's heading text comes from the entry-supplied `gruppo`
field, which -- unlike `voce`/`fonte`/`nota` -- is NOT documented as
allowing wikitext, so it is passed through `mw.text.nowiki()` here
before being used as a heading. "anno"/"mese"/"giorno" headings and
the fixed "Data incerta"/"Senza gruppo" strings never need this: they
are either plain numbers/month names or our own trusted text.

Parameters:
  parent  (table) -- an mw.html node (the spine container).
  group   (table) -- one group, as produced by Module:ChronoSpine/group.
  options (table) -- the options passed to M.render (see its docstring).
--]]
local function render_group(parent, group, options)
	local group_node = parent:tag("div"):addClass("chronospine-group")
	if group.kind == "incerta" then
		group_node:addClass("chronospine-group--incerta")
	end

	local label_text = i18n.group_label(group.key, options.raggruppa, group.kind)
	local marker = group_node:tag("div"):addClass("chronospine-group-marker")

	local linked = false
	if group.kind == "normal" and options.raggruppa == "anno" and group.key.anno then
		local pattern = config.year_link_pattern[options.tempo]
		if pattern then
			local target = string.format(pattern, group.key.anno)
			marker:wikitext(string.format("[[%s|%s]]", target, label_text))
			linked = true
		end
	end

	if not linked then
		if options.raggruppa == "stagione" then
			marker:wikitext(mw.text.nowiki(label_text))
		else
			marker:wikitext(label_text)
		end
	end

	for _, e in ipairs(group.entries) do
		render_entry(group_node, e, options.tempo)
	end
end

--[[
M.render(groups, options)
----------------------------
The main entry point: builds the whole spine.

Parameters:
  groups  (table)      -- the ordered group list from
                           Module:ChronoSpine/group's M.group().
  options (table|nil)  -- {
                             tempo     = "reale"|"narrativo",
                             raggruppa = "anno"|"mese"|"giorno"|"stagione",
                             titolo    = string|nil,
                             allinea   = "destra"|"sinistra"|nil,
                           }
                           (the same `raggruppa` value that was passed
                           to Module:ChronoSpine/group's M.group() for
                           these `groups` -- this module trusts the
                           caller to pass the matching one, rather
                           than re-deriving it from the data).

Returns:
  (string) -- wikitext for the whole spine box. Does NOT include the
              problems notice or tracking category -- see
              M.problems_notice below.
--]]
function M.render(groups, options)
	options = options or {}

	local box = mw.html.create("div"):addClass("chronospine-box")
	if options.allinea == "sinistra" then
		box:addClass("chronospine-box--left")
	end

	if options.titolo then
		box:tag("div"):addClass("chronospine-title"):wikitext(options.titolo)
	end

	local spine = box:tag("div"):addClass("chronospine-spine")
	for _, group in ipairs(groups) do
		render_group(spine, group, options)
	end

	return tostring(box)
end

--[[
M.problems_notice(problems)
------------------------------
Builds the visible maintenance notice for Module:ChronoSpine/manual's
`problems` list, if any -- one line per problem (via
Module:ChronoSpine/i18n's M.message), plus the tracking category from
Module:ChronoSpine/config's M.problem_category.

Each message is passed through `mw.text.nowiki()` before being used as
wikitext. This matters because a problem's underlying raw text (e.g.
an unparseable date an editor typed) can itself contain wiki markup
and so on -- and that raw text ends up embedded inside
the message by Module:ChronoSpine/i18n's M.message. Escaping the
WHOLE resulting message is safe (the fixed Italian wording around it
is plain text with nothing that needs to render as markup) and is
what actually neutralises anything an editor's raw input might
otherwise inject into the page.

Parameters:
  problems (table|nil) -- as produced by Module:ChronoSpine/manual's
                           M.build() (the `.problems` field).

Returns:
  (string|nil) -- wikitext for the notice + category, or `nil` if
                  `problems` is nil or empty (nothing to show, and
                  nothing to add the page to).
--]]
function M.problems_notice(problems)
	if not problems or #problems == 0 then
		return nil
	end

	local box = mw.html.create("div"):addClass("chronospine-problems")
	box:tag("p"):wikitext("'''ChronoSpine: problemi rilevati nei dati.'''")

	local list = box:tag("ul")
	for _, problem in ipairs(problems) do
		list:tag("li"):wikitext(mw.text.nowiki(i18n.message(problem)))
	end

	return tostring(box) .. "[[" .. config.problem_category .. "]]"
end

return M