Module:Catnav
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
For calling in template code use either
{{#invoke:Catnav|main|<named_argument_list>}}
or{{#invoke:Catnav|Q|<positional_argument_list>|<named_arguments_without_number_suffix>}}
(putting named arguments before positional ones is fine too if your layout benefits)
The following doc is extensive. Usually things should just work. If you want to use this module for a template, start by looking at the examples. This module is intended to save template authors from typing and repeating the same phrases over and over again. If you find a bug, keep calm and if you will not fix it yourself, report it to the discussion page.
First case | Second case | |||
---|---|---|---|---|
| ||||
named_argument_list is compatible with that of the legacy Template:Catnav, which served as an inspiration for this module. With default values given in braces and with X being replacable by a number in the range 0 to maxnum , the possible arguments are:
For a given number To determine the link target of an entry, the value of If Thereafter
For instance, with
|
All named arguments that end in X , as described left, should not be passed in this mode as they may be overwritten internally by positional argument processing. Other named arguments may be used. The default value for prefix is :Category: in this mode.
If no commons category (P373) could be found, then Qarg is used to set All positional arguments may be encased in simple wikitext formatting modifiers, i.e. The property identifier in
| |||
The sorting feature can be disabled explicitly using In |
Code
-- please report issues to the discussion page
-- if you will not fix them yourself
local titlenew = mw.title.new
function exists(w)
local t = titlenew(w, '')
return t and t.exists
end
function isRedirect(w)
local t = titlenew(w, '')
return t:getContent():find('.ategory ?.edirect') or t.isRedirect
end
-- mw.getCurrentFrame():callParserFunction('PAGENAME', w)
function pagename(w)
local t = titlenew(w, '')
return t and t.text
end
-- mw.title.new(w, ''):inNamespace(mw.site.namespaces.File.id)
function imgexists(w)
return w:find('File:', 1, true) == 1
or w:find('Image:', 1, true) == 1
end
-- parser called once and cached
isRTL = mw.getLanguage(mw.getCurrentFrame():callParserFunction('int', 'Lang')):isRTL()
-- flatten table entries of t to a string
function flatten(t)
local r = tbl()
for u, v in ipairs(t) do
u = { }
for i = 1, 10 do -- v may not be a sequential table
if v[i] and #v[i] > 0 then
r.ins(u, v[i])
end
end
if #u > 0 then
u = mw.text.split(r.cat(u, ' '):gsub('', '\8'), '')
for i = #u-1, 1, -1 do
u[i] = u[i] .. u[i+1]:sub(2)
end
u = mw.text.split(u[1], '\8')
for i = 2, #u do
u[i] = u[i-1]:sub(1, -2) .. u[i]
end
r:app(u[#u])
end
end
return r
end
function tbl(t)
t = t or { }
t.trc = function(_, x)
repeat until t:rm():find(x, 1, true)
return 1
end
t.app = function(...)
for i = 2, arg.n do
t[#t + 1] = arg[i]
end
end
t.cat = table.concat
t.ins = table.insert
t.rm = table.remove
return t
end
-- def(x) == nil if x is empty
function def(x, y)
return x and #x > 0 and x or y
end
function top(title, i, pfx, sfx, tpn)
local r = tbl{
'<div dir="', isRTL and 'rtl' or 'ltr',
'" class="catlinks catnav catnav_', def(tpn, ''):sub(10):gsub('[^%-%w\128-\255]+', '_'),
'" style="clear:none;display:table;font-size:88%;line-height:normal;margin:2px 0;padding:2px"><div style="display:table-cell;min-width:36em">',
}
if def(i.img) then
if imgexists(i.img) then
local wl = def(flatten{{ pfx, i.lnk, sfx }}[1], '')
i.lnk = exists(wl) and wl or exists(i.lnk) and i.lnk or ''
r:app(
'<div style="float:', i.aln, ';margin-', rvd(i.aln), ':2px;', i.stl,
'">[[', i.img, '|', i.wth, '|border|link=', i.lnk, ']]</div>'
)
else
r:app(i.img)
end
end
if def(title) then
local pn = pagename(flatten{{ pfx, title, sfx }}[1])
if pn then
r:app('<em>', pn, ' :</em> ')
end
end
return r:cat('')
end
function bottom()
return '<div style="clear:both"></div></div></div>'
end
local spA = '<span style="white-space:nowrap">'
local spZ = '</span>'
function _seq(class, css, c)
return not class and '</div>' or tbl{
'<div class="catnav_', class, '" style="', css[class],
def(c and c > 0 and ';' .. def(css['_' .. class], ''), ''),
'">',
}:cat('')
end
function row(aln, wth, pfx, sfx, all, css,
sep, disp, link, pref, suff, ticl, note, icon)
local r = tbl()
if disp or link then
local c, _l = 0
r.rwd = function(t, l)
if c > 1 and _l and isRedirect(_l) then
c = c - t:trc(spA)
end
_l = not all and l
end
r:app('', '', '') -- maybe seq, seqdata, sep
if link then
local wl = {}
for _p in mw.text.gsplit(pref or '', '|') do
for _s in mw.text.gsplit(suff or '', '|') do
if ticl then
wl[#wl+1] = { pfx, ticl, _p, link, _s, sfx }
end
wl[#wl+1] = { pfx, _p, link, _s, sfx }
end
end
for _, l in ipairs(flatten(wl)) do
if all or exists(l) then
c = c + 1
r:rwd(l)
r:app(spA, c > 1 and ' ≈ [[' or '[[')
aln = aln and #r + 1
r.aln = function(t, v)
t:ins(aln or #t + 1, v)
end
if icon then
r:aln(icon .. '|' .. wth .. '|link=' .. l)
end
if disp then
r:aln(icon and ']] [[' or '')
r:aln(l .. '|' .. disp)
end
r:app(']]', note or '', spZ)
end
end
r:rwd()
elseif disp then
r:app(spA, disp, note or '', spZ)
c = c + 1
end
local h, t = sep:seq_bounds()
local s = c > 0 and sep:get()
if h and h < 0 then
r[1] = _seq('seq', css)
end
if h then
r[2] = _seq('seqdata', css, h)
end
if s then
r[3] = s
end
if t then
r:app(_seq(), _seq())
end
elseif note then
if sep:seq_ahead() then
r:app(
_seq('seq', css),
_seq('seqlabel', css, sep:seq_ahead()),
css.__indent and (note:gsub('^ *<[Bb][Rr] */?>', '')) or note,
_seq())
else
r:app(note)
end
end
return r:cat('')
end
function _sep(sep, compact, omit)
local seqlabeled
local seq
local c = 0
return function(ld)
return {
get = function(_, r)
r = not omit and sep
omit = nil
return r
end,
seq_ahead = function()
seqlabeled = ld
return seqlabeled and c
end,
seq_bounds = function()
local h, t
if not seq then
h = seqlabeled and c or (-1 - c)
seq = true
omit = not compact or c == 0
end
if seq and not ld then
t = c
seqlabeled = nil
seq = nil
c = c + 1
end
return h, t
end,
}
end
end
function rvd(x)
return x:find('right', 1, true) and 'left' or 'right'
end
function use(x)
return x and #x > 0 and ('only once true yes 1 2'):find(x, 1, true) or nil
end
function named_args(_f)
local a = _f.args
local f = _f:getParent() or _f
if pairs(a)(a) == nil then -- if invoked without args
a = f.args -- take parent args, else take unset args only:
elseif _f ~= f then
for k, v in pairs(f.args) do
a[k] = a[k] or v
end
end
a.__art = def(a.article)
a.__art = not a.__art and tonumber(a.all) == 2 and 'the' or a.__art
a.__sep = def(a.sep) or ' <b>·</b> '
a.img = {
img = a.img or a.image,
aln = def(a.imgalign or a.imagealign, 'right'),
lnk = a.imglink or a.imagelink or a.title or '',
stl = def(a.imgstyle or a.imagestyle),
wth = def(a.imgwidth or a.imagewidth, '30px'),
}
a.iconsalign = def(a.iconsalign, rvd(a.img.aln))
a.iconswidth = def(a.iconswidth, '15px')
a.img.aln = isRTL and rvd(a.img.aln) or a.img.aln
f = {
__indent = not use(a.compact) and use(a.indent),
}
a.__css = f
if f.__indent then
local side = isRTL and 'left' or 'right'
f.seq = 'display:table-row;vertical-align:top'
f.seqlabel = 'display:table-cell;text-align:' .. side .. ';padding-' .. side .. ':.4em;white-space:nowrap'
f._seqlabel = 'padding-top:.2em'
f.seqdata = 'display:table-cell'
f._seqdata = 'padding-top:.2em'
else
f.seq = 'display:inline'
f.seqlabel = 'display:inline'
f.seqdata = 'display:inline'
end
return a
end
local QIDpattern = '%f[%w][Qq]%d+'
local getLabel = mw.wikibase.getLabel
function key(k)
return type(k) == 'string' and k
:gsub('<!%-%-(.-)%-%->', '') -- strip HTML/XML comments
:gsub('</?%s*([%a_][%-%.:%w_]*)[^>]*>', '') -- strip HTML/XML element tags (preserve text elements)
:gsub('^%s*(.-)%s*$', '%1') -- trim leading/trailing whitespaces
:gsub('%s+', ' ') -- pack remaining whitespaces
end
function number_suffixed_named_args(f, a, qc, tpn)
local r = tbl()
local o = use(a.compact)
local s = _sep(a.__sep, o)
local s0 = s()
local s1 = s(1)
local icons = use(a.icons)
local fuse_icons = not (icons == 1) or nil
local srt = fuse_icons and use(def(a.sort, '1'))
local srt_once = srt == 6 and o
local ttr = tpn and tpn .. '/i18n'
local tr = def
if ttr and exists(ttr) then
tr = function(x)
x = x and f:expandTemplate{
title = ttr,
args = { x }
}
return def(x)
end
local k = tr('__sort__') or ''
srt = (srt_once or #k == 8 or use(k)) and srt
end
local function Q(x, ex)
return x and x
:gsub('i18n{([^}]+)}', tr)
:gsub(QIDpattern,
function(m)
return ex and ex:find(m, 1, true) and m
or getLabel(m)
end)
end
local t = not srt and r or tbl{
srt = function(t, x)
if (not srt_once or srt_once and x) and #t > 0 then
table.sort(t,
function(x, y)
return y.key < x.key
end)
while #t > 0 do
r:ins(t:rm())
end
end
end,
}
local u = 1 + (qc or def(a.maxnum, 99))
for c = 0, u do
local i = icons and def(a['icon' .. c])
local d = def(a['display' .. c])
local l = def(a['link' .. c])
local dl = d or l
if d and imgexists(d)
then
i, d = d, nil
else
d = (fuse_icons or not i or nil) and Q(d or tr(l), not qc and l)
end
if r.n and (not o and dl or not dl) then
r:ins{ note = r.n, sep = s1, }
end
r.n = Q(def(a['note' .. c]))
if dl then
t:ins{
disp = d,
icon = i,
link = l,
key = srt and key(d) or c,
note = r.n,
sep = s1,
ticl = def(a['article' .. c], a.__art),
pref = a['prefix' .. c],
suff = a['suffix' .. c],
}
r.n = nil
else
if srt then
t:srt(c == u)
end
if r[#r] then
r[#r].sep = s0
end
end
end
return r
end
-- maintenance / tracker categories
function maintcat(tpn, r, t)
for k, v in pairs(t) do
if v and tpn then
r:app('[[Category:', tpn, ' maintenance/', k, ' users]]')
end
end
end
function _tpn(frame)
frame = frame:getParent()
local tpn = frame and frame:getTitle()
return def(tpn and tpn:find('Template:', 1, true) == 1 and tpn)
end
function main(frame, qc)
local tpn = _tpn(frame)
local a = named_args(frame)
local aln = a.iconsalign:find('right', 1, true)
local wth = a.iconswidth
local pfx = a.prefix
local sfx = a.suffix
local all = use(a.all or a.redlinks)
local css = a.__css
local r = tbl()
r:app(top(a.title, a.img, pfx, sfx, tpn))
for _, v in ipairs(number_suffixed_named_args(frame, a, qc, tpn)) do
r:app(
row(aln, wth, pfx, sfx, all, css,
v.sep, v.disp, v.link, v.pref, v.suff, v.ticl, v.note, v.icon
)
)
end
r:app(bottom())
maintcat(tpn, r, {
redlink = all,
--indent = a.__css.__indent,
})
return r:cat('')
end
function Q(frame)
local a = frame.args
local c = 1
local ip = def(a.icons) and (a.icons:match('^[0no]+[ly]*$') or '1') or ''
local ps = function(m, p)
for _, v in ipairs(mw.wikibase.getBestStatements(m, p)) do
v = v.mainsnak and v.mainsnak.datavalue
v = v and v.value
if v then
return v
end
end
end
a.icons, ip = ip, use(ip) and a.icons:match('^P%d+$')
a.prefix = a.prefix or ':Category:'
for i, q in ipairs(a) do
c = c + 1
if def(q) then
for m in q:gmatch(QIDpattern) do
-- commons cat
a['link' .. i], m = ps(m, 'P373'), ip and ps(m, ip)
a['icon' .. i] = m and 'File:' .. m
break
end
if a['link' .. i] then
a['display' .. i] = q -- q may contain wikitext
else
a['note' .. i] = q -- no wikidata id or no P373
end
end
end
return main(frame, c)
end
-- exported functions
return {
main = main,
Q = Q,
}