Строка 1: |
Строка 1: |
− | require('strict')
| + | -- |
| + | -- Реализует {{навигационная таблица}}, {{подгруппы навигационной таблицы}} и {{навигационная таблица с блоками}}. |
| + | -- Основной объём кода заимствован из английского Module:Navbox. |
| + | -- |
| + | |
| local p = {} | | local p = {} |
− | local navbar = require('Module:Navbar')._navbar
| + | |
− | local cfg = mw.loadData('Module:Navbox/configuration')
| |
| local getArgs -- lazily initialized | | local getArgs -- lazily initialized |
− | local args | + | local yesno -- lazily initialized |
− | local format = string.format | + | local styleratio |
| + | |
| + | local ODD_EVEN_MARKER = '\127_ODDEVEN_\127' |
| + | local RESTART_MARKER = '\127_ODDEVEN0_\127' |
| + | local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127' |
| + | |
| + | -- общие параметры для всех шаблонов |
| + | local commonAliases = { |
| + | name = {'name', 'имя'}, |
| + | navigation = {'navigation', 'навигация'}, |
| + | navbar = {'navbar', 'ссылка_на_просмотр'}, |
| + | state = {'state'}, |
| + | orphan = {'orphan'}, |
| + | tracking = {'tracking'}, |
| + | border = {'border', 1}, |
| + | title = {'title', 'заголовок'}, |
| + | titlegroup = {'titlegroup'}, |
| + | above = {'above', 'вверху'}, |
| + | image = {'image', 'изображение'}, |
| + | imageleft = {'imageleft', 'изображение2', 'изображение_слева'}, |
| + | below = {'below', 'внизу'}, |
| + | |
| + | bodyclass = {'bodyclass', 'класс_тела'}, |
| + | titleclass = {'titleclass', 'класс_заголовка'}, |
| + | titlegroupclass = {'titlegroupclass'}, |
| + | aboveclass = {'aboveclass', 'класс_вверху'}, |
| + | belowclass = {'belowclass', 'класс_внизу'}, |
| + | groupclass = {'groupclass', 'класс_групп'}, |
| + | listclass = {'listclass', 'класс_списков'}, |
| + | imageclass = {'imageclass', 'класс_изображения'}, |
| + | |
| + | basestyle = {'basestyle', 'стиль', 'стиль_базовый'}, |
| + | bodystyle = {'style', 'bodystyle', 'стиль_тела'}, |
| + | titlestyle = {'titlestyle', 'стиль_основного_заголовка', 'стиль_заголовка'}, |
| + | titlegroupstyle = {'titlegroupstyle'}, |
| + | innerstyle = {'innerstyle'}, |
| + | abovestyle = {'abovestyle', 'стиль_вверху'}, |
| + | belowstyle = {'belowstyle', 'стиль_внизу'}, |
| + | imagestyle = {'imagestyle', 'стиль_изображения'}, |
| + | imageleftstyle = {'imageleftstyle', 'imagestyle2', 'стиль_изображения_слева'}, |
| + | } |
| + | |
| + | -- параметры {{навигационная таблица}} и {{подгруппы навигационной таблицы}} |
| + | local standardAliases = { |
| + | groupstyle = {'groupstyle', 'стиль_заголовков', 'стиль_групп'}, |
| + | liststyle = {'liststyle', 'стиль_списков'}, |
| + | evenodd = {'evenodd', 'чётные_нечётные', 'четные_нечетные'}, |
| + | groupwidth = {'groupwidth', 'ширина_групп'}, |
| + | listpadding = {'listpadding', 'отступ_списков'}, |
| + | } |
| + | |
| + | -- параметры {{навигационная таблица}} и {{подгруппы навигационной таблицы}} с нумерацией |
| + | local standardElementAliases = { |
| + | group = {'group%s', 'заголовок%s', 'группа%s'}, |
| + | list = {'list%s', 'список%s'}, |
| + | groupstyle = {'group%sstyle', 'стиль_заголовка%s', 'стиль_группы%s'}, |
| + | listclass = {'list%sclass', 'класс%sсписка', 'класс_списка%s'}, |
| + | liststyle = {'list%sstyle', 'стиль_списка%s'}, |
| + | listpadding = {'list%spadding'} |
| + | } |
| + | |
| + | -- параметры {{навигационная таблица с блоками}} |
| + | -- с нижнего подчеркивания начинаются параметры, конфликтующие с standardAliases |
| + | local groupsParentAliases = { |
| + | selected = {'selected', 'открытый_блок', 'развернуть'}, |
| + | secttitlestyle = {'secttitlestyle', 'стиль_заголовков'}, |
| + | _groupstyle = {'groupstyle', 'стиль_блоков'}, |
| + | _liststyle = {'liststyle', 'стиль_списков', 'contentstyle'}, |
| + | _listpadding = {'listpadding', 'отступ_списка', 'отступ_списков'} |
| + | } |
| + | |
| + | -- параметры {{навигационная таблица с блоками}} с нумерацией |
| + | local groupsChildAliases = { |
| + | groupname = {'abbr%s', 'имя_блока%s', 'аббр%s'}, |
| + | state = {'state%s'}, |
| + | title = {'group%s', 'блок%s', 'заголовок%s', 'группа%s', 'sect%s', 'section%s', 'секция%s'}, |
| + | list1 = {'list%s', 'список%s', 'content%s'}, |
| + | image = {'image%s', 'изображение%s'}, |
| + | imageleft = {'imageleft%s', 'изображение_слева%s'}, |
| + | |
| + | secttitlestyle = {'sect%stitlestyle', 'стиль%sзаголовка', 'стиль_секции%s'}, |
| + | groupstyle = {'group%sstyle', 'стиль%sблока', 'стиль_группы%s', 'стиль_блока%s'}, |
| + | listclass = {'list%sclass', 'класс%sсписка', 'класс_списка%s'}, |
| + | liststyle = {'list%sstyle', 'стиль%sсписка', 'стиль_списка%s', 'content%sstyle'}, |
| + | color = {'цвет%s'} |
| + | } |
| | | |
− | local function striped(wikitext, border) | + | local function checkAliases(args, aliases, index) |
| + | for _, alias in ipairs(aliases) do |
| + | local arg |
| + | if index then |
| + | arg = args[string.format(alias, index)] |
| + | else |
| + | arg = args[alias] |
| + | end |
| + | |
| + | if arg then |
| + | return arg |
| + | end |
| + | end |
| + | |
| + | return nil |
| + | end |
| + | |
| + | local function checkElAliases(args, name, index) |
| + | return checkAliases(args, standardElementAliases[name], index) |
| + | end |
| + | |
| + | local function concatStyles(t) |
| + | local res |
| + | for k, v in pairs(t) do |
| + | if v then |
| + | res = res and res .. ';' .. v or v |
| + | end |
| + | end |
| + | return res |
| + | end |
| + | |
| + | local function striped(wikitext, args) |
| -- Return wikitext with markers replaced for odd/even striping. | | -- Return wikitext with markers replaced for odd/even striping. |
| -- Child (subgroup) navboxes are flagged with a category that is removed | | -- Child (subgroup) navboxes are flagged with a category that is removed |
| -- by parent navboxes. The result is that the category shows all pages | | -- by parent navboxes. The result is that the category shows all pages |
| -- where a child navbox is not contained in a parent navbox. | | -- where a child navbox is not contained in a parent navbox. |
− | local orphanCat = cfg.category.orphan | + | local orphanCat = '[[Категория:Навигационные шаблоны без родителя]]' |
− | if border == cfg.keyword.border_subgroup and args[cfg.arg.orphan] ~= cfg.keyword.orphan_yes then | + | if args.border == 'subgroup' and args.orphan ~= 'yes' then |
| -- No change; striping occurs in outermost navbox. | | -- No change; striping occurs in outermost navbox. |
| return wikitext .. orphanCat | | return wikitext .. orphanCat |
| end | | end |
− | local first, second = cfg.class.navbox_odd_part, cfg.class.navbox_even_part | + | local first, second = 'odd', 'even' |
− | if args[cfg.arg.evenodd] then | + | if args.evenodd then |
− | if args[cfg.arg.evenodd] == cfg.keyword.evenodd_swap then | + | if args.evenodd == 'swap' then |
| first, second = second, first | | first, second = second, first |
| else | | else |
− | first = args[cfg.arg.evenodd] | + | first = args.evenodd |
| second = first | | second = first |
| end | | end |
Строка 45: |
Строка 164: |
| end | | end |
| local regex = orphanCat:gsub('([%[%]])', '%%%1') | | local regex = orphanCat:gsub('([%[%]])', '%%%1') |
− | return (wikitext:gsub(regex, ''):gsub(cfg.marker.regex, changer)) -- () omits gsub count | + | return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer)) -- () omits gsub count |
| end | | end |
| | | |
− | local function processItem(item, nowrapitems) | + | local function addNewline(s) |
− | if item:sub(1, 2) == '{|' then | + | if s:match('^[*:;#]') or s:match('^{|') then |
− | -- Applying nowrap to lines in a table does not make sense.
| + | return '\n' .. s ..'\n' |
− | -- Add newlines to compensate for trim of x in |parm=x in a template.
| + | else |
− | return '\n' .. item ..'\n' | + | return s |
| end | | end |
− | if nowrapitems == cfg.keyword.nowrapitems_yes then | + | end |
− | local lines = {}
| + | |
− | for line in (item .. '\n'):gmatch('([^\n]*)\n') do | + | local function renderNavBar(titleCell, args) |
− | local prefix, content = line:match('^([*:;#]+)%s*(.*)')
| + | local currentFrame = mw.getCurrentFrame() |
− | if prefix and not content:match(cfg.pattern.nowrap) then
| + | if args.navbar ~= 'off' and args.navbar ~= 'plain' |
− | line = format(cfg.nowrap_item, prefix, content)
| + | and (args.name or not currentFrame:getParent():getTitle():gsub('/песочница$', '') == 'Шаблон:Навигационная таблица') then |
− | end
| + | |
− | table.insert(lines, line)
| + | -- Check color contrast of the gear icon |
| + | if not styleratio then |
| + | styleratio = require('Module:Color contrast')._styleratio |
| end | | end |
− | item = table.concat(lines, '\n') | + | local contrastStyle = args.titlestyle or args.basestyle |
| + | local gearStyleBlack = (contrastStyle and mw.text.unstripNoWiki(contrastStyle) .. '; color:#666;' or '') |
| + | local gearStyleWhite = (contrastStyle and mw.text.unstripNoWiki(contrastStyle) .. '; color:#fff;' or '') |
| + | local gear = currentFrame:expandTemplate{ |
| + | title = 'Tnavbar-view', |
| + | args = { |
| + | args.name, |
| + | fontcolor = (styleratio{gearStyleBlack} < styleratio{gearStyleWhite}) and 'white', |
| + | } |
| + | } |
| + | |
| + | --- Gear creation |
| + | titleCell |
| + | :tag('span') |
| + | :addClass('navbox-gear') |
| + | :css('float', 'left') |
| + | :css('text-align', 'left') |
| + | :css('width', '5em') |
| + | :css('margin-right', '0.5em') |
| + | :wikitext(gear) |
| end | | end |
− | if item:match('^[*:;#]') then
| |
− | return '\n' .. item ..'\n'
| |
− | end
| |
− | return item
| |
− | end
| |
| | | |
− | local function has_navbar()
| |
− | return args[cfg.arg.navbar] ~= cfg.keyword.navbar_off
| |
− | and args[cfg.arg.navbar] ~= cfg.keyword.navbar_plain
| |
− | and (
| |
− | args[cfg.arg.name]
| |
− | or mw.getCurrentFrame():getParent():getTitle():gsub(cfg.pattern.sandbox, '')
| |
− | ~= cfg.pattern.navbox
| |
− | )
| |
| end | | end |
| | | |
− | local function renderNavBar(titleCell) | + | -- |
− | if has_navbar() then | + | -- Title row |
− | titleCell:wikitext(navbar{
| + | -- |
− | [cfg.navbar.name] = args[cfg.arg.name],
| + | local function renderTitleRow(tbl, args) |
− | [cfg.navbar.mini] = 1,
| + | if not args.title then return end |
− | [cfg.navbar.fontstyle] = (args[cfg.arg.basestyle] or '') .. ';' ..
| |
− | (args[cfg.arg.titlestyle] or '') ..
| |
− | ';background:none transparent;border:none;box-shadow:none;padding:0;'
| |
− | })
| |
− | end
| |
| | | |
− | end
| + | local titleRow = tbl:tag('tr') |
| | | |
− | local function renderTitleRow(tbl)
| + | if args.titlegroup then |
− | if not args[cfg.arg.title] then return end
| + | titleRow |
| + | :tag('th') |
| + | :attr('scope', 'row') |
| + | :addClass('navbox-group') |
| + | :addClass(args.titlegroupclass) |
| + | :cssText(args.basestyle) |
| + | :cssText(args.groupstyle) |
| + | :cssText(args.titlegroupstyle) |
| + | :wikitext(args.titlegroup) |
| + | end |
| | | |
− | local titleRow = tbl:tag('tr') | + | local titleCell = titleRow:tag('th'):attr('scope', 'colgroup') |
| | | |
− | local titleCell = titleRow:tag('th'):attr('scope', 'col') | + | if args.titlegroup then |
| + | titleCell |
| + | :css('border-left', '2px solid #fdfdfd') |
| + | :css('width', '100%') |
| + | end |
| | | |
| local titleColspan = 2 | | local titleColspan = 2 |
− | if args[cfg.arg.imageleft] then titleColspan = titleColspan + 1 end | + | if args.imageleft then titleColspan = titleColspan + 1 end |
− | if args[cfg.arg.image] then titleColspan = titleColspan + 1 end | + | if args.image then titleColspan = titleColspan + 1 end |
| + | if args.titlegroup then titleColspan = titleColspan - 1 end |
| | | |
| titleCell | | titleCell |
− | :cssText(args[cfg.arg.basestyle]) | + | :cssText(args.basestyle) |
− | :cssText(args[cfg.arg.titlestyle]) | + | :cssText(args.titlestyle) |
− | :addClass(cfg.class.navbox_title) | + | :addClass('navbox-title') |
| :attr('colspan', titleColspan) | | :attr('colspan', titleColspan) |
| | | |
− | renderNavBar(titleCell) | + | renderNavBar(titleCell, args) |
| | | |
| titleCell | | titleCell |
| :tag('div') | | :tag('div') |
− | -- id for aria-labelledby attribute
| + | :attr('id', mw.uri.anchorEncode(args.title)) |
− | :attr('id', mw.uri.anchorEncode(args[cfg.arg.title])) | + | :addClass(args.titleclass) |
− | :addClass(args[cfg.arg.titleclass]) | |
| :css('font-size', '114%') | | :css('font-size', '114%') |
− | :css('margin', '0 4em') | + | :css('margin', '0 5em') |
− | :wikitext(processItem(args[cfg.arg.title])) | + | :wikitext(addNewline(args.title)) |
| end | | end |
| | | |
− | local function getAboveBelowColspan() | + | -- |
| + | -- Above/Below rows |
| + | -- |
| + | |
| + | local function getAboveBelowColspan(args) |
| local ret = 2 | | local ret = 2 |
− | if args[cfg.arg.imageleft] then ret = ret + 1 end | + | if args.imageleft then ret = ret + 1 end |
− | if args[cfg.arg.image] then ret = ret + 1 end | + | if args.image then ret = ret + 1 end |
| return ret | | return ret |
| end | | end |
| | | |
− | local function renderAboveRow(tbl) | + | local function renderAboveRow(tbl, args) |
− | if not args[cfg.arg.above] then return end | + | if not args.above then return end |
| | | |
| tbl:tag('tr') | | tbl:tag('tr') |
| :tag('td') | | :tag('td') |
− | :addClass(cfg.class.navbox_abovebelow) | + | :addClass('navbox-abovebelow') |
− | :addClass(args[cfg.arg.aboveclass]) | + | :addClass(args.aboveclass) |
− | :cssText(args[cfg.arg.basestyle]) | + | :cssText(args.basestyle) |
− | :cssText(args[cfg.arg.abovestyle]) | + | :cssText(args.abovestyle) |
− | :attr('colspan', getAboveBelowColspan()) | + | :attr('colspan', getAboveBelowColspan(args)) |
| :tag('div') | | :tag('div') |
− | -- id for aria-labelledby attribute, if no title
| + | :wikitext(addNewline(args.above)) |
− | :attr('id', (not args[cfg.arg.title]) and mw.uri.anchorEncode(args[cfg.arg.above]) or nil)
| |
− | :wikitext(processItem(args[cfg.arg.above], args[cfg.arg.nowrapitems])) | |
| end | | end |
| | | |
− | local function renderBelowRow(tbl) | + | local function renderBelowRow(tbl, args) |
− | if not args[cfg.arg.below] then return end | + | if not args.below then return end |
| | | |
| tbl:tag('tr') | | tbl:tag('tr') |
| :tag('td') | | :tag('td') |
− | :addClass(cfg.class.navbox_abovebelow) | + | :addClass('navbox-abovebelow') |
− | :addClass(args[cfg.arg.belowclass]) | + | :addClass(args.belowclass) |
− | :cssText(args[cfg.arg.basestyle]) | + | :cssText(args.basestyle) |
− | :cssText(args[cfg.arg.belowstyle]) | + | :cssText(args.belowstyle) |
− | :attr('colspan', getAboveBelowColspan()) | + | :attr('colspan', getAboveBelowColspan(args)) |
| :tag('div') | | :tag('div') |
− | :wikitext(processItem(args[cfg.arg.below], args[cfg.arg.nowrapitems])) | + | :wikitext(addNewline(args.below)) |
| + | end |
| + | |
| + | -- |
| + | -- List rows |
| + | -- |
| + | |
| + | local function haveSubgroups(args) |
| + | for i = 1, 23 do |
| + | if checkElAliases(args, 'group', i) and checkElAliases(args, 'list', i) then |
| + | return true |
| + | end |
| + | end |
| + | return false |
| end | | end |
| | | |
− | local function renderListRow(tbl, index, listnum, listnums_size) | + | local function renderListRow(tbl, args, index, rowspan, rowArgs) |
| local row = tbl:tag('tr') | | local row = tbl:tag('tr') |
| | | |
− | if index == 1 and args[cfg.arg.imageleft] then | + | if index == 1 and args.imageleft then |
| row | | row |
| :tag('td') | | :tag('td') |
− | :addClass(cfg.class.noviewer) | + | :addClass('navbox-image') |
− | :addClass(cfg.class.navbox_image)
| + | :addClass(args.imageclass) |
− | :addClass(args[cfg.arg.imageclass]) | + | :css('width', '1px') |
− | :css('width', '1px') -- Minimize width | + | :css('padding', '0px 7px 0px 0px') |
− | :css('padding', '0 2px 0 0') | + | :cssText(args.imageleftstyle) |
− | :cssText(args[cfg.arg.imageleftstyle]) | + | :attr('rowspan', rowspan) |
− | :attr('rowspan', listnums_size) | |
| :tag('div') | | :tag('div') |
− | :wikitext(processItem(args[cfg.arg.imageleft])) | + | :wikitext(addNewline(args.imageleft)) |
| end | | end |
| | | |
− | local group_and_num = format(cfg.arg.group_and_num, listnum) | + | if rowArgs.group then |
− | local groupstyle_and_num = format(cfg.arg.groupstyle_and_num, listnum)
| |
− | if args[group_and_num] then
| |
| local groupCell = row:tag('th') | | local groupCell = row:tag('th') |
− |
| |
− | -- id for aria-labelledby attribute, if lone group with no title or above
| |
− | if listnum == 1 and not (args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group2]) then
| |
− | groupCell
| |
− | :attr('id', mw.uri.anchorEncode(args[cfg.arg.group1]))
| |
− | end
| |
| | | |
| groupCell | | groupCell |
| :attr('scope', 'row') | | :attr('scope', 'row') |
− | :addClass(cfg.class.navbox_group) | + | :addClass('navbox-group') |
− | :addClass(args[cfg.arg.groupclass]) | + | :addClass(args.groupclass) |
− | :cssText(args[cfg.arg.basestyle]) | + | :cssText(args.basestyle) |
− | -- If groupwidth not specified, minimize width
| + | :css('width', args.groupwidth or '1px') -- If groupwidth not specified, minimize width |
− | :css('width', args[cfg.arg.groupwidth] or '1%') | |
| | | |
| groupCell | | groupCell |
− | :cssText(args[cfg.arg.groupstyle]) | + | :cssText(args.groupstyle) |
− | :cssText(args[groupstyle_and_num]) | + | :cssText(rowArgs.groupstyle) |
− | :wikitext(args[group_and_num]) | + | :wikitext(rowArgs.group) |
| end | | end |
| | | |
| local listCell = row:tag('td') | | local listCell = row:tag('td') |
| | | |
− | if args[group_and_num] then | + | if rowArgs.group then |
| listCell | | listCell |
− | :addClass(cfg.class.navbox_list_with_group) | + | :css('text-align', 'left') |
| + | :css('border-left-width', '2px') |
| + | :css('border-left-style', 'solid') |
| else | | else |
− | listCell:attr('colspan', 2) | + | if haveSubgroups(args) then |
| + | listCell |
| + | :attr('colspan', 2) |
| + | end |
| end | | end |
| | | |
− | if not args[cfg.arg.groupwidth] then | + | if not args.groupwidth then |
| listCell:css('width', '100%') | | listCell:css('width', '100%') |
| end | | end |
| | | |
− | local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing | + | local listText = rowArgs.list |
− | if index % 2 == 1 then
| + | local oddEven = ODD_EVEN_MARKER |
− | rowstyle = args[cfg.arg.oddstyle]
| |
− | else
| |
− | rowstyle = args[cfg.arg.evenstyle]
| |
− | end
| |
− | | |
− | local list_and_num = format(cfg.arg.list_and_num, listnum)
| |
− | local listText = args[list_and_num]
| |
− | local oddEven = cfg.marker.oddeven | |
| if listText:sub(1, 12) == '</div><table' then | | if listText:sub(1, 12) == '</div><table' then |
| -- Assume list text is for a subgroup navbox so no automatic striping for this row. | | -- Assume list text is for a subgroup navbox so no automatic striping for this row. |
− | oddEven = listText:find(cfg.pattern.navbox_title) and cfg.marker.restart or cfg.class.navbox_odd_part | + | oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd' |
| end | | end |
− |
| |
− | local liststyle_and_num = format(cfg.arg.liststyle_and_num, listnum)
| |
− | local listclass_and_num = format(cfg.arg.listclass_and_num, listnum)
| |
| listCell | | listCell |
− | :css('padding', '0') | + | :css('padding', '0px') |
− | :cssText(args[cfg.arg.liststyle]) | + | :cssText(args.liststyle) |
− | :cssText(rowstyle) | + | :cssText(rowArgs.liststyle) |
− | :cssText(args[liststyle_and_num])
| + | :addClass('navbox-list') |
− | :addClass(cfg.class.navbox_list) | + | :addClass('navbox-' .. oddEven) |
− | :addClass(cfg.class.navbox_part .. oddEven) | + | :addClass(args.listclass) |
− | :addClass(args[cfg.arg.listclass]) | + | :addClass(rowArgs.listclass) |
− | :addClass(args[listclass_and_num]) | |
| :tag('div') | | :tag('div') |
− | :css('padding', | + | :css('padding', rowArgs.listpadding or args.listpadding or '0em 0.25em') |
− | (index == 1 and args[cfg.arg.list1padding]) or args[cfg.arg.listpadding] or '0 0.25em'
| + | :wikitext(addNewline(listText)) |
− | )
| |
− | :wikitext(processItem(listText, args[cfg.arg.nowrapitems])) | |
| | | |
− | if index == 1 and args[cfg.arg.image] then | + | if index == 1 and args.image then |
| row | | row |
| :tag('td') | | :tag('td') |
− | :addClass(cfg.class.noviewer) | + | :addClass('navbox-image') |
− | :addClass(cfg.class.navbox_image)
| + | :addClass(args.imageclass) |
− | :addClass(args[cfg.arg.imageclass]) | + | :css('width', '1px') |
− | :css('width', '1px') -- Minimize width | + | :css('padding', '0px 0px 0px 7px') |
− | :css('padding', '0 0 0 2px') | + | :cssText(args.imagestyle) |
− | :cssText(args[cfg.arg.imagestyle]) | + | :attr('rowspan', rowspan) |
− | :attr('rowspan', listnums_size) | |
| :tag('div') | | :tag('div') |
− | :wikitext(processItem(args[cfg.arg.image])) | + | :wikitext(addNewline(args.image)) |
| end | | end |
| end | | end |
| | | |
− | local function has_list_class(htmlclass) | + | -- |
− | local patterns = { | + | -- Tracking categories |
− | '^' .. htmlclass .. '$',
| + | -- |
− | '%s' .. htmlclass .. '$', | + | local function needsChangetoSubgroups(args) |
− | '^' .. htmlclass .. '%s',
| + | for i = 1, 23 do |
− | '%s' .. htmlclass .. '%s'
| + | if (checkElAliases(args, 'group', i)) and not (checkElAliases(args, 'list', i)) then |
− | }
| + | return true |
− |
| |
− | for arg, _ in pairs(args) do
| |
− | if type(arg) == 'string' and mw.ustring.find(arg, cfg.pattern.class) then
| |
− | for _, pattern in ipairs(patterns) do
| |
− | if mw.ustring.find(args[arg] or '', pattern) then
| |
− | return true
| |
− | end
| |
− | end
| |
| end | | end |
| end | | end |
Строка 282: |
Строка 404: |
| end | | end |
| | | |
− | -- there are a lot of list classes in the wild, so we add their TemplateStyles
| + | local function needsHorizontalLists(args) |
− | local function add_list_styles() | + | if args.border == 'subgroup' or args.tracking == 'no' then |
− | local frame = mw.getCurrentFrame() | |
− | local function add_list_templatestyles(htmlclass, templatestyles)
| |
− | if has_list_class(htmlclass) then
| |
− | return frame:extensionTag{
| |
− | name = 'templatestyles', args = { src = templatestyles }
| |
− | }
| |
− | else
| |
− | return ''
| |
− | end
| |
− | end
| |
− |
| |
− | local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)
| |
− | local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)
| |
− |
| |
− | -- a second workaround for [[phab:T303378]]
| |
− | -- when that issue is fixed, we can actually use has_navbar not to emit the
| |
− | -- tag here if we want
| |
− | if has_navbar() and hlist_styles == '' then
| |
− | hlist_styles = frame:extensionTag{
| |
− | name = 'templatestyles', args = { src = cfg.hlist_templatestyles }
| |
− | }
| |
− | end
| |
− |
| |
− | -- hlist -> plainlist is best-effort to preserve old Common.css ordering.
| |
− | -- this ordering is not a guarantee because most navboxes will emit only
| |
− | -- one of these classes [hlist_note]
| |
− | return hlist_styles .. plainlist_styles
| |
− | end
| |
− | | |
− | local function needsHorizontalLists(border)
| |
− | if border == cfg.keyword.border_subgroup or args[cfg.arg.tracking] == cfg.keyword.tracking_no then
| |
| return false | | return false |
| end | | end |
− | return not has_list_class(cfg.pattern.hlist) and not has_list_class(cfg.pattern.plainlist) | + | local listClasses = { |
| + | ['plainlist'] = true, ['hlist'] = true, ['hlist hnum'] = true, |
| + | ['hlist hwrap'] = true, ['hlist vcard'] = true, ['vcard hlist'] = true, |
| + | ['hlist vevent'] = true, ['hlist hlist-items-nowrap'] = true, ['hlist-items-nowrap'] = true, |
| + | } |
| + | return not (listClasses[args.listclass] or listClasses[args.bodyclass]) |
| end | | end |
| | | |
− | local function hasBackgroundColors() | + | -- local function hasBackgroundColors() |
− | for _, key in ipairs({cfg.arg.titlestyle, cfg.arg.groupstyle,
| + | -- return mw.ustring.match(titlestyle or '','background') or mw.ustring.match(groupstyle or '','background') or mw.ustring.match(basestyle or '','background') |
− | cfg.arg.basestyle, cfg.arg.abovestyle, cfg.arg.belowstyle}) do
| + | -- end |
− | if tostring(args[key]):find('background', 1, true) then
| |
− | return true
| |
− | end
| |
− | end
| |
− | return false
| |
− | end | |
| | | |
− | local function hasBorders() | + | local function isIllegible(args) |
− | for _, key in ipairs({cfg.arg.groupstyle, cfg.arg.basestyle, | + | if not styleratio then |
− | cfg.arg.abovestyle, cfg.arg.belowstyle}) do
| + | styleratio = require('Module:Color contrast')._styleratio |
− | if tostring(args[key]):find('border', 1, true) then | |
− | return true
| |
− | end
| |
| end | | end |
− | return false
| |
− | end
| |
| | | |
− | local function isIllegible()
| |
− | local styleratio = require('Module:Color contrast')._styleratio
| |
| for key, style in pairs(args) do | | for key, style in pairs(args) do |
− | if tostring(key):match(cfg.pattern.style) then | + | if tostring(key):match("style$") or tostring(key):match("^стиль") then |
| if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then | | if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then |
− | return true | + | return true |
| end | | end |
| end | | end |
Строка 352: |
Строка 435: |
| end | | end |
| | | |
− | local function getTrackingCategories(border) | + | local function getTrackingCategories(args) |
| local cats = {} | | local cats = {} |
− | if needsHorizontalLists(border) then table.insert(cats, cfg.category.horizontal_lists) end | + | if needsChangetoSubgroups(args) then table.insert(cats, 'Навигационные шаблоны с ошибочным использованием заголовков') end |
− | if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end | + | if needsHorizontalLists(args) then table.insert(cats, 'Навигационные шаблоны без горизонтальных списков') end |
− | if isIllegible() then table.insert(cats, cfg.category.illegible) end | + | if isIllegible(args) then table.insert(cats, 'Потенциально нечитаемые навигационные шаблоны') end |
− | if hasBorders() then table.insert(cats, cfg.category.borders) end
| |
| return cats | | return cats |
| end | | end |
| | | |
− | local function renderTrackingCategories(builder, border) | + | local function renderTrackingCategories(builder, args) |
| local title = mw.title.getCurrentTitle() | | local title = mw.title.getCurrentTitle() |
| if title.namespace ~= 10 then return end -- not in template space | | if title.namespace ~= 10 then return end -- not in template space |
| local subpage = title.subpageText | | local subpage = title.subpageText |
− | if subpage == cfg.keyword.subpage_doc or subpage == cfg.keyword.subpage_sandbox | + | if subpage == 'doc' or subpage == 'песочница' or subpage == 'тесты' then return end |
− | or subpage == cfg.keyword.subpage_testcases then return end
| |
| | | |
− | for _, cat in ipairs(getTrackingCategories(border)) do | + | for i, cat in ipairs(getTrackingCategories(args)) do |
− | builder:wikitext('[[Category:' .. cat .. ']]') | + | builder:wikitext('[[Категория:' .. cat .. ']]') |
| end | | end |
| end | | end |
| | | |
− | local function renderMainTable(border, listnums) | + | -- |
| + | -- Main navbox tables |
| + | -- |
| + | local function renderMainTable(args, listnums) |
| local tbl = mw.html.create('table') | | local tbl = mw.html.create('table') |
− | :addClass(cfg.class.nowraplinks) | + | :addClass('nowraplinks') |
− | :addClass(args[cfg.arg.bodyclass]) | + | :addClass(args.bodyclass) |
| | | |
− | local state = args[cfg.arg.state]
| + | if args.title and (args.state ~= 'plain' and args.state ~= 'off') then |
− | if args[cfg.arg.title] and state ~= cfg.keyword.state_plain and state ~= cfg.keyword.state_off then | |
− | if state == cfg.keyword.state_collapsed then
| |
− | state = cfg.class.collapsed
| |
− | end
| |
| tbl | | tbl |
− | :addClass(cfg.class.collapsible) | + | :addClass('collapsible') |
− | :addClass(state or cfg.class.autocollapse) | + | :addClass(args.state or 'autocollapse') |
| end | | end |
| | | |
| tbl:css('border-spacing', 0) | | tbl:css('border-spacing', 0) |
− | if border == cfg.keyword.border_subgroup or border == cfg.keyword.border_none then | + | if args.border == 'subgroup' or args.border == 'none' then |
| tbl | | tbl |
− | :addClass(cfg.class.navbox_subgroup) | + | :addClass('navbox-subgroup') |
− | :cssText(args[cfg.arg.bodystyle]) | + | :cssText(args.bodystyle) |
− | :cssText(args[cfg.arg.style])
| + | else -- regular navbox - bodystyle and style will be applied to the wrapper table |
− | else -- regular navbox - bodystyle and style will be applied to the wrapper table | |
| tbl | | tbl |
− | :addClass(cfg.class.navbox_inner) | + | :addClass('navbox-inner') |
| :css('background', 'transparent') | | :css('background', 'transparent') |
| :css('color', 'inherit') | | :css('color', 'inherit') |
| end | | end |
− | tbl:cssText(args[cfg.arg.innerstyle]) | + | tbl:cssText(args.innerstyle) |
| | | |
− | renderTitleRow(tbl) | + | renderTitleRow(tbl, args) |
− | renderAboveRow(tbl) | + | renderAboveRow(tbl, args) |
− | local listnums_size = #listnums
| |
| for i, listnum in ipairs(listnums) do | | for i, listnum in ipairs(listnums) do |
− | renderListRow(tbl, i, listnum, listnums_size) | + | local rowArgs = { |
| + | group = checkElAliases(args, 'group', listnum), |
| + | list = checkElAliases(args, 'list', listnum), |
| + | groupstyle = checkElAliases(args, 'groupstyle', listnum), |
| + | listclass = checkElAliases(args, 'listclass', listnum), |
| + | liststyle = checkElAliases(args, 'liststyle', listnum), |
| + | listpadding = checkElAliases(args, 'listpadding', listnum) |
| + | } |
| + | renderListRow(tbl, args, i, #listnums, rowArgs) |
| end | | end |
− | renderBelowRow(tbl) | + | renderBelowRow(tbl, args) |
| | | |
| return tbl | | return tbl |
| end | | end |
| | | |
− | local function add_navbox_styles(hiding_templatestyles) | + | -- Read the arguments in the order they'll be output in, to make references number in the right order. |
− | local frame = mw.getCurrentFrame() | + | local function readInTheRightOrder(args, groupAliases, listAliases) |
− | -- This is a lambda so that it doesn't need the frame as a parameter | + | local _ |
− | local function add_user_styles(templatestyles)
| + | _ = checkAliases(args, commonAliases.title) |
− | if templatestyles and templatestyles ~= '' then
| + | _ = checkAliases(args, commonAliases.above) |
− | return frame:extensionTag{
| + | for i = 1, 23 do |
− | name = 'templatestyles', args = { src = templatestyles }
| + | _ = checkAliases(args, groupAliases, i) |
− | }
| + | _ = checkAliases(args, listAliases, i) |
− | end | |
− | return ''
| |
| end | | end |
− | | + | _ = checkAliases(args, commonAliases.below) |
− | -- get templatestyles. load base from config so that Lua only needs to do | |
− | -- the work once of parser tag expansion
| |
− | local base_templatestyles = cfg.templatestyles
| |
− | local templatestyles = add_user_styles(args[cfg.arg.templatestyles])
| |
− | local child_templatestyles = add_user_styles(args[cfg.arg.child_templatestyles])
| |
− | | |
− | -- The 'navbox-styles' div exists to wrap the styles to work around T200206
| |
− | -- more elegantly. Instead of combinatorial rules, this ends up being linear
| |
− | -- number of CSS rules.
| |
− | return mw.html.create('div')
| |
− | :addClass(cfg.class.navbox_styles)
| |
− | :wikitext(
| |
− | add_list_styles() .. -- see [hlist_note] applied to 'before base_templatestyles'
| |
− | base_templatestyles ..
| |
− | templatestyles ..
| |
− | child_templatestyles ..
| |
− | table.concat(hiding_templatestyles)
| |
− | )
| |
− | :done()
| |
| end | | end |
| | | |
− | -- work around [[phab:T303378]]
| + | function p._navbox(args) |
− | -- for each arg: find all the templatestyles strip markers, insert them into a
| + | if not yesno then |
− | -- table. then remove all templatestyles markers from the arg
| + | yesno = require('Module:Yesno') |
− | local function move_hiding_templatestyles(args)
| |
− | local gfind = string.gfind | |
− | local gsub = string.gsub
| |
− | local templatestyles_markers = {}
| |
− | local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
| |
− | for k, arg in pairs(args) do
| |
− | for marker in gfind(arg, strip_marker_pattern) do | |
− | table.insert(templatestyles_markers, marker)
| |
− | end
| |
− | args[k] = gsub(arg, strip_marker_pattern, '')
| |
| end | | end |
− | return templatestyles_markers
| |
− | end
| |
| | | |
− | function p._navbox(navboxArgs)
| |
− | args = navboxArgs
| |
− | local hiding_templatestyles = move_hiding_templatestyles(args)
| |
| local listnums = {} | | local listnums = {} |
− | | + | for k, v in pairs(args) do |
− | for k, _ in pairs(args) do | + | local listnum = ('' .. k):match('^list(%d+)$') or ('' .. k):match('^список(%d+)$') |
− | if type(k) == 'string' then | + | if listnum then table.insert(listnums, tonumber(listnum)) end |
− | local listnum = k:match(cfg.pattern.listnum)
| |
− | if listnum then table.insert(listnums, tonumber(listnum)) end
| |
− | end
| |
| end | | end |
| + | |
| table.sort(listnums) | | table.sort(listnums) |
| | | |
− | local border = mw.text.trim(args[cfg.arg.border] or args[1] or '') | + | args.border = mw.text.trim(args.border or args[1] or '') |
− | if border == cfg.keyword.border_child then | + | if args.border == 'child' then |
− | border = cfg.keyword.border_subgroup | + | args.border = 'subgroup' |
| + | end |
| + | |
| + | for argname, aliasesList in pairs(commonAliases) do |
| + | args[argname] = checkAliases(args, aliasesList) |
| + | end |
| + | for argname, aliasesList in pairs(standardAliases) do |
| + | args[argname] = checkAliases(args, aliasesList) |
| end | | end |
| | | |
| + | args.navigation = yesno(args.navigation, '') |
| + | |
| -- render the main body of the navbox | | -- render the main body of the navbox |
− | local tbl = renderMainTable(border, listnums) | + | local tbl = renderMainTable(args, listnums) |
| | | |
| + | -- render the appropriate wrapper around the navbox, depending on the border param |
| local res = mw.html.create() | | local res = mw.html.create() |
− | -- render the appropriate wrapper for the navbox, based on the border param
| + | if args.border == 'none' then |
− | | |
− | if border == cfg.keyword.border_none then | |
− | res:node(add_navbox_styles(hiding_templatestyles))
| |
| local nav = res:tag('div') | | local nav = res:tag('div') |
| :attr('role', 'navigation') | | :attr('role', 'navigation') |
| :node(tbl) | | :node(tbl) |
− | -- aria-labelledby title, otherwise above, otherwise lone group
| + | if args.title then |
− | if args[cfg.arg.title] or args[cfg.arg.above] or (args[cfg.arg.group1] | + | nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title)) |
− | and not args[cfg.arg.group2]) then
| |
− | nav:attr( | |
− | 'aria-labelledby',
| |
− | mw.uri.anchorEncode(
| |
− | args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group1]
| |
− | )
| |
− | )
| |
| else | | else |
− | nav:attr('aria-label', cfg.aria_label) | + | nav:attr('aria-label', 'Навигационный шаблон') |
| + | end |
| + | if args.name and args.name ~= '-' then |
| + | nav:attr('data-name', args.name) |
| + | end |
| + | if args.navigation == true then |
| + | nav:attr('data-navboxnavigation', '1') |
| + | elseif args.navigation == false then |
| + | nav:attr('data-navboxnavigation', '0') |
| end | | end |
− | elseif border == cfg.keyword.border_subgroup then | + | elseif args.border == 'subgroup' then |
− | -- We assume that this navbox is being rendered in a list cell of a | + | -- We assume that this navbox is being rendered in a list cell of a parent navbox, and is |
− | -- parent navbox, and is therefore inside a div with padding:0em 0.25em.
| + | -- therefore inside a div with padding:0em 0.25em. We start with a </div> to avoid the |
− | -- We start with a </div> to avoid the padding being applied, and at the
| + | -- padding being applied, and at the end add a <div> to balance out the parent's </div> |
− | -- end add a <div> to balance out the parent's </div>
| |
| res | | res |
− | :wikitext('</div>') | + | :wikitext('</div>') -- XXX: hack due to lack of unclosed support in mw.html. |
| :node(tbl) | | :node(tbl) |
− | :wikitext('<div>') | + | :wikitext('<div>') -- XXX: hack due to lack of unclosed support in mw.html. |
| else | | else |
− | res:node(add_navbox_styles(hiding_templatestyles))
| |
| local nav = res:tag('div') | | local nav = res:tag('div') |
| :attr('role', 'navigation') | | :attr('role', 'navigation') |
− | :addClass(cfg.class.navbox) | + | :addClass('navbox') |
− | :addClass(args[cfg.arg.navboxclass])
| + | :cssText(args.bodystyle) |
− | :cssText(args[cfg.arg.bodystyle]) | |
− | :cssText(args[cfg.arg.style])
| |
− | :css('padding', '3px')
| |
| :node(tbl) | | :node(tbl) |
− | -- aria-labelledby title, otherwise above, otherwise lone group
| + | if args.title then |
− | if args[cfg.arg.title] or args[cfg.arg.above] | + | nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title)) |
− | or (args[cfg.arg.group1] and not args[cfg.arg.group2]) then
| |
− | nav:attr( | |
− | 'aria-labelledby',
| |
− | mw.uri.anchorEncode(args[cfg.arg.title] or args[cfg.arg.above] or args[cfg.arg.group1])
| |
− | )
| |
| else | | else |
− | nav:attr('aria-label', cfg.aria_label) | + | nav:attr('aria-label', 'Навигационный шаблон') |
| + | end |
| + | if args.name and args.name ~= '-' then |
| + | nav:attr('data-name', args.name) |
| + | end |
| + | if args.navigation == true then |
| + | nav:attr('data-navboxnavigation', '1') |
| + | elseif args.navigation == false then |
| + | nav:attr('data-navboxnavigation', '0') |
| end | | end |
| + | |
| end | | end |
| | | |
− | if (args[cfg.arg.nocat] or cfg.keyword.nocat_false):lower() == cfg.keyword.nocat_false then | + | renderTrackingCategories(res, args) |
− | renderTrackingCategories(res, border)
| + | |
− | end
| + | return striped(tostring(res), args) |
− | return striped(tostring(res), border) | |
| end | | end |
| | | |
Строка 545: |
Строка 599: |
| getArgs = require('Module:Arguments').getArgs | | getArgs = require('Module:Arguments').getArgs |
| end | | end |
− | args = getArgs(frame, {wrappers = {cfg.pattern.navbox}}) | + | if not yesno then |
| + | yesno = require('Module:Yesno') |
| + | end |
| + | args = getArgs(frame, {wrappers = {'Шаблон:Навигационная таблица', 'Шаблон:Подгруппы навигационной таблицы'}}) |
| + | if frame.args.border then |
| + | -- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}. |
| + | args.border = frame.args.border |
| + | end |
| + | |
| + | readInTheRightOrder(args, standardElementAliases.group, standardElementAliases.list) |
| | | |
− | -- Read the arguments in the order they'll be output in, to make references | + | return p._navbox(args) |
− | -- number in the right order. | + | end |
− | local _ | + | |
− | _ = args[cfg.arg.title] | + | function p.navboxWithCollapsibleGroups(frame) |
− | _ = args[cfg.arg.above] | + | if not getArgs then |
− | -- Limit this to 20 as covering 'most' cases (that's a SWAG) and because | + | getArgs = require('Module:Arguments').getArgs |
− | -- iterator approach won't work here | + | end |
| + | local args = getArgs(frame, {wrappers = {'Шаблон:Навигационная таблица с блоками'}}) |
| + | |
| + | readInTheRightOrder(args, groupsChildAliases.title, groupsChildAliases.list1) |
| + | |
| + | local parent = {} |
| + | for argname, aliasesList in pairs(commonAliases) do |
| + | parent[argname] = checkAliases(args, aliasesList) |
| + | end |
| + | for argname, aliasesList in pairs(groupsParentAliases) do |
| + | parent[argname] = checkAliases(args, aliasesList) |
| + | end |
| + | |
| for i = 1, 20 do | | for i = 1, 20 do |
− | _ = args[format(cfg.arg.group_and_num, i)] | + | local child = {} |
− | _ = args[format(cfg.arg.list_and_num, i)] | + | for argname, aliasesList in pairs(groupsChildAliases) do |
| + | child[argname] = checkAliases(args, aliasesList, i) |
| + | end |
| + | |
| + | child.color = child.color and string.format('background:%s;', child.color) or '' |
| + | child.border = 'child' |
| + | child.navbar = 'plain' |
| + | |
| + | if parent.selected and parent.selected == child.groupname then |
| + | child.state = 'uncollapsed' |
| + | end |
| + | |
| + | child.state = child.state or 'collapsed' |
| + | |
| + | child.basestyle = concatStyles{parent.basestyle, parent.secttitlestyle, child.secttitlestyle} |
| + | child.titlestyle = concatStyles{parent._groupstyle, child.groupstyle, child.color} |
| + | child.liststyle = concatStyles{parent._liststyle, child.liststyle} |
| + | child.lispadding = parent._listpadding |
| + | |
| + | if child.title then |
| + | parent['list' .. i] = p._navbox(child) |
| + | else |
| + | parent['list' .. i] = child.list1 |
| + | end |
| end | | end |
− | _ = args[cfg.arg.below] | + | |
− | | + | return p._navbox(parent) |
− | return p._navbox(args) | + | |
| end | | end |
| | | |
| return p | | return p |