Module:ArgumentContent
La documentation pour ce module peut être créée à Module:ArgumentContent/doc
-- Module:ArgumentContent (MediaWiki 1.43)
local p = {}
-- ========================
-- I18N
-- ========================
local MESSAGES = {
fr = {
justifs_title = "Justifications",
objs_title = "Objections",
edit = "modifier",
edit_justifs_tt = "Modifier la liste des arguments ci-dessous",
edit_objs_tt = "Modifier la liste des objections ci-dessous",
add_argument = "Ajouter un argument",
add_objection = "Ajouter une objection",
add_justif_tt = "Ajouter une justification à l'argument : %s",
add_obj_tt = "Ajouter une objection à l'argument : %s",
none_argument = "''Aucun argument n'a été entré.''",
none_objection = "''Aucune objection n'a été entrée.''",
none_content = "''Aucun contenu n'a été entré.''",
detailed_debate_title = "%s",
prop_page_params = "Paramètres de la page", -- plus utilisé, conservé pour compat éventuelle
prop_argument_content = "Contenu d'argument",
prop_arguments_map = "Carte des arguments",
-- Propriétés SMW utilisées pour la requête groupée
prop_argument_number = "Numéro d'argument",
prop_detailed_debate = "Débat détaillé",
prop_justifs_list = "Liste des justifications",
prop_objs_list = "Liste des objections",
prop_warn_justifs = "Avertissements pour les justifications",
prop_warn_objs = "Avertissements pour les objections",
form_justifications = "Justifications",
form_objections = "Objections",
form_new_argument_title = "Nouveau titre d'argument",
form_type = "type",
type_justification = "Justification",
type_objection = "Objection",
template_banner = "Bandeau %s",
},
en = {
justifs_title = "Supporting arguments",
objs_title = "Objections",
edit = "edit",
edit_justifs_tt = "Edit the list of arguments below",
edit_objs_tt = "Edit the list of objections below",
add_argument = "Add an argument",
add_objection = "Add an objection",
add_justif_tt = "Add a supporting argument to: %s",
add_obj_tt = "Add an objection to: %s",
none_argument = "''No argument has been entered.''",
none_objection = "''No objection has been entered.''",
none_content = "''No content has been entered.''",
detailed_debate_title = "%s",
prop_page_params = "Page parameters",
prop_argument_content = "Argument content",
prop_arguments_map = "Arguments map",
prop_argument_number = "Argument number",
prop_detailed_debate = "Detailed debate",
prop_justifs_list = "List of justifications",
prop_objs_list = "List of objections",
prop_warn_justifs = "Warnings for justifications",
prop_warn_objs = "Warnings for objections",
form_justifications = "Supporting arguments",
form_objections = "Objections",
form_new_argument_title = "New argument title",
form_type = "type",
type_justification = "Justification",
type_objection = "Objection",
template_banner = "%s banner",
},
es = {
justifs_title = "Justificaciones",
objs_title = "Objeciones",
edit = "editar",
edit_justifs_tt = "Editar la lista de argumentos de abajo",
edit_objs_tt = "Editar la lista de objeciones de abajo",
add_argument = "Añadir un argumento",
add_objection = "Añadir una objeción",
add_justif_tt = "Añadir una justificación a: %s",
add_obj_tt = "Añadir una objeción a: %s",
none_argument = "''No se ha introducido ningún argumento.''",
none_objection = "''No se ha introducido ninguna objeción.''",
none_content = "''No se ha introducido ningún contenido.''",
detailed_debate_title = "%s",
prop_page_params = "Parámetros de la página",
prop_argument_content = "Contenido del argumento",
prop_arguments_map = "Mapa de argumentos",
prop_argument_number = "Número de argumento",
prop_detailed_debate = "Debate detallado",
prop_justifs_list = "Lista de justificaciones",
prop_objs_list = "Lista de objeciones",
prop_warn_justifs = "Avisos para las justificaciones",
prop_warn_objs = "Avisos para las objeciones",
form_justificaciones = "Justificaciones",
form_objections = "Objeciones",
form_new_argument_title = "Nuevo título de argumento",
form_type = "tipo",
type_justification = "Justificación",
type_objection = "Objeción",
template_banner = "Aviso %s",
},
}
-- ========================
-- Helpers
-- ========================
local function trim(s)
if type(s) ~= "string" then
return s
end
return s:match("^%s*(.-)%s*$")
end
-- Fast split (plain find, sans patterns)
local function split(s, delim)
if type(s) ~= "string" or s == "" then
return {}
end
delim = delim or ","
if delim == "" then
return { s }
end
local out, start = {}, 1
while true do
local i, j = string.find(s, delim, start, true)
if not i then
out[#out+1] = trim(string.sub(s, start))
break
end
out[#out+1] = trim(string.sub(s, start, i - 1))
start = j + 1
end
return out
end
local function safeIndex(t, idx, default)
if type(t) ~= "table" then
return default
end
local v = t[idx]
if v == nil or v == "" then
return default
end
return v
end
local function toNumber(s, default)
local n = tonumber(s or "")
return n or default or 0
end
local function urlencode(s, mode)
return mw.uri.encode(s or "", mode or "QUERY")
end
local function anchorencode(s)
return mw.uri.anchorEncode(s or "")
end
-- ========================
-- I18N helpers
-- ========================
local function normalizeLang(code)
code = trim(code or "")
if code == "" then
return ""
end
local base = code:match("^([a-zA-Z]+)") or code
return mw.ustring.lower(base)
end
local function getLang(args)
local param = normalizeLang(args.lang or args.language or "")
if param ~= "" and MESSAGES[param] then
return param
end
local contentLang = ""
if mw.getContentLanguage then
local obj = mw.getContentLanguage()
if obj and obj.getCode then
contentLang = normalizeLang(obj:getCode())
end
end
if contentLang ~= "" and MESSAGES[contentLang] then
return contentLang
end
return "en"
end
local function msg(lang, key, ...)
local pack = MESSAGES[lang] or MESSAGES.en
local text = pack[key] or (MESSAGES.en and MESSAGES.en[key]) or key
if select("#", ...) > 0 then
return string.format(text, ...)
end
return text
end
-- ========================
-- VariablesLua cache
-- ========================
local VL = (mw.ext and mw.ext.VariablesLua) or nil
local LOCAL_CACHE = {}
local function cache_get(key)
local v = LOCAL_CACHE[key]
if v ~= nil then
return v
end
if VL and VL.var then
local vv = VL.var(key, nil)
if vv ~= nil and vv ~= "" then
LOCAL_CACHE[key] = vv
return vv
end
end
return nil
end
local function cache_set(key, value)
local v = value or ""
LOCAL_CACHE[key] = v
if VL and VL.vardefine then
if VL.var and VL.var(key, nil) == v then
return
end
VL.vardefine(key, v)
end
end
-- ========================
-- SMW access
-- ========================
local function smwShow(frame, page, property)
if not page or page == "" or not property or property == "" then
return ""
end
local cacheKey = "WDB:pf#show|" .. page .. "|" .. property
local cached = cache_get(cacheKey)
if cached ~= nil then
return cached
end
local out = trim(frame:callParserFunction("#show", { page, "?" .. property }) or "")
cache_set(cacheKey, out)
return out
end
-- ========================
-- SMW – une seule requête pour toutes les propriétés d'un argument
-- ========================
local ARGDATA_CACHE = {}
local function smwGetArgumentData(frame, pageTitle)
if not pageTitle or pageTitle == "" then
return {}
end
local cacheKey = "WDB:argDataByPage|" .. pageTitle
local cached = ARGDATA_CACHE[cacheKey]
if cached then
return cached
end
-- Propriétés SMW (noms EXACTS des Property:)
local PROP_ID = "Numéro d'argument"
local PROP_CONTENT = "Contenu d'argument"
local PROP_JUSTIFS = "Liste des justifications"
local PROP_OBJS = "Liste des objections"
local PROP_DETAILED = "Débat détaillé"
local PROP_WARN_J = "Avertissements pour les justifications"
local PROP_WARN_O = "Avertissements pour les objections"
local data = {
pageTitle = pageTitle,
idArgument = "",
contenuArgument = "",
pageDebatDetaille = "",
pairesJustifsRaw = "",
pairesObjRaw = "",
bandeauxJustifsCsv = "",
bandeauxObjsCsv = "",
}
if not (mw.smw and mw.smw.ask) then
ARGDATA_CACHE[cacheKey] = data
return data
end
local query = {
"[[ " .. pageTitle .. " ]]",
"?" .. PROP_ID,
"?" .. PROP_CONTENT,
"?" .. PROP_JUSTIFS,
"?" .. PROP_OBJS,
"?" .. PROP_DETAILED,
"?" .. PROP_WARN_J,
"?" .. PROP_WARN_O,
"mainlabel=page",
"limit=1",
}
local res = mw.smw.ask(query)
local row = res and res[1] or nil
if not row then
ARGDATA_CACHE[cacheKey] = data
return data
end
-- Nettoie un éventuel lien [[Page]] ou [[:Page|Texte]]
local function normalizePageValue(v)
v = trim(tostring(v or ""))
-- enlève crochets et éventuel deux-points + texte après |
v = v:gsub("^%[%[:?(.-)%]%]$", "%1")
v = v:gsub("^(.-)|.*$", "%1")
return trim(v)
end
local function firstField(r, key, isPage)
local v = r[key]
if v == nil then
return ""
end
if type(v) == "table" then
v = v[1]
end
v = trim(tostring(v or ""))
if isPage then
v = normalizePageValue(v)
end
return v
end
data.idArgument = firstField(row, PROP_ID, false)
data.contenuArgument = firstField(row, PROP_CONTENT, false)
data.pageDebatDetaille = firstField(row, PROP_DETAILED, true) -- ✨ page → on nettoie [[...]]
data.pairesJustifsRaw = firstField(row, PROP_JUSTIFS, false)
data.pairesObjRaw = firstField(row, PROP_OBJS, false)
data.bandeauxJustifsCsv = firstField(row, PROP_WARN_J, false)
data.bandeauxObjsCsv = firstField(row, PROP_WARN_O, false)
ARGDATA_CACHE[cacheKey] = data
return data
end
-- ========================
-- Page Forms helpers
-- ========================
local function formlink(frame, form, target, linktext, tooltip)
return frame:callParserFunction("#formlink", {
"form=" .. (form or ""),
"target=" .. (target or ""),
"link text=" .. (linktext or ""),
"tooltip=" .. (tooltip or "")
})
end
local function queryformlink(frame, form, querystring, linktype, linktext, tooltip)
return frame:callParserFunction("#queryformlink", {
"form=" .. (form or ""),
"query string=" .. (querystring or ""),
"link type=" .. (linktype or ""),
"link text=" .. (linktext or ""),
"tooltip=" .. (tooltip or "")
})
end
-- ========================
-- Bandeaux (mémoïsés)
-- ========================
local BANDEAUX_CACHE = {}
local function expandBandeaux(frame, csv, lang)
if not csv or csv == "" then
return ""
end
local ck = "WDB:bandeaux|" .. lang .. "|" .. csv
local c = BANDEAUX_CACHE[ck]
if c ~= nil then
return c
end
local html = {}
for _, item in ipairs(split(csv, ",")) do
if item and item ~= "" then
local title = msg(lang, "template_banner", item)
html[#html+1] = frame:expandTemplate{
title = title,
args = { item }
}
end
end
local out = table.concat(html, " ")
BANDEAUX_CACHE[ck] = out
return out
end
function p.render(frame)
local parent = frame:getParent()
local args = {}
-- Fallback depuis parent
for k, v in pairs(parent and parent.args or {}) do
args[k] = v
end
-- Override par #invoke
for k, v in pairs(frame.args or {}) do
if v ~= nil and v ~= "" then
args[k] = v
end
end
local L = getLang(args)
local nowiki = mw.text.nowiki
local uencQ = function(s) return mw.uri.encode(s or "", "QUERY") end
local aenc = function(s) return mw.uri.anchorEncode(s or "") end
-- Messages
local PROP_ARG_CONTENT = msg(L, "prop_argument_content")
local PROP_ARGS_MAP = msg(L, "prop_arguments_map")
local TITLE_JUSTIFS = msg(L, "justifs_title")
local TITLE_OBJS = msg(L, "objs_title")
local NONE_ARG = msg(L, "none_argument")
local NONE_OBJ = msg(L, "none_objection")
local NONE_CONTENT = msg(L, "none_content")
local DETAILED_TITLE = function(s) return msg(L, "detailed_debate_title", s) end
local EDIT = msg(L, "edit")
local EDIT_J_TT = msg(L, "edit_justifs_tt")
local EDIT_O_TT = msg(L, "edit_objs_tt")
local ADD_ARG = msg(L, "add_argument")
local ADD_OBJ = msg(L, "add_objection")
local ADD_J_TT = function(s) return msg(L, "add_justif_tt", s) end
local ADD_O_TT = function(s) return msg(L, "add_obj_tt", s) end
-- Paramètres
local pageArgument = mw.uri.decode(trim(args.argument or ""))
local cheminRaw = trim(args.path or args.chemin or "")
local levelRaw = toNumber(args.level or args.niveau, 0)
local niveau = levelRaw + 1
local typeArg = trim(args.type or "")
local avertissements = trim(args.warnings or args.avertissements or "")
local pageParam = trim(args.page or "")
local editableRaw = mw.ustring.lower(trim(args.editable or ""))
local isEditable = (editableRaw == "yes" or editableRaw == "oui" or editableRaw == "true" or editableRaw == "1")
local cheminParts = split(cheminRaw, "@@@")
local lastPath = safeIndex(cheminParts, #cheminParts, "")
local racineParts = split(lastPath, ":::")
local racine = safeIndex(racineParts, 2, "")
-- UNE SEULE requête SMW
local data = smwGetArgumentData(frame, pageArgument)
local pageTitle = data.pageTitle or pageArgument
local idArgument = data.idArgument or ""
local contenuArgument = data.contenuArgument or ""
local pageDebatDetaille = data.pageDebatDetaille or ""
local pairesJustifsRaw = data.pairesJustifsRaw or ""
local pairesObjRaw = data.pairesObjRaw or ""
local bandeauxJustifsCsv= data.bandeauxJustifsCsv or ""
local bandeauxObjsCsv = data.bandeauxObjsCsv or ""
-- Si aucun contenu du tout
if (contenuArgument == "" or contenuArgument == nil)
and (pairesJustifsRaw == "" or pairesJustifsRaw == nil)
and (pairesObjRaw == "" or pairesObjRaw == nil)
and (pageDebatDetaille == "" or pageDebatDetaille == nil)
then
return '<div class="aucun-contenu">' .. NONE_CONTENT .. '</div>__NOTOC__'
end
local avertHtml = ""
if avertissements and avertissements ~= "" then
avertHtml = expandBandeaux(frame, avertissements, L)
end
local preOpen, preClose = "", ""
if isEditable and levelRaw == 1 then
preOpen = string.format('<div class="contenu-argument-%s">', typeArg or "")
preClose = '</div>'
end
local bodyHtml = {}
bodyHtml[#bodyHtml+1] = string.format(
'<div class="contenu-argument">%s</div>',
contenuArgument or ""
)
-- Débat détaillé
if pageDebatDetaille and pageDebatDetaille ~= "" then
local carteProp = PROP_ARGS_MAP
local carteArgs = smwShow(frame, pageDebatDetaille, carteProp)
if (carteArgs == "" or carteArgs == nil) and L ~= "fr" then
carteArgs = smwShow(frame, pageDebatDetaille, MESSAGES.fr.prop_arguments_map)
end
bodyHtml[#bodyHtml+1] = string.format(
'<div class="carte-debat-detaille onglet-externe">' ..
'<div class="titre-debat-detaille">%s</div>' ..
'%s' ..
'</div>',
DETAILED_TITLE(pageDebatDetaille),
carteArgs or ""
)
-- Sinon : Justifs + Objs
else
local function buildList(pairesRaw, sens, sensInverseOpt)
local out = {}
local paires = split(pairesRaw, "&&&")
local total = #paires
local sensInverse = sensInverseOpt
local typePour = (typeArg == "pour")
for i = 1, total do
local brut = safeIndex(paires, i, "")
if brut ~= "" then
local paire = split(brut, "-¡-")
local id = safeIndex(paire, 1, "")
local titre = safeIndex(paire, 2, id)
if id ~= "" or titre ~= "" then
local titreEnc = uencQ(titre)
local idEnc = uencQ(id)
local t = sens
if sensInverse then
t = typePour and "contre" or "pour"
end
out[#out+1] =
string.format(
'<div class="argument-expandable">' ..
'<div id="%s" class="argument argument-expandable-title level-%d level-sup" ' ..
'data-template="%s" ' ..
'data-page="%s" ' ..
'data-argument="%s" ' ..
'data-type="%s" ' ..
'data-level="%d" ' ..
'data-root="%s" ' ..
'data-path="%s@@@%s:::%s" ' ..
'data-warnings="%s">' ..
'[[File: Expand.svg | 11px | link= | alt= | class=fleche-deroulante]]' ..
'[[File: Argument-%s.svg | 17px | link= | alt= | class=pictogramme-h4 mw-no-invert]]' ..
'<span>%s</span>' ..
'</div>' ..
'<div class="argument-content-wrapper"></div>' ..
'</div>',
aenc(titre),
niveau,
PROP_ARG_CONTENT,
nowiki(pageParam or ""),
idEnc,
nowiki(t),
niveau,
nowiki(racine),
nowiki(cheminRaw or ""),
nowiki(t),
titreEnc,
nowiki(avertissements or ""),
nowiki(t),
nowiki(titre)
)
end
end
end
if #out == 0 then
return '<div class="aucun-argument">' .. (sensInverse and NONE_OBJ or NONE_ARG) .. "</div>"
end
return table.concat(out, "")
end
local formJust = msg(L, "form_justifications")
local formObj = msg(L, "form_objections")
local formNew = msg(L, "form_new_argument_title")
local formType = msg(L, "form_type")
if L ~= "fr" then
formJust = formJust ~= "" and formJust or MESSAGES.fr.form_justifications
formObj = formObj ~= "" and formObj or MESSAGES.fr.form_objections
formNew = formNew ~= "" and formNew or MESSAGES.fr.form_new_argument_title
formType = formType ~= "" and formType or MESSAGES.fr.form_type
end
local modJust = ""
local modObj = ""
local btnJust = ""
local btnObj = ""
if isEditable then
modJust = formlink(frame, formJust, pageTitle, EDIT, EDIT_J_TT)
modObj = formlink(frame, formObj, pageTitle, EDIT, EDIT_O_TT)
local typeJust = msg(L, "type_justification")
local typeObj = msg(L, "type_objection")
if L ~= "fr" then
typeJust = typeJust ~= "" and typeJust or MESSAGES.fr.type_justification
typeObj = typeObj ~= "" and typeObj or MESSAGES.fr.type_objection
end
btnJust = queryformlink(
frame,
formNew,
string.format(
"%s[%s]=%s&%s[ID]=%s&_run",
formNew,
formType,
(typeJust or ""),
formNew,
(idArgument or "")
),
"post button",
ADD_ARG,
ADD_J_TT(pageArgument or "")
)
btnObj = queryformlink(
frame,
formNew,
string.format(
"%s[%s]=%s&%s[ID]=%s&_run",
formNew,
formType,
(typeObj or ""),
formNew,
(idArgument or "")
),
"post button",
ADD_OBJ,
ADD_O_TT(pageArgument or "")
)
end
local gauche = string.format(
'<div class="colonne-gauche">' ..
'<div class="NavFramex %s">' ..
'<div class="NavHead"><span class="titre-boite">%s</span>%s</div>' ..
'<div class="NavContent">' ..
'<div>%s</div>' ..
'%s' ..
'</div>' ..
'%s' ..
'</div>' ..
'</div>',
(typeArg == "pour") and "bordure-argument-pour" or "bordure-argument-contre",
TITLE_JUSTIFS,
(isEditable and ('<span class="modifier-section">' .. modJust .. '</span>') or ''),
expandBandeaux(frame, bandeauxJustifsCsv, L),
(pairesJustifsRaw ~= "" and buildList(pairesJustifsRaw, typeArg, false) or '<div class="aucun-argument">' .. NONE_ARG .. '</div>'),
(isEditable and ('<div class="NavButton"><div class="bouton-ajouter navigation-not-searchable">' .. btnJust .. '</div></div>') or '')
)
local droite = string.format(
'<div class="colonne-droite">' ..
'<div class="NavFramex %s">' ..
'<div class="NavHead"><span class="titre-boite">%s</span>%s</div>' ..
'<div class="NavContent">' ..
'<div>%s</div>' ..
'%s' ..
'</div>' ..
'%s' ..
'</div>' ..
'</div>',
(typeArg == "pour") and "bordure-argument-contre" or "bordure-argument-pour",
TITLE_OBJS,
(isEditable and ('<span class="modifier-section">' .. modObj .. '</span>') or ''),
expandBandeaux(frame, bandeauxObjsCsv, L),
(pairesObjRaw ~= "" and buildList(pairesObjRaw, typeArg, true) or '<div class="aucun-argument">' .. NONE_OBJ .. '</div>'),
(isEditable and ('<div class="NavButton"><div class="bouton-ajouter navigation-not-searchable">' .. btnObj .. '</div></div>') or '')
)
bodyHtml[#bodyHtml+1] = '<div class="colonnes">'
bodyHtml[#bodyHtml+1] = gauche
bodyHtml[#bodyHtml+1] = droite
bodyHtml[#bodyHtml+1] = '</div>'
end
local html = {}
if avertHtml ~= "" then
html[#html+1] = string.format('<div>%s</div>', avertHtml)
end
html[#html+1] = preOpen
html[#html+1] = table.concat(bodyHtml, "")
html[#html+1] = preClose
html[#html+1] = "__NOTOC__"
return table.concat(html, "")
end
return p