Aller au contenu

Module:NavLivreSommaire

Un livre de Wikilivres.

La documentation pour ce module peut être créée à Module:NavLivreSommaire/Documentation

-- Module:NavLivreSommaire
local p = {}

local function trim(s)
	return mw.text.trim(s or '')
end

local function normalizeTitle(s)
	s = trim(s)
	if s == '' then return nil end
	local t = mw.title.new(s)
	if not t then return nil end
	return t.prefixedText
end

local function extractBetweenMarkers(wikitext, startMarker, endMarker)
	startMarker = startMarker and startMarker ~= '' and startMarker or '<!--NAV-START-->'
	endMarker   = endMarker   and endMarker   ~= '' and endMarker   or '<!--NAV-END-->'

	local a = wikitext:find(startMarker, 1, true)
	local b = wikitext:find(endMarker, 1, true)
	if a and b and b > a then
		return wikitext:sub(a + #startMarker, b - 1)
	end
	return wikitext
end

local function parseLinks(wikitext, baseForRelative)
	local items = {}
	local seen = {}

	-- Capture le contenu de [[...]] sans gérer tous les cas exotiques.
	-- Suffisant si le sommaire contient des liens "simples" vers les chapitres.
	for inner in mw.ustring.gmatch(wikitext, '%[%[([^%[%]]-)%]%]') do
		local target, label = inner:match('^([^|]+)|(.+)$')
		target = trim(target or inner)

		-- Ignore les ancres seules
		if target ~= '' and target:sub(1,1) ~= '#' then
			-- Supprime une éventuelle ancre de section
			target = target:gsub('#.*$', '')

			if baseForRelative and target:sub(1,1) == '/' then
				target = baseForRelative .. target
			end

			local norm = normalizeTitle(target)
			if norm and not seen[norm] then
				seen[norm] = true
				items[#items + 1] = {
					target = norm,
					label = trim(label) ~= '' and trim(label) or nil
				}
			end
		end
	end

	return items
end

local function defaultLabelFromTarget(prefixedText)
	-- Affiche seulement la dernière portion après "/"
	local t = prefixedText:gsub('^.+:', '') -- enlève le namespace si présent
	local last = t:match('([^/]+)$') or t
	return last
end

local function makeLink(target, label)
	return string.format('[[%s|%s]]', target, label)
end

local function renderBanner(position, prevItem, tocTarget, tocLabel, nextItem)
	local html = mw.html.create('div')
	html:addClass('nav-livre')
	html:addClass(position == 'bas' and 'nav-livre--bas' or 'nav-livre--haut')

	local left = mw.html.create('span'):addClass('nav-livre__prev')
	local center = mw.html.create('span'):addClass('nav-livre__toc')
	local right = mw.html.create('span'):addClass('nav-livre__next')

	if prevItem then
		local lbl = prevItem.label or defaultLabelFromTarget(prevItem.target)
		left:wikitext(makeLink(prevItem.target, '← ' .. lbl))
	end

	center:wikitext(makeLink(tocTarget, tocLabel))

	if nextItem then
		local lbl = nextItem.label or defaultLabelFromTarget(nextItem.target)
		right:wikitext(makeLink(nextItem.target, lbl .. ' →'))
	end

	html:node(left)
	html:node(center)
	html:node(right)

	return tostring(html)
end

function p.main(frame)
	local args = frame.args
	local position = trim(args.position or args.pos or 'haut'):lower()
	if position ~= 'haut' and position ~= 'bas' then position = 'haut' end

	local sommaire = trim(args.sommaire or args.toc or '')
	if sommaire == '' then
		return ''
	end

	local tocTitle = mw.title.new(sommaire)
	if not tocTitle then
		return ''
	end

	local tocLabel = trim(args.sommaire_label or args.toc_label or 'Sommaire')
	local startMarker = args.debut or args.start
	local endMarker = args.fin or args["end"]

	local content = tocTitle:getContent()
	if not content or content == '' then
		-- Si le sommaire n’existe pas ou n’est pas lisible, on affiche au moins un lien central
		return renderBanner(position, nil, tocTitle.prefixedText, tocLabel, nil)
	end

	local slice = extractBetweenMarkers(content, startMarker, endMarker)

	local baseForRelative = tocTitle.text

	local items = parseLinks(slice, baseForRelative)
	if #items == 0 then
		return renderBanner(position, nil, tocTitle.prefixedText, tocLabel, nil)
	end

	local current = mw.title.getCurrentTitle().prefixedText
	local idx

	for i, it in ipairs(items) do
		if it.target == current then
			idx = i
			break
		end
	end

	if not idx then
		-- Page non listée dans le sommaire : on affiche seulement le lien vers le sommaire.
		return renderBanner(position, nil, tocTitle.prefixedText, tocLabel, nil)
	end

	local prevItem = idx > 1 and items[idx - 1] or nil
	local nextItem = idx < #items and items[idx + 1] or nil

	return renderBanner(position, prevItem, tocTitle.prefixedText, tocLabel, nextItem)
end

return p