打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

本站正在进行早期测试,目前仍存在许多内容的缺失。

模块:Navbox

来自星砂岛百科

概述

Navbox 用于渲染通用导航框,适合在条目底部输出“按分类浏览”的导航内容。

用法

{{Navbox|数据:Item/item_navbox_index.json}}
{{Navbox
 | data_page = 数据:Item/item_navbox_index.json
 | box = tool
 | target = DIY核心
}}

数据格式

{
  "boxes": {
    "tool": {
      "title": "工具导航",
      "subtitle": "按用途和类型快速浏览已创建的工具条目",
      "badge": "66 项",
      "groups": [
        {
          "label": "钓竿",
          "links": [
            { "page": "木制钓竿", "label": "木制钓竿" }
          ]
        }
      ]
    }
  },
  "lookup": {
    "diy核心": "tool",
    "item.diycore": "tool"
  }
}

参数

  • data_page / 第 1 参数:数据页标题。
  • box / 第 2 参数:可选,指定具体导航框 key。
  • target / 第 3 参数:可选,用于从 lookup 自动匹配导航框,并高亮当前条目。
  • class:可选,给外层追加 class。

local common = require('Module:Common')
local css = require('Module:CSS')

local p = {}

local data_cache = {}

local function trim(value)
    return common.trim(value)
end

local function normalize_key(value)
    return common.normalizeKey(value)
end

local function load_data(title_text)
    local page_name = trim(title_text)
    if page_name == '' then
        return nil
    end

    if data_cache[page_name] ~= nil then
        return data_cache[page_name] or nil
    end

    local data = common.loadJsonData(page_name)
    if type(data) ~= 'table' then
        data_cache[page_name] = false
        return nil
    end

    data_cache[page_name] = data
    return data
end

local function get_box_by_key(boxes, box_key)
    if type(boxes) ~= 'table' then
        return nil
    end

    local resolved = trim(box_key)
    if resolved == '' then
        return nil
    end

    if type(boxes[resolved]) == 'table' then
        return boxes[resolved]
    end

    local normalized = normalize_key(resolved)
    if type(boxes[normalized]) == 'table' then
        return boxes[normalized]
    end

    return nil
end

local function resolve_box(data, box_key, target)
    if type(data) ~= 'table' then
        return nil
    end

    local boxes = data.boxes
    if type(boxes) ~= 'table' then
        return nil
    end

    local explicit = get_box_by_key(boxes, box_key)
    if explicit then
        return explicit
    end

    local lookup = data.lookup
    if type(lookup) == 'table' then
        local normalized_target = normalize_key(target)
        if normalized_target ~= '' then
            local lookup_key = lookup[normalized_target]
            local matched = get_box_by_key(boxes, lookup_key)
            if matched then
                return matched
            end
        end
    end

    return get_box_by_key(boxes, target)
end

local function build_current_targets(target)
    local normalized = {}

    local function append(value)
        local key = normalize_key(value)
        if key ~= '' then
            normalized[key] = true
        end
    end

    append(target)
    append(common.getCurrentTitleText())

    return normalized
end

local function is_current_link(link, current_targets)
    if type(link) ~= 'table' then
        return false
    end

    for _, value in ipairs({
        link.page,
        link.title,
        link.label,
    }) do
        local normalized = normalize_key(value)
        if normalized ~= '' and current_targets[normalized] then
            return true
        end
    end

    return false
end

local function render_link(container, link, current_targets)
    if type(link) ~= 'table' then
        return
    end

    local page = trim(link.page or link.title or link.label)
    local label = trim(link.label or link.title or link.page)

    if page == '' then
        return
    end
    if label == '' then
        label = page
    end

    local chip = container:tag('span'):addClass('navbox__chip')
    local icon_file = trim(link.icon_file)
    if icon_file ~= '' then
        chip:tag('span')
            :addClass('navbox__chip-icon')
            :wikitext('[[File:' .. icon_file .. '|18x18px|link=]]')
    end

    if is_current_link(link, current_targets) then
        chip:addClass('is-current')
        chip:tag('span'):addClass('navbox__chip-label'):wikitext(label)
        return
    end

    if label == page then
        chip:tag('span'):addClass('navbox__chip-label'):wikitext('[[' .. page .. ']]')
    else
        chip:tag('span'):addClass('navbox__chip-label'):wikitext('[[' .. page .. '|' .. label .. ']]')
    end
end

local function render_group(container, group, current_targets)
    if type(group) ~= 'table' or type(group.links) ~= 'table' then
        return
    end

    local link_count = 0
    for _, _ in ipairs(group.links) do
        link_count = link_count + 1
    end
    if link_count == 0 then
        return
    end

    local row = container:tag('div'):addClass('navbox__group')
    local label = trim(group.label)
    if label ~= '' then
        row:tag('div'):addClass('navbox__group-label'):wikitext(label)
    else
        row:tag('div'):addClass('navbox__group-label')
    end

    local links = row:tag('div'):addClass('navbox__links')
    for _, link in ipairs(group.links) do
        render_link(links, link, current_targets)
    end
end

local function render_icon(title_row, options)
    local icon_markup = trim(options.icon)
    local icon_file = trim(options.icon_file)
    if icon_markup == '' and icon_file == '' then
        return
    end

    local icon = title_row:tag('div'):addClass('navbox__icon')
    if icon_markup ~= '' then
        icon:wikitext(icon_markup)
        return
    end

    if common.filePageExists(icon_file) then
        icon:wikitext('[[File:' .. icon_file .. '|44x44px|link=]]')
    end
end

local function render_box(box, target, options)
    if type(box) ~= 'table' or type(box.groups) ~= 'table' then
        return ''
    end

    local group_count = 0
    for _, group in ipairs(box.groups) do
        if type(group) == 'table' and type(group.links) == 'table' and group.links[1] ~= nil then
            group_count = group_count + 1
        end
    end
    if group_count == 0 then
        return ''
    end

    local root = mw.html.create('div')
    root:addClass('navbox')
    root:attr('role', 'navigation')
    root:attr('aria-label', trim(box.title) ~= '' and trim(box.title) or '导航')

    local custom_class = trim(options.extra_class)
    if custom_class ~= '' then
        root:addClass(custom_class)
    end

    local panel = root:tag('div'):addClass('navbox__panel')
    local header = panel:tag('div'):addClass('navbox__header')
    local heading = header:tag('div'):addClass('navbox__heading')
    local title_row = heading:tag('div'):addClass('navbox__title-row')

    render_icon(title_row, options)

    local title = trim(box.title)
    if title ~= '' then
        title_row:tag('h2'):addClass('navbox__title'):wikitext(title)
    end

    local subtitle = trim(box.subtitle)
    if subtitle ~= '' then
        heading:tag('div'):addClass('navbox__subtitle'):wikitext(subtitle)
    end

    local badge = trim(box.badge)
    if badge ~= '' then
        header:tag('div'):addClass('navbox__badge'):wikitext(badge)
    end

    local body = panel:tag('div'):addClass('navbox__body')
    local current_targets = build_current_targets(target)
    for _, group in ipairs(box.groups) do
        render_group(body, group, current_targets)
    end

    local footer = trim(box.footer)
    if footer ~= '' then
        panel:tag('div'):addClass('navbox__footer'):wikitext(footer)
    end

    return tostring(root)
end

function p.render(frame)
    local data_page = common.getArg(frame, 'data_page', common.getArg(frame, 1, ''))
    if data_page == '' then
        return ''
    end

    local box_key = common.getArg(frame, 'box', common.getArg(frame, 2, ''))
    local target = common.getArg(frame, 'target', common.getArg(frame, 3, common.getCurrentTitleText()))
    local options = {
        extra_class = common.getArg(frame, 'class', ''),
        icon = common.getArg(frame, 'icon', ''),
        icon_file = common.getArg(frame, 'icon_file', ''),
    }

    local data = load_data(data_page)
    local box = resolve_box(data, box_key, target)
    if not box then
        return ''
    end

    local content = render_box(box, target, options)
    if content == '' then
        return ''
    end

    return (css.quickCall('Navbox') or '') .. content
end

return p