跳转到内容
打开/关闭菜单
  • 486 条目
  • 873 文件
  • 22 用户
  • 9656 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
KK​(留言 | 贡献)2026年10月30日 (五) 15:54的版本

此模块的文档可以在模块:Navbox/doc创建

--
-- Module:Navbox
-- 移除了 Navbar (VDE) 功能
-- 新增独立「编」按钮
-- 支持 name / editname 指定编辑模板
-- 支持直接调用模块时自动识别模板
--

local p = {}

local getArgs -- lazily initialized

local args
local border
local listnums

local ODD_EVEN_MARKER = '\127_ODDEVEN_\127'
local RESTART_MARKER = '\127_ODDEVEN0_\127'
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127'


--
-- 奇偶行样式处理
--
local function striped(wikitext)
    local orphanCat = '[[Category:Navbox orphans]]'

    if border == 'subgroup' and args.orphan ~= 'yes' then
        return wikitext .. orphanCat
    end

    local first, second = 'odd', 'even'

    if args.evenodd then
        if args.evenodd == 'swap' then
            first, second = second, first
        else
            first = args.evenodd
            second = first
        end
    end

    local changer

    if first == second then
        changer = first
    else
        local index = 0

        changer = function(code)
            if code == '0' then
                index = 0
                return first
            end

            index = index + 1
            return index % 2 == 1 and first or second
        end
    end

    local regex = orphanCat:gsub('([%[%]])', '%%%1')

    return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer))
end


--
-- 列表内容处理
--
local function processItem(item, nowrapitems)
    if not item then
        return nil
    end

    if item:sub(1, 2) == '{|' then
        return '\n' .. item .. '\n'
    end

    if nowrapitems == 'yes' then
        local lines = {}

        for line in (item .. '\n'):gmatch('([^\n]*)\n') do
            local prefix, content = line:match('^([*:;#]+)%s*(.*)')

            if prefix and not content:match('^<span class="nowrap">') then
                line = prefix .. '<span class="nowrap">' .. content .. '</span>'
            end

            table.insert(lines, line)
        end

        item = table.concat(lines, '\n')
    end

    if item:match('^[*:;#]') then
        return '\n' .. item .. '\n'
    end

    return item
end


--
-- 编辑目标识别
--
local function getEditTarget()

    -- 允许关闭编辑按钮
    if args.edit == 'off' or args.navbar == 'off' then
        return nil
    end

    -- 优先读取显式指定的模板名称
    local name = mw.text.trim(args.editname or args.name or '')

    if name ~= '' then
        local title = mw.title.new(name, 10)

        if title
            and title.namespace == 10
            and title.interwiki == ''
        then
            return title
        end
    end

    -- 尝试从模块调用来源识别模板
    local frame = mw.getCurrentFrame()
    local parent = frame:getParent()

    if parent then
        local parentTitle = mw.title.new(parent:getTitle())

        if parentTitle and parentTitle.namespace == 10 then
            local parentName = parentTitle.text:gsub('/sandbox$', '')

            -- 排除通用 Navbox 包装模板
            if parentName ~= 'Navbox'
                and parentName ~= 'Navbox subgroup'
            then
                return parentTitle
            end
        end
    end

    return nil
end


--
-- 标题行
--
local function renderTitleRow(tbl)
    if not args.title then
        return
    end

    local titleRow = tbl:tag('tr')

    if args.titlegroup then
        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 titleCell = titleRow:tag('th'):attr('scope', 'col')

    if args.titlegroup then
        titleCell:addClass('navbox-title1')
    end

    local titleColspan = 2

    if args.imageleft then
        titleColspan = titleColspan + 1
    end

    if args.image then
        titleColspan = titleColspan + 1
    end

    if args.titlegroup then
        titleColspan = titleColspan - 1
    end

    titleCell
        :cssText(args.basestyle)
        :cssText(args.titlestyle)
        :addClass('navbox-title')
        :attr('colspan', titleColspan)


    --
    -- 独立「编」按钮
    --
    local editTarget = getEditTarget()

    if editTarget then
        titleCell:css('position', 'relative')

        titleCell
            :tag('span')
            :addClass('navbox-editlink')
            :css('position', 'absolute')
            :css('left', '0.6em')
            :css('top', '50%')
            :css('transform', 'translateY(-50%)')
            :css('font-size', '80%')
            :css('font-weight', 'normal')
            :css('line-height', '1')
            :css('white-space', 'nowrap')
            :css('z-index', '1')
            :tag('a')
            :attr('href', editTarget:fullUrl('action=edit'))
            :attr('title', '编辑模板:' .. editTarget.text)
            :attr('aria-label', '编辑模板:' .. editTarget.text)
            :css('color', 'inherit')
            :css('text-decoration', 'underline')
            :wikitext('编')
    end


    --
    -- 标题文本
    --
    titleCell
        :tag('div')
        :attr('id', mw.uri.anchorEncode(args.title))
        :addClass(args.titleclass)
        :css('font-size', '114%')
        :css('margin', '0')
        :wikitext(processItem(args.title))
end


--
-- 上下方内容的列跨度
--
local function getAboveBelowColspan()
    local ret = 2

    if args.imageleft then
        ret = ret + 1
    end

    if args.image then
        ret = ret + 1
    end

    return ret
end


--
-- 上方内容
--
local function renderAboveRow(tbl)
    if not args.above then
        return
    end

    tbl
        :tag('tr')
        :tag('td')
        :addClass('navbox-abovebelow')
        :addClass(args.aboveclass)
        :cssText(args.basestyle)
        :cssText(args.abovestyle)
        :attr('colspan', getAboveBelowColspan())
        :tag('div')
        :attr(
            'id',
            args.title and nil or mw.uri.anchorEncode(args.above)
        )
        :wikitext(processItem(args.above, args.nowrapitems))
end


--
-- 下方内容
--
local function renderBelowRow(tbl)
    if not args.below then
        return
    end

    tbl
        :tag('tr')
        :tag('td')
        :addClass('navbox-abovebelow')
        :addClass(args.belowclass)
        :cssText(args.basestyle)
        :cssText(args.belowstyle)
        :attr('colspan', getAboveBelowColspan())
        :tag('div')
        :wikitext(processItem(args.below, args.nowrapitems))
end


--
-- 列表行
--
local function renderListRow(tbl, index, listnum)
    local row = tbl:tag('tr')

    --
    -- 左侧图片
    --
    if index == 1 and args.imageleft then
        row
            :tag('td')
            :addClass('navbox-image')
            :addClass(args.imageclass)
            :css('width', '1px')
            :css('padding', '0 2px 0 0')
            :cssText(args.imageleftstyle)
            :attr('rowspan', #listnums)
            :tag('div')
            :wikitext(processItem(args.imageleft))
    end


    --
    -- 分组标题
    --
    if args['group' .. listnum] then
        row
            :tag('th')
            :attr('scope', 'row')
            :addClass('navbox-group')
            :addClass(args.groupclass)
            :cssText(args.basestyle)
            :css('width', args.groupwidth or '1%')
            :cssText(args.groupstyle)
            :cssText(args['group' .. listnum .. 'style'])
            :wikitext(args['group' .. listnum])
    end


    --
    -- 列表主体
    --
    local listCell = row:tag('td')

    if args['group' .. listnum] then
        listCell:addClass('navbox-list1')
    else
        listCell:attr('colspan', 2)
    end

    if not args.groupwidth then
        listCell:css('width', '100%')
    end

    local rowstyle =
        (index % 2 == 1) and args.oddstyle or args.evenstyle

    local listText = args['list' .. listnum]

    local oddEven = ODD_EVEN_MARKER

    -- 子导航框的奇偶样式处理
    if listText:sub(1, 12) == '</div><table'
        or listText:find('navbox%-subgroup')
    then
        oddEven =
            listText:find('<th[^>]*"navbox%-title"')
            and RESTART_MARKER
            or 'odd'
    end

    listCell
        :css('padding', '0px')
        :cssText(args.liststyle)
        :cssText(rowstyle)
        :cssText(args['list' .. listnum .. 'style'])
        :addClass('navbox-list')
        :addClass('navbox-' .. oddEven)
        :addClass(args.listclass)
        :addClass(args['list' .. listnum .. 'class'])
        :tag('div')
        :css(
            'padding',
            (index == 1 and args.list1padding)
                or args.listpadding
                or '0em 0.25em'
        )
        :wikitext(processItem(listText, args.nowrapitems))


    --
    -- 右侧图片
    --
    if index == 1 and args.image then
        row
            :tag('td')
            :addClass('navbox-image')
            :addClass(args.imageclass)
            :css('width', '1px')
            :css('padding', '0 0 0 2px')
            :cssText(args.imagestyle)
            :attr('rowspan', #listnums)
            :tag('div')
            :wikitext(processItem(args.image))
    end
end


--
-- 列表样式加载
--
local function addListStyles()
    local frame = mw.getCurrentFrame()

    local function _add(htmlclass, ts)
        local found = false

        for _, arg in ipairs({
            'bodyclass',
            'listclass',
            'titleclass',
            'groupclass'
        }) do
            if (args[arg] or ''):find(htmlclass) then
                found = true
                break
            end
        end

        return found
            and frame:extensionTag{
                name = 'templatestyles',
                args = {src = ts}
            }
            or ''
    end

    return
        _add('hlist', 'Flatlist/styles.css')
        .. _add('plainlist', 'Plainlist/styles.css')
end


--
-- 主导航框生成
--
function p._navbox(navboxArgs)
    args = navboxArgs

    listnums = {}

    for k, _ in pairs(args) do
        local listnum =
            type(k) == 'string'
            and k:match('^list(%d+)$')

        if listnum then
            table.insert(listnums, tonumber(listnum))
        end
    end

    table.sort(listnums)

    border = mw.text.trim(args.border or args[1] or '')

    if border == 'child' then
        border = 'subgroup'
    end


    --
    -- 创建表格
    --
    local tbl = mw.html.create('table')
        :addClass('nowraplinks')
        :addClass(args.bodyclass)


    --
    -- 折叠功能
    --
    if args.title
        and args.state ~= 'plain'
        and args.state ~= 'off'
    then
        tbl
            :addClass('mw-collapsible')
            :addClass(
                args.state == 'collapsed'
                and 'mw-collapsed'
                or (args.state or 'autocollapse')
            )
    end

    tbl:css('border-spacing', 0)


    --
    -- 边框样式
    --
    if border == 'subgroup' or border == 'none' then
        tbl
            :addClass('navbox-subgroup')
            :cssText(args.bodystyle)
            :cssText(args.style)
    else
        tbl
            :addClass('navbox-inner')
            :css('background', 'transparent')
            :css('color', 'inherit')
    end


    --
    -- 渲染各个部分
    --
    renderTitleRow(tbl)
    renderAboveRow(tbl)

    for i, listnum in ipairs(listnums) do
        renderListRow(tbl, i, listnum)
    end

    renderBelowRow(tbl)


    --
    -- 加载 TemplateStyles
    --
    local res = mw.html.create()
    local frame = mw.getCurrentFrame()

    res
        :tag('div')
        :addClass('navbox-styles')
        :addClass('nomobile')
        :wikitext(
            frame:extensionTag{
                name = 'templatestyles',
                args = {
                    src = 'Module:Navbox/styles.css'
                }
            }
        )
        :done()


    --
    -- 输出导航框
    --
    if border == 'subgroup' then
        res
            :wikitext('</div>')
            :wikitext(addListStyles())
            :node(tbl)
            :wikitext('<div>')
    else
        res
            :tag('div')
            :attr('role', 'navigation')
            :addClass(
                border ~= 'none'
                and 'navbox'
                or nil
            )
            :addClass(args.navboxclass)
            :cssText(args.bodystyle or args.style)
            :wikitext(addListStyles())
            :node(tbl)
    end

    return striped(tostring(res))
end


--
-- 模块入口
--
function p.navbox(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end

    args = getArgs(frame, {
        wrappers = {
            'Template:Navbox',
            'Template:Navbox subgroup'
        }
    })

    return p._navbox(args)
end

return p