Módulo:Navegación

La documentación para este módulo puede ser creada en Módulo:Navegación/doc

-- port a lua de la [[Plantilla:Navegación]]
-- hecho por DennouNeko y traducido por Daisuke

-- == Funciones de ayuda ==
--[[ librerias ]]
local common = require("Module:Common")
local style = require("Module:Styles")

local colors = style.main_colors

if mw.text == nil then mw.text = require("Module:MW.text") end

--[[ Recuperar una lista completa de argumentos como 'lista#', 'estilolista#', 'grupo#' y 'estilogrupo#'
     como una tabla de objetos con campos como 'índice', 'lista', 'estilolista', 'grupo' y 'estilogrupo'
     en donde 'índice' es el número del nombre del argumento 'lista#'
     'lista' e 'índice' son los únicos que siempre están definidos. Los elementos que no se especifiquen estarán vacíos.
     Si no se define el argumento 'lista#', los demás elementos que tengan un índice serán ignorados.
     La clave del argumento debe coincidir con el número en el nombre de los argumentos. ]]
local function scan_lists(frame)
  local idx = {}
  local list = {}
  local group = {}
  local liststyle = {}
  local groupstyle = {}

  local ret = {}

  local s,e,t

  for k,v in frame:argumentPairs() do
    -- sólo acepta parámetros que no comienzen con ceros
    s,e,t = string.find(k, '^estilogrupo([1-9]%d*)$')
    if s ~= nil then groupstyle[tonumber(t)] = v end

    s,e,t = string.find(k, '^estilolista([1-9]%d*)$')
    if s ~= nil then liststyle[tonumber(t)] = v end

    s,e,t = string.find(k, '^grupo([1-9]%d*)$')
    if s ~= nil then group[tonumber(t)] = v end

    s,e,t = string.find(k, '^lista([1-9]%d*)$')
    if s ~= nil then
      list[tonumber(t)] = v
      idx[#idx+1] = tonumber(t)
    end
  end

  table.sort(idx)

  for k,v in pairs(idx) do
    local tmp = {}
    tmp['índice'] = v
    tmp['lista'] = list[v]
    if common.isset(group[v]) then tmp['grupo'] = group[v] end
    if common.isset(liststyle[v]) then tmp['estilo lista'] = liststyle[v] end
    if common.isset(groupstyle[v]) then tmp['estilo grupo'] = groupstyle[v] end
    ret[#ret+1] = tmp
  end

  return ret
end


-- == Funciones que genera el documento de elementos ==

--[[ Se genera el incio de la caja, basado en un 'ttype' y en un 'borde' específico. ]]
local function start_box(frame, border, ttype)
  local ret = {}
  if border == "subgroup" or border == "child" then
    -- ya que estamos en una celda de la navegación matriz, debemos cerrarlo con un elemento <div>
    ret[#ret+1] = '</div>'
  elseif border == "none" then
    -- no hay nada que hacer
  else
    -- uno nuevo, una navegación independiente - se creará un marco a su alrededor
    ret[#ret+1] = '<table class="navbox'
    if common.isset(frame.args['clase cuerpo']) then ret[#ret+1] = " " .. frame.args['clase cuerpo'] end
    ret[#ret+1] = '" cellspacing="0" style="'
    if common.isset(colors[ttype]) then ret[#ret+1] = 'background:' .. colors[ttype]['background'] .. ';' end
    -- ret[#ret+1] = 'border:1px solid #aaa; padding:1px;width:100%;vertical-align:middle;margin:auto;clear:both;font-size:88%;text-align:center;'
    style.add(ret, frame.args['estilo cuerpo'])
    style.add(ret, frame.args['estilo'])
    ret[#ret+1] = '"><tr><td style="padding: 2px;">'
  end

  -- La navegación de ahora comienza aquí
  ret[#ret+1] = '<table cellspacing="0" class="nowraplinks'
  if common.isset(frame.args['título']) and frame.args['estado'] ~= "plain" and frame.args['estado'] ~= "off" then
    --TODO: si soporta los parámetrps collapsible y autocollapse, comenta aquí
    --ret[#ret+1] = ' collapsible '
    ret[#ret+1] = ' mw-collapsible '
    if common.isset(frame.args['estado']) then
      ret[#ret+1] = frame.args['estado']
    else
      --ret[#ret+1] = 'autocollapse'
      ret[#ret+1] = 'mw-collapsed'
    end
  end
  if border == "subgroup" or border == "child" or border == "none" then
    ret[#ret+1] = ' navbox-subgroup" style="'
    if common.isset(colors[ttype]) then table.insert(ret, 'background:' .. colors[ttype]['background'] .. ';') end
    -- ret[#ret+1] = 'margin:auto;clear:both;font-size:88%;text-align:center;width:100%;vertical-align:middle;'
    ret[#ret+1] = 'width: 100%;'
    style.add(ret, frame.args['estilo cuerpo'])
    style.add(ret, frame.args['estilo'])
    ret[#ret+1] = '">'
  else
    ret[#ret+1] = '" style="width:100%;background:transparent;color:inherit;">'
  end

  return table.concat(ret)
end

--[[ Terminado la caja, se basa en un 'ttype' y 'borde' específico ]]
local function end_box(frame, border, ttype)
  local ret = {""}
  ret[#ret+1] = '</table>'
  if border == "subgroup" or border == "child" then
    -- como cerramos un <div>, abremos otro para comenzar esta navegación child
    ret[#ret+1] = '<div>'
  elseif border == "none" then
    -- no hay nada que hacer
  else
    -- cerremos la tabla al comenzar un marco
    ret[#ret+1] = '</td></tr></table>'
  end
  return table.concat(ret)
end

--[[ Generar la fila del título: botones 'v(er) • d(iscusión) • e(ditar)' + títulos + '[Expand]'/'[Collapse]'. ]]
local function build_title(frame, border, ttype)
  local ret = {""}
  if not common.isset(frame.args['título']) then return '' end

  table.insert(ret, '<tr>')
  if common.isset(frame.args['título grupo']) then
    -- en caso de que no haya un grupo para el título
    ret[#ret+1] = '<td class="navbox-group" style="'
    if common.isset(colors[ttype]) then ret[#ret+1] = 'background:' .. colors[ttype]['grupo'] .. ';' end
    ret[#ret+1] = 'padding-left:1em;padding-right:1em;white-space:nowrap;text-align:right;'
    style.add(ret, frame.args['estilo base'])
    style.add(ret, frame.args['estilo grupo'])
    style.add(ret, frame.args['estilo título grupo'])
    ret[#ret+1] = '">\n'
    ret[#ret+1] = frame.args['título grupo']
    ret[#ret+1] = '</td>'

    ret[#ret+1] = '<th style="border-left:2px solid '
    ret[#ret+1] = common.cv(common.isset(colors[ttype]), colors[ttype]['background'], '#fdfdfd')
    ret[#ret+1] = ';width:100%;'
  else
    -- sin grupo para el título
    table.insert(ret, '<th style="')
  end
  if common.isset(colors[ttype]) then ret[#ret+1] = 'background:' .. colors[ttype]['título'] .. ';' end
  ret[#ret+1] = 'text-align:center;'
  style.add(ret, frame.args['estilo base'])
  style.add(ret, frame.args['estilo título'])
  ret[#ret+1] = '"'

  -- calcular el colspan para el título
  local cs = 2 -- 2 porque cada grupo con el elemnto lista ocupa 2 columnas
  if common.isset(frame.args['imagen izquierda']) then cs = cs + 1 end
  if common.isset(frame.args['imagen']) then cs = cs + 1 end
  if common.isset(frame.args['título grupo']) then cs = cs - 1 end -- si en el caso en que hubiese un título grupo, este ocuparía una celda ya hecha
  ret[#ret+1] = ' colspan = "' .. cs .. '"'
  ret[#ret+1] = ' class="navbox-title">'

  -- agrega navbars y/o padding cuando se necesita
  if frame.args['navbar'] == "plain" or frame.args['navbar'] == "off" or border == "subgroup" or border == "child" or border == "none" then
    if frame.args['navbar'] == "off" then
      if frame.args['estado'] == "plain" then ret[#ret+1] = '<div style="float:right;width:6em;">&nbsp;</div>' end
    else
      if frame.args['estado'] ~= "plain" then ret[#ret+1] = '<div style="float:left; width:6em;text-align:left;">&nbsp;</div>' end
    end
  else
    if frame.args['estado'] == "plain" then ret[#ret+1] = '<div style="float:right;width:6em;">&nbsp;</div>' end

    ret[#ret+1] = '<div style="float:left; width:6em;text-align:left;">'
    if frame.args['nombre'] ~= nil then
      local args = {}
      args[#args+1] = frame.args['nombre']
      args['mini'] = '1'

      -- TODO: ¿Contruir el navbar sin una plantilla externa?
      local q = {""}
      q[#q+1] = 'text-align:left;'
      style.add(q, frame.args['estilo base'])
      style.add(q, frame.args['estilo título'])
      q[#q+1] = 'border:none;'
      args['fontstyle'] = table.concat(q)

      ret[#ret+1] = frame:expandTemplate{title = 'Navbar', args = args}
    else
      ret[#ret+1] = '&nbsp;'
      ret[#ret+1] = '[[Categoría:Navegaciones sin nombre]]'
    end
    ret[#ret+1] = '</div>'
  end

  -- el título comienza aquí
  ret[#ret+1] = '<span'
  if common.isset(frame.args['clase título']) then ret[#ret+1] = ' class="' .. frame.args['clase título'] .. '"' end
  ret[#ret+1] = ' style="font-size:' .. common.cv((border == "subgroup" or border == "child" or border == "none"), '100%', '110%') .. ';">\n'

  ret[#ret+1] = frame.args['título']
  
  ret[#ret+1] = '</span>'
  ret[#ret+1] = '</th></tr>'
  return table.concat(ret)
end

--[[ Genera las filas 'arriba' y 'abajo'.
     Regresa la fila generada y se actualiza el valor 'sep' (si es que la separación se va a necesitar). ]]
local function build_above_below(frame, border, ttype, text, rstyle, sep)
  local ret = {''}
  if common.isset(text) then
    if sep then -- si se necesita la separación
      table.insert(ret, '<tr style="height: 2px;"><td></td></tr>')
    end

    -- comienza la fila 'arriba' o 'abajo'
    ret[#ret+1] = '<tr>'
    ret[#ret+1] = '<td class="navbox-abovebelow" style="'
    if common.isset(colors[ttype]) then
      -- los parámetros arriba y abajo usan el mismo color por defecto de la tabla 'colores'
      ret[#ret+1] = 'background:' .. common.cv((border ~= "subgroup" and border ~= "child"), colors[ttype]['arriba'], colors[ttype]['grupo']) .. ';'
    end
    ret[#ret+1] = 'padding-left:1em;padding-right:1em;text-align:center;'
    style.add(ret, frame.args['estilo base'])
    style.add(ret, rstyle)
    ret[#ret+1] = '"'

    -- calcula y se agrega un colspan (el ancho de la tabla siempre están completo)
    local cs = 2
    if common.isset(frame.args['imagen izquierda']) then cs = cs + 1 end
    if common.isset(frame.args['imagen']) then cs = cs + 1 end
    ret[#ret+1] = ' colspan="' .. cs .. '">\n'

    -- el contenido de la fila
    ret[#ret+1] = text

    ret[#ret+1] = '</td></tr>'
    sep = true -- ahora se va usar una separación
  end
  return table.concat(ret),sep
end

--[[ Genera una sola fila de lista. ]]
local function build_row(frame, border, ttype, k, group, list, groupstyle, liststyle)
  local ret = {""}
  if common.isset(group) then
    -- como la lista actual tiene un nombre grupo, se agrega una celda para esta
    ret[#ret+1] = '<td class="navbox-group" style="'
    if common.isset(colors[ttype]) then 
      ret[#ret+1] = 'background:' .. common.cv((border ~= "subgroup" and border ~= "child"), colors[ttype]['grupo'], colors[ttype]['subgroup']) .. ';'
    end
    ret[#ret+1] = 'padding-left:1em; padding-right:1em; white-space:nowrap; text-align:right;'
    style.add(ret, frame.args['estilo base'])
    if common.isset(frame.args['ancho grupo']) then ret[#ret+1] = 'width:' .. frame.args['ancho grupo'] .. ';' end
    style.add(ret, frame.args['estilo grupo'])
    style.add(ret, groupstyle)
    ret[#ret+1] = '">'
    ret[#ret+1] = '<div style="padding:0;">\n'
    ret[#ret+1] = group
    ret[#ret+1] = '</div></td>'

    -- comienza la celda contenida
    ret[#ret+1] = '<td style="text-align:left;border-left-width:2px;border-left-style:solid;'
  else
    -- sin nombre grupo, comienza el contenido listo
    ret[#ret+1] = '<td colspan=2 style="'
  end
  -- un poco complicado, pero esto funciona como XOR
  if (frame.args['par impar'] == "swap") ~= ((k % 2) == 0) then
    if common.isset(colors[ttype]) then ret[#ret+1] = 'background:' .. colors[ttype]['dark'] .. ';' end
  else
    ret[#ret+1] = 'background:transparent;'
  end
  if not common.isset(frame.args['ancho grupo']) then ret[#ret+1] = 'width:100%;' end
  ret[#ret+1] = 'padding: 0;'
  style.add(ret, frame.args['estilo lista'])
  style.add(ret, common.cv((frame.args['par impar'] == "swap") ~= ((k % 2) == 0), frame.args['estilo par'], frame.args['estilo impar']))
  style.add(ret, liststyle)
  ret[#ret+1] = '" class="navbox-list ' .. common.cv((frame.args['par impar'] == "swap") ~= ((k % 2) == 0), 'navbox-even', 'navbox-odd') .. '">'

  -- agregue el <div> con contenido (el mismo <div> que la navegación child tiene cerrado cuando se inica la tabla)
  ret[#ret+1] = '<div style="padding:'
  if common.isset(frame.args['list' .. k .. 'padding']) then
    ret[#ret+1] = frame.args['list' .. k .. 'padding']
  elseif common.isset(frame.args['listpadding']) then
    ret[#ret+1] = frame.args['listpadding']
  else
    ret[#ret+1] = '0em 0.25em'
  end
  ret[#ret+1] = ';">\n'
  ret[#ret+1] = list
  ret[#ret+1] = '</div></td>'
  return table.concat(ret)
end

--[[ Genera el "cuerpo" de la tabla (todo debajo del título) ]]
local function build_body(frame, border, ttype, lists)
  local ret = {}
  local sep = common.isset(frame.args['título']) -- si hay un título, se agregamos una separación a la primera fila
  local imgs = true -- agrega imágenes si se presenta
  local irows = common.cv( (#lists > 0), (2 * #lists - 1), 1 )

  -- aff la fila 'arriba'
  ret[#ret+1],sep = build_above_below(frame, border, ttype, frame.args['arriba'], frame.args['estilo arriba'], sep)
  
  for k,v in pairs(lists) do
    if sep then -- add separation if needed
      ret[#ret+1] = '<tr style="height: 2px;"><td></td></tr>'
    end

    table.insert(ret, '<tr>')

    -- agrega una imagen a la parte izquierda (si aún no se agrega hasta ahora)
    if imgs then
      if common.isset(frame.args['imagen izquierda']) then
        ret[#ret+1] = '<td style="width:0%;padding:0px 2px 0px 0px;'
        style.add(ret, frame.args['estilo imagen izquierda'])
        ret[#ret+1] = '" rowspan="' .. irows .. '">' .. frame.args['imagen izquierda'] .. '</td>'
      end
    end

    -- agrega la contenido de la lista
    ret[#ret+1] = build_row(frame, border, ttype, v['índice'], v['grupo'], v['lista'], v['estilo grupo'], v['estilo lista'])

    -- agrega una imagen a la parte derecha (si aún no se agrega hasta ahora)
    if imgs then
      if common.isset(frame.args['imagen']) then
        ret[#ret+1] = '<td style="width:0%;padding:0px 0px 0px 2px;'
        style.add(ret, frame.args['estilo imagen'])
        ret[#ret+1] = '" rowspan="' .. irows .. '">' .. frame.args['imagen'] .. '</td>'
      end

      -- marca las imágenes que agregamos
      imgs = false
    end

    ret[#ret+1] = '</tr>'
    sep = true -- ahora la separación se va a utilizar por seguridad
  end

  ret[#ret+1],sep = build_above_below(frame, border, ttype, frame.args['abajo'], frame.args['estilo abajo'], sep)

  return table.concat(ret)
end

-- == Funciones exportadas ==
--[[ Genera y regresa toda la tabla de navegación. ]]
local function buildNavbox(frame)
  local template = {}
  local border = ""
  local ttype = ""

  if common.isset(frame.args['borde']) then
    border = frame.args['borde']
  elseif common.isset(frame.args[1]) then
    border = mw.text.trim(frame.args[1])
  end

  if common.isset(frame.args['tipo']) then
    ttype = frame.args['tipo']
  end

  -- TODO: ¿filtrar los valores de borde y tipo?

  -- prepara el primer dato
  local lists = scan_lists(frame)

  -- build the content  
  template[#template+1] = start_box(frame, border, ttype)
  template[#template+1] = build_title(frame, border, ttype)
  template[#template+1] = build_body(frame, border, ttype, lists)
  template[#template+1] = end_box(frame, border, ttype)

  return table.concat(template)
end

-- [[ Solución para la plantila "Navegación subgrupo" ]]
local function buildNavboxSubgroup(frame)
  if not common.isset(frame.args['borde']) then frame.args['borde'] = 'child' end

  -- TODO: ¿agregar/modificar/eliminar otros parámetros?

  -- llamar la función principal
  return buildNavbox(frame)
end

--[[ Principalmente para la depuración, se genera y regresa una tabla de conjuntos de colores
     y el valor en las celdas con colores usados como fondos. ]]
local function buildColorTable(frame)
  local ret = {""}
  table.insert(ret, '<table class="wikitable" style="text-align: center;">')
  table.insert(ret, '<caption>List of colors in order from darkest to brigthest one</caption>')
  table.insert(ret, '<tr><th>"tipo"</th><th>título</th><th>arriba, abajo</th><th>grupo,<br/>sub-above/below</th><th>sub-group</th><th>dark background</th><th>background</th></tr>')
  for k,v in pairs(colors) do
    table.insert(ret, '<tr>')
    table.insert(ret, '<td>' .. k .. '</td>')
    table.insert(ret, '<td style="background:' .. v['título'] .. '">' .. v['título'] .. '</td>')
    table.insert(ret, '<td style="background:' .. v['arriba'] .. '">' .. v['arriba'] .. '</td>')
    table.insert(ret, '<td style="background:' .. v['grupo'] .. '">' .. v['grupo'] .. '</td>')
    table.insert(ret, '<td style="background:' .. v['subgroup'] .. '">' .. v['subgroup'] .. '</td>')
    table.insert(ret, '<td style="background:' .. v['dark'] .. '">' .. v['dark'] .. '</td>')
    table.insert(ret, '<td style="background:' .. v['background'] .. '">' .. v['background'] .. '</td>')
    table.insert(ret, '</tr>')
  end
  table.insert(ret, '</table>')
  return table.concat(ret)
end

-- función de exportación local
return {
  ['construirnavegación'] = buildNavbox,
  ['construirplantillanavegación'] = function(frame) return buildNavbox(frame:getParent()) end, -- for wrappers
  ['construirsubgruponavegación'] = buildNavboxSubgroup,
  ['construirplantillasubgruponavegación'] = function(frame) return buildNavboxSubgroup(frame:getParent()) end, -- for wrappers
  ['contruirtablacolor'] = buildColorTable
}

-- [[Categoría:Scripts Lua|{{PAGENAME}}]]