Module:NavboxMobile: Difference between revisions

From Ikwipedia
No edit summary
No edit summary
 
(154 intermediate revisions by the same user not shown)
Line 50: Line 50:


local function add_list_styles()
local function add_list_styles()
local frame = mw.getCurrentFrame()
    local frame = mw.getCurrentFrame()
local function add_list_templatestyles(htmlclass, templatestyles)
    local function add_list_templatestyles(htmlclass, templatestyles)
if has_list_class(htmlclass) then
        if has_list_class(htmlclass) then
return frame:extensionTag{
            return frame:extensionTag{
name = 'templatestyles', args = { src = templatestyles }
                name = 'templatestyles', args = { src = templatestyles }
}
            }
else
        else
return ''
            return ''
end
        end
end
    end
       
local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)
    local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)
local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)
    local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)
   
-- a second workaround for [[phab:T303378]]
    -- a second workaround for [[phab:T303378]]
-- when that issue is fixed, we can actually use has_navbar not to emit the
    -- when that issue is fixed, we can actually use has_navbar not to emit the
-- tag here if we want
    -- tag here if we want
if has_navbar() and hlist_styles == '' then
    if has_navbar() and hlist_styles == '' then
hlist_styles = frame:extensionTag{
        hlist_styles = frame:extensionTag{
name = 'templatestyles', args = { src = cfg.hlist_templatestyles }
            name = 'templatestyles', args = { src = cfg.hlist_templatestyles }
}
        }
end
    end
   
-- hlist -> plainlist is best-effort to preserve old Common.css ordering.
    -- hlist -> plainlist is best-effort to preserve old Common.css ordering.
-- this ordering is not a guarantee because most navboxes will emit only
    -- this ordering is not a guarantee because most navboxes will emit only
-- one of these classes [hlist_note]
    -- one of these classes [hlist_note]
return hlist_styles .. plainlist_styles
    return hlist_styles .. plainlist_styles
end
end


Line 95: Line 95:


     -- Combine list styles with base and user styles
     -- Combine list styles with base and user styles
     local list_styles = add_list_styles() -- Ensure this function is defined or required appropriately
     local list_styles = add_list_styles()
     local base_templatestyles = cfg.templatestyles
     local base_templatestyles = cfg.templatestyles
     local templatestyles = add_user_styles(args[cfg.arg.templatestyles])
     local templatestyles = add_user_styles(args[cfg.arg.templatestyles])
Line 141: Line 141:
     end
     end
end
end
local function processItem(item, nowrapitems)
    if not item then
        return '' -- Prevent nil errors
    end
    if item:sub(1, 2) == '{|' then
        -- Applying nowrap to lines in a table does not make sense.
        -- Add newlines to compensate for trim of x in |parm=x in a template.
        item = item:gsub('|%s*width%s*=%s*"[%d%w]+"', '| width="100%"')
        return '\n' .. item .. '\n'
    end
    if nowrapitems == cfg.keyword.nowrapitems_yes or true then
        local lines = {}
        for line in (item .. '\n'):gmatch('([^\n]*)\n') do
            local prefix, content = line:match('^([*:;#]+)%s*(.*)')
            if prefix and not content:match(cfg.pattern.nowrap) then
                line = format(cfg.nowrap_item, prefix, content)
            end
            table.insert(lines, line)
        end
        item = table.concat(lines, '\n')
    end
    if item:match('^[*:;#]') then
        return '\n' .. item .. '\n'
    end
    -- Ensure no excessive whitespace
    item = mw.ustring.gsub(item, "%s+", " ")
    return item
end


-- Function to add a new table row with optional gutter
-- Function to add a new table row with optional gutter
local function addTableRow(tbl)
local function addTableRow(tbl)
     if tableRowAdded then
    -- Check if the spacer rows are necessary based on the listclass or bodyclass
         tbl
    local suppressSpacer = false
            :tag('tr')
    if args.listclass then
                :css('height', '2px')
        suppressSpacer = suppressSpacer or args.listclass:find("hlist")
                :tag('td')
    end
                    :attr('colspan', 2)
    if args.bodyclass then
        -- Note: You can add additional gutter styling here if needed
        suppressSpacer = suppressSpacer or args.bodyclass:find("hlist")
    end
 
    -- Only add spacer rows if they are not explicitly suppressed
     if tableRowAdded and not suppressSpacer then
         tbl:tag('tr')
            :css('height', '2px')
            :tag('td')
                :attr('colspan', 2)
                :css('background-color', 'transparent') -- Ensure spacer is invisible
     end
     end
     tableRowAdded = true
     tableRowAdded = true
     return tbl:tag('tr')
     return tbl:tag('tr')
Line 157: Line 198:


-- Function to render the navigation bar
-- Function to render the navigation bar
local function renderNavBar(titleCell)
local function renderNavBar(cell)
    local spacerSide = nil
 
     if args.navbar == 'off' then
     if args.navbar == 'off' then
         if args.state == 'plain' then spacerSide = 'right' end
         return -- Don't render anything if Navbar is off
    elseif args.navbar == 'plain' or (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:navbox') then
        if args.state ~= 'plain' then spacerSide = 'left' end
    else
        if args.state == 'plain' then spacerSide = 'right' end
 
        titleCell:wikitext(navbar{
            args.name,
            mini = 1,
            fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') ..  ';background:none transparent;border:none;'
        })
     end
     end


     if spacerSide then
     -- Render the Navbar
         titleCell
    cell:wikitext(navbar{
            :tag('span')
        args.name,
                :css('float', spacerSide)
         mini = 1,
                :css('width', '6em')
        fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;'
                :wikitext(' ')
     })
     end
end
end


--
--
Line 190: Line 219:


     local titleRow = addTableRow(tbl)
     local titleRow = addTableRow(tbl)
    if args.titlegroup then
        titleRow
            :tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-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')
     local titleCell = titleRow:tag('th'):attr('scope', 'col')
 
        :addClass('navboxMobile-title')
    if args.titlegroup then
        :addClass(cfg.class.navbox_list)
         titleCell
        :addClass(args[cfg.arg.titleclass])
            :css('border-left', '2px solid #fdfdfd')
         :attr('data-level', 1)
            :css('width', '100%')
        :attr('colspan', 2)
    end
 
    local titleColspan = 2
    -- Adjust colspan based on image presence if needed
    if args.titlegroup then titleColspan = titleColspan - 1 end
 
    titleCell
         :cssText(args.basestyle)
         :cssText(args.basestyle)
         :cssText(args.titlestyle)
         :cssText(args.titlestyle)
         :addClass('navboxMobile-title')
         :css('position', 'relative') -- Ensure relative positioning for absolute children
        :attr('colspan', titleColspan)


     renderNavBar(titleCell)
     -- Add the title content, centered across the full width
    titleCell
        :tag('div')
            :css('margin', '0 auto')
            :css('text-align', 'center')
            :wikitext(addNewline(args.title))


     titleCell
     -- Add the navbar, positioned absolutely in the top-right corner
        :tag('div')
    if has_navbar() then
            :addClass(args.titleclass)
        titleCell
            :css('font-size', '114%')
            :tag('div')
            :wikitext(addNewline(args.title))
                :addClass('navboxMobile-navbar')
                :css('position', 'absolute')
                :css('top', '0')
                :css('right', '0')
                :css('padding', '0.2em') -- Optional: Adjust padding for alignment
                :wikitext(navbar{
                    args.name,
                    mini = 1,
                    fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;'
                })
    end
end
end


Line 243: Line 266:
     addTableRow(tbl)
     addTableRow(tbl)
         :tag('td')
         :tag('td')
             :addClass('navboxMobile-abovebelow')
             :addClass('navboxMobile-abovebelow-content')
            :addClass(cfg.class.navbox_list) -- Add consistent list styling
             :addClass(args.aboveclass)
             :addClass(args.aboveclass)
             :cssText(args.basestyle)
             :cssText(args.basestyle)
             :cssText(args.abovestyle)
             :cssText(args.abovestyle)
            :css('padding', args[cfg.arg.abovepadding] or '0.5em') -- Optional padding
             :attr('colspan', getAboveBelowColspan())
             :attr('colspan', getAboveBelowColspan())
             :tag('div')
             :tag('div')
                 :wikitext(addNewline(args.above))
                 :wikitext(addNewline(args.above))
end
end


local function renderBelowRow(tbl)
local function renderBelowRow(tbl)
Line 257: Line 284:
     addTableRow(tbl)
     addTableRow(tbl)
         :tag('td')
         :tag('td')
             :addClass('navboxMobile-abovebelow')
             :addClass('navboxMobile-abovebelow-content')
            :addClass(cfg.class.navbox_list) -- Add consistent list styling
             :addClass(args.belowclass)
             :addClass(args.belowclass)
             :cssText(args.basestyle)
             :cssText(args.basestyle)
             :cssText(args.belowstyle)
             :cssText(args.belowstyle)
            :css('padding', args[cfg.arg.belowpadding] or '0.5em') -- Optional padding
             :attr('colspan', getAboveBelowColspan())
             :attr('colspan', getAboveBelowColspan())
             :tag('div')
             :tag('div')
Line 266: Line 295:
end
end


--
--  List rows
--
local function renderListRow(tbl, listnum)
    local row = addTableRow(tbl)


    -- Process group
    if args['group' .. listnum] then
        local groupCell = row:tag('th')
        groupCell
            :attr('scope', 'row')
            :addClass('navboxMobile-group')
            :cssText(args.basestyle)
            :cssText(args.groupstyle)
            :cssText(args['group' .. listnum .. 'style'])
            :wikitext(args['group' .. listnum])
    end


     -- Add table row for the list
local function renderListRow(tbl, path, parentLevel)
     row = addTableRow(tbl)
     -- Initialize path and parentLevel
     local listCell = row:tag('td')
     path = path or ""
    listCell:attr('colspan', 2)
     parentLevel = parentLevel or 2 -- Start at level 2 for child groups
    listCell:css('width', '100%')
    listCell:cssText(args.liststyle)
    listCell:addClass('navboxMobile-list-content')


     -- Check for nested lists (e.g., child1_groupX)
     -- Keep track of rendered children to avoid duplicates
     local nestedGroup = args['child' .. listnum .. '_group1']
     local renderedChildren = {}
    local nestedList = args['child' .. listnum .. '_list1']


     if nestedGroup or nestedList then
     for i = 1, 10 do
         local nestedTbl = mw.html.create('table')
         local groupKey = (path ~= "" and "child" .. path .. "_" or "") .. "group" .. i
            :addClass('navboxMobile-subgroup')
        local listKey = (path ~= "" and "child" .. path .. "_" or "") .. "list" .. i
            :css('width', '100%')


         for i = 1, 10 do -- Assume a max of 10 nested groups/lists
         -- Skip if this group has already been rendered
            local nestedGroup = args['child' .. listnum .. '_group' .. i]
        if not renderedChildren[listKey] and args[listKey] then
             local nestedList = args['child' .. listnum .. '_list' .. i]
             renderedChildren[listKey] = true -- Track rendered lists


             if not nestedGroup and not nestedList then break end
             -- Render the group (heading)
            local groupRow = addTableRow(tbl)
            local groupCell = groupRow:tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-group-content')
                :addClass('navboxMobile-group-level' .. parentLevel) -- Correct level
                :attr('data-level', parentLevel) -- Optional attribute for CSS targeting
                :wikitext(processItem(args[groupKey]))


             local nestedRow = addTableRow(nestedTbl)
             -- Check if the list is non-empty before rendering content
 
             if args[listKey] and args[listKey] ~= "" then
             if nestedGroup then
                 if args[listKey] == "child" then
                 nestedRow:tag('th')
                     -- Check if the child contains valid groups or lists
                     :addClass('navboxMobile-group-content')
                    local childHasContent = false
                     :cssText(args.basestyle)
                     for j = 1, 10 do
                    :cssText(args['child' .. listnum .. '_group' .. i .. 'style'])
                        local childGroupKey = "child" .. (path ~= "" and path .. "_" or "") .. i .. "_group" .. j
                     :wikitext(nestedGroup)
                        local childListKey = "child" .. (path ~= "" and path .. "_" or "") .. i .. "_list" .. j
            end
                        if args[childGroupKey] or args[childListKey] then
                            childHasContent = true
                            break
                        end
                     end


            if nestedList then
                    if childHasContent then
                 nestedRow:tag('td')
                        -- Recursively render child lists
                    :addClass('navboxMobile-list-content')
                        local newPath = (path ~= "" and path .. "_" or "") .. i
                    :cssText(args['child' .. listnum .. '_list' .. i .. 'style'])
                        renderListRow(tbl, tostring(newPath), parentLevel + 1)
                    :wikitext(addNewline(nestedList))
                    end
                 else
                    -- Render flat lists
                    local listRow = addTableRow(tbl)
                    local listCell = listRow:tag('td')
                        :attr('colspan', 2)
                        :addClass('navboxMobile-list-content') -- Base content class
                        :addClass('navboxMobile-list-level' .. parentLevel) -- Correct level
                        :addClass((i % 2 == 1) and 'navboxMobile-odd' or 'navboxMobile-even') -- Odd/even class
                        :addClass(args.listclass or '') -- Apply `listclass` if defined in the template
                        :wikitext(processItem(args[listKey], args.nowrapitems))
                end
             end
             end
         end
         end
        listCell:node(nestedTbl)
    else
        listCell:wikitext(addNewline(args['list' .. listnum]))
     end
     end
end
end


--
--
Line 369: Line 399:
local function getTrackingCategories()
local function getTrackingCategories()
     local cats = {}
     local cats = {}
     if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
     if needsHorizontalLists() then table.insert(cats, cfg.category.horizontal_lists) end
     if hasBackgroundColors() then table.insert(cats, 'navboxMobiles using background colours') end
     if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end
     if isIllegible() then table.insert(cats, 'Potentially illegible navboxMobiles') end
     if isIllegible() then table.insert(cats, cfg.category.illegible) end
    if border == 'subgroup' then table.insert(cats, cfg.category.subgroup) end
    if args.tracking == 'no' then table.insert(cats, cfg.category.no_tracking) end
     return cats
     return cats
end
end
Line 392: Line 424:
     local tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
         :addClass('nowraplinks')
         :addClass('nowraplinks')
        :addClass(cfg.class.navbox)
         :addClass(args.bodyclass)
         :addClass(args.bodyclass)
        :addClass(args.listclass or '') -- Apply the `listclass` argument globally if applicable
        :cssText(args.bodystyle)
        :cssText(args.style)
        :css('margin-top', '0')
        :css('margin-bottom', '0')


     -- Uncomment and implement collapsibility if needed
     -- Add the title row
     --[[if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
     renderTitleRow(tbl)
        tbl
            :addClass('collapsible')
            :addClass(args.state or 'autocollapse')
    end]]


     tbl:css('border-spacing', 0)
     -- Add the above row
    if border == 'subgroup' or border == 'child' or border == 'none' then
     renderAboveRow(tbl)
        tbl
            :addClass('navboxMobile-subgroup')
            :cssText(args.bodystyle)
            :cssText(args.style)
     else -- Regular navbox - bodystyle and style will be applied to the wrapper table
        tbl
            :addClass('navboxMobile-inner')
            :css('background', 'transparent')
            :css('color', 'inherit')
    end
    tbl:cssText(args.innerstyle)


     renderTitleRow(tbl)
     -- Render all group and list rows
    renderAboveRow(tbl)
     for _, listnum in ipairs(listnums) do
     for _, listnum in ipairs(listnums) do
         renderListRow(tbl, listnum)
         local listKey = "list" .. listnum
        local groupKey = "group" .. listnum
 
        if args[listKey] then
            -- Render group row
            local groupRow = addTableRow(tbl)
            local groupCell = groupRow:tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-group-content')
                :addClass('navboxMobile-group-level1')
                :addClass(args.listclass or '') -- Apply `listclass` for group rows
                :wikitext(processItem(args[groupKey]))
 
            -- Render list row
            if args[listKey] == 'child' then
                renderListRow(tbl, tostring(listnum), 2)
            else
                local listRow = addTableRow(tbl)
                listRow:tag('td')
                    :attr('colspan', 2)
                    :addClass('navboxMobile-list-content')
                    :addClass((listnum % 2 == 1) and 'navboxMobile-odd' or 'navboxMobile-even')
                    :addClass(args.listclass or '') -- Apply `listclass` to flat rows
                    :wikitext(processItem(args[listKey]))
            end
        end
     end
     end
    -- Add the below row
     renderBelowRow(tbl)
     renderBelowRow(tbl)


Line 425: Line 474:
end
end


--
--  Main NavboxMobile Function
--
-- In the main navboxMobile function
function p._navboxMobile(navboxMobileArgs)
function p._navboxMobile(navboxMobileArgs)
     args = navboxMobileArgs
     args = navboxMobileArgs
Line 435: Line 490:
     local hiding_templatestyles = move_hiding_templatestyles(args)
     local hiding_templatestyles = move_hiding_templatestyles(args)


     -- Collect list numbers
     -- Collect top-level list numbers (list1, list2, etc.)
     for k, v in pairs(args) do
     for k, v in pairs(args) do
         if type(k) == 'string' then
         if type(k) == 'string' then
Line 454: Line 509:
     -- Create the final HTML with styles
     -- Create the final HTML with styles
     local res = mw.html.create()
     local res = mw.html.create()
    -- Inject styles analogous to Module:Navbox
     res:node(add_navbox_mobile_styles(hiding_templatestyles))
     res:node(add_navbox_mobile_styles(hiding_templatestyles))
 
     res:node(tbl)
     -- Handle wrapping based on border parameter
    if border == 'none' then
        -- Wrap the table within a navigation div with ARIA attributes
        local navWrapper = mw.html.create('div')
            :attr('role', 'navigation')
            :attr('aria-label', cfg.aria_label)
            :node(tbl)
        res:node(navWrapper)
    elseif border == 'subgroup' or border == 'child' then
        -- Assume this navboxMobile is inside a parent navboxMobile's list cell
        -- Insert closing and opening divs to manage padding
        res
            :wikitext('</div>') -- Close parent div
            :node(tbl)
            :wikitext('<div>') -- Reopen parent div
    else
        -- Wrap the table within a navigation div with additional classes and styles
        local navWrapper = mw.html.create('div')
            :attr('role', 'navigation')
            :addClass('navboxMobile') -- Add appropriate classes
            :cssText(args.bodystyle)
            :cssText(args.style)
            :css('padding', '2px') -- Adjust padding as needed
            :node(tbl)
        res:node(navWrapper)
    end
 
    -- Render tracking categories
    renderTrackingCategories(res)


     return tostring(res)
     return tostring(res)
end
end


--
--  Main NavboxMobile Function
--
function p.navboxMobile(frame)
function p.navboxMobile(frame)
     if not getArgs then
     if not getArgs then

Latest revision as of 06:13, 27 November 2024

This module implements the {{Navbox}} template for mobile devices when Extension:MobileFrontend is installed. This mobile version differs from the desktop version as follows:

  • The table does not collapse.
  • The list cells, e.g. group1, list1, each display across the width of the screen
  • Images are not displayed, as it increases the complexity of the layout while reducing the area needed for content.

Original from wikipedia::Module:User:Lady G2016/NavboxMobile/doc

Overview[edit source]

This approach works by utilizing CSS class rules. See: Hiding content on desktop devices

An onlymobile class in MediaWiki:Common.css will not display classes intended for mobile devices.

A nomobile class in MediaWiki:Mobile.css will not display classes intended for desktop devices.

Use these classes together inside the same template. The template will be rendered twice, but only display once depending on the view (desktop or mobile).

Module description[edit source]

The module was copied from Module:Navbox and modified as follows:

  • All instances of navbox- were replaced with navboxMobile- to provide unique CSS identifiers that are independent of navbox.
  • All statements which processed images were removed. The statements remain as comments to show what was changed.

In function renderListRow(), the cell pair of (groupCell, listCell) appearing as a single row is broken into separate rows by adding local row = addTableRow(tbl) just before listCell.

Adding this row and removing the images are the only functional changes made to the existing navbox template. Except for removal of the images, the parameters used in the current navbox are supported.

Implementation[edit source]

MediaWiki:Common.css[edit source]

In MediaWiki:Common.css, add:

/* Only for mobile devices */
.onlymobile {
display:none;
}

MediaWiki:Mobile.css[edit source]

In MediaWiki:Mobile.css, add:

/* Only for desktop devices */
.nomobile {
display:none;
}

MobileFrontend processes MediaWiki:Mobile.css. To provide an independent class that is not affected by the navbox rules, copy all of the navbox and hlist CSS styles from MediaWiki:Common.css to MediaWiki:Mobile.css.

Replace all navbox- with navboxMobile-. For example, navbox-title becomes navboxMobile-title. This will match the CSS properties in Module:NavboxMobile.

An example page can be found at User:Lady G2016/MediaWiki:Mobile.css.

Template:Navbox[edit source]

Modify Template:Navbox to call both the desktop and mobile Lua modules as follows:

<div class="nomobile">{{#invoke:Navbox|navbox}}</div><div class="onlymobile">{{#invoke:NavboxMobile|navboxMobile}}</div><noinclude>
{{Documentation}}
</noinclude>

This will render both the desktop (navbox) and mobile (navboxMobile) versions. When displaying from a desktop view, MediaWiki:Common.css will not display the mobile template. When displaying from a mobile, only the mobile template will be displayed.

Constraints and limitations[edit source]

This template was developed with MediaWiki 1.27.4 (Long Term Support) and MobileFrontEnd 1.0.0 (29 June 2016). No testing was performed with other versions.

The navbox class is explicitly disabled in /extensions/MobileFrontend/resources/skins.minerva.content.styles/hacks.less. Attempts to override navbox using $wgMFRemovableClasses do not display as intended when the navbox class is enabled.


-- Module:NavboxMobile
-- This module implements {{navbox}} for MobileFrontend

local p = {}

require('strict')
local cfg = mw.loadData('Module:NavboxMobile/configuration')
local navbar = require('Module:Navbar')._navbar
local getArgs -- Lazily initialized
local inArray = require('Module:TableTools').inArray
local format = string.format

local args
local tableRowAdded = false
local border
local listnums = {}

-- Function to check if the given HTML class is a list class
local function has_list_class(htmlclass)
    local patterns = {
        '^' .. htmlclass .. '$',
        '%s' .. htmlclass .. '$',
        '^' .. htmlclass .. '%s',
        '%s' .. htmlclass .. '%s'
    }
    
    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
    return false
end

-- Function to check if the navbar is enabled
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


local function add_list_styles()
    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

-- Analogous function to add_navbox_styles from Module:Navbox
local function add_navbox_mobile_styles(hiding_templatestyles)
    local frame = mw.getCurrentFrame()
    
    -- Function to add user-defined templatestyles
    local function add_user_styles(templatestyles)
        if templatestyles and templatestyles ~= '' then
            return frame:extensionTag{
                name = 'templatestyles',
                args = { src = templatestyles }
            }
        end
        return ''
    end

    -- Combine list styles with base and user styles
    local list_styles = add_list_styles()
    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])

    -- Combine all styles into a single <div>
    return mw.html.create('div')
        :addClass(cfg.class.navbox_styles)
        :wikitext(
            list_styles ..
            base_templatestyles ..
            templatestyles ..
            child_templatestyles ..
            table.concat(hiding_templatestyles)
        )
        :done()
end

-- Work around for style markers (similar to Navbox)
local function move_hiding_templatestyles(args)
    local gfind = string.gmatch
    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
    return templatestyles_markers
end

-- Helper function to trim whitespace
local function trim(s)
    return (mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1"))
end

-- Helper function to add newlines based on content
local function addNewline(s)
    if s:match('^[*:;#]') or s:match('^{|') then
        return '\n' .. s ..'\n'
    else
        return s
    end
end


local function processItem(item, nowrapitems)
    if not item then
        return '' -- Prevent nil errors
    end
    if item:sub(1, 2) == '{|' then
        -- Applying nowrap to lines in a table does not make sense.
        -- Add newlines to compensate for trim of x in |parm=x in a template.
        item = item:gsub('|%s*width%s*=%s*"[%d%w]+"', '| width="100%"')
        return '\n' .. item .. '\n'
    end
    if nowrapitems == cfg.keyword.nowrapitems_yes or true then
        local lines = {}
        for line in (item .. '\n'):gmatch('([^\n]*)\n') do
            local prefix, content = line:match('^([*:;#]+)%s*(.*)')
            if prefix and not content:match(cfg.pattern.nowrap) then
                line = format(cfg.nowrap_item, prefix, content)
            end
            table.insert(lines, line)
        end
        item = table.concat(lines, '\n')
    end
    if item:match('^[*:;#]') then
        return '\n' .. item .. '\n'
    end
    -- Ensure no excessive whitespace
    item = mw.ustring.gsub(item, "%s+", " ")
    return item
end


-- Function to add a new table row with optional gutter
local function addTableRow(tbl)
    -- Check if the spacer rows are necessary based on the listclass or bodyclass
    local suppressSpacer = false
    if args.listclass then
        suppressSpacer = suppressSpacer or args.listclass:find("hlist")
    end
    if args.bodyclass then
        suppressSpacer = suppressSpacer or args.bodyclass:find("hlist")
    end

    -- Only add spacer rows if they are not explicitly suppressed
    if tableRowAdded and not suppressSpacer then
        tbl:tag('tr')
            :css('height', '2px')
            :tag('td')
                :attr('colspan', 2)
                :css('background-color', 'transparent') -- Ensure spacer is invisible
    end

    tableRowAdded = true
    return tbl:tag('tr')
end

-- Function to render the navigation bar
local function renderNavBar(cell)
    if args.navbar == 'off' then
        return -- Don't render anything if Navbar is off
    end

    -- Render the Navbar
    cell:wikitext(navbar{
        args.name,
        mini = 1,
        fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;'
    })
end


--
--   Title row
--
local function renderTitleRow(tbl)
    if not args.title then return end

    local titleRow = addTableRow(tbl)
    local titleCell = titleRow:tag('th'):attr('scope', 'col')
        :addClass('navboxMobile-title')
        :addClass(cfg.class.navbox_list)
        :addClass(args[cfg.arg.titleclass])
        :attr('data-level', 1)
        :attr('colspan', 2)
        :cssText(args.basestyle)
        :cssText(args.titlestyle)
        :css('position', 'relative')  -- Ensure relative positioning for absolute children

    -- Add the title content, centered across the full width
    titleCell
        :tag('div')
            :css('margin', '0 auto')
            :css('text-align', 'center')
            :wikitext(addNewline(args.title))

    -- Add the navbar, positioned absolutely in the top-right corner
    if has_navbar() then
        titleCell
            :tag('div')
                :addClass('navboxMobile-navbar')
                :css('position', 'absolute')
                :css('top', '0')
                :css('right', '0')
                :css('padding', '0.2em')  -- Optional: Adjust padding for alignment
                :wikitext(navbar{
                    args.name,
                    mini = 1,
                    fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;'
                })
    end
end

--
--   Above/Below rows
--

local function getAboveBelowColspan()
    return 2
end

local function renderAboveRow(tbl)
    if not args.above then return end

    addTableRow(tbl)
        :tag('td')
            :addClass('navboxMobile-abovebelow-content')
            :addClass(cfg.class.navbox_list) -- Add consistent list styling
            :addClass(args.aboveclass)
            :cssText(args.basestyle)
            :cssText(args.abovestyle)
            :css('padding', args[cfg.arg.abovepadding] or '0.5em') -- Optional padding
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.above))
end



local function renderBelowRow(tbl)
    if not args.below then return end

    addTableRow(tbl)
        :tag('td')
            :addClass('navboxMobile-abovebelow-content')
            :addClass(cfg.class.navbox_list) -- Add consistent list styling
            :addClass(args.belowclass)
            :cssText(args.basestyle)
            :cssText(args.belowstyle)
            :css('padding', args[cfg.arg.belowpadding] or '0.5em') -- Optional padding
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.below))
end



local function renderListRow(tbl, path, parentLevel)
    -- Initialize path and parentLevel
    path = path or ""
    parentLevel = parentLevel or 2 -- Start at level 2 for child groups

    -- Keep track of rendered children to avoid duplicates
    local renderedChildren = {}

    for i = 1, 10 do
        local groupKey = (path ~= "" and "child" .. path .. "_" or "") .. "group" .. i
        local listKey = (path ~= "" and "child" .. path .. "_" or "") .. "list" .. i

        -- Skip if this group has already been rendered
        if not renderedChildren[listKey] and args[listKey] then
            renderedChildren[listKey] = true -- Track rendered lists

            -- Render the group (heading)
            local groupRow = addTableRow(tbl)
            local groupCell = groupRow:tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-group-content')
                :addClass('navboxMobile-group-level' .. parentLevel) -- Correct level
                :attr('data-level', parentLevel) -- Optional attribute for CSS targeting
                :wikitext(processItem(args[groupKey]))

            -- Check if the list is non-empty before rendering content
            if args[listKey] and args[listKey] ~= "" then
                if args[listKey] == "child" then
                    -- Check if the child contains valid groups or lists
                    local childHasContent = false
                    for j = 1, 10 do
                        local childGroupKey = "child" .. (path ~= "" and path .. "_" or "") .. i .. "_group" .. j
                        local childListKey = "child" .. (path ~= "" and path .. "_" or "") .. i .. "_list" .. j
                        if args[childGroupKey] or args[childListKey] then
                            childHasContent = true
                            break
                        end
                    end

                    if childHasContent then
                        -- Recursively render child lists
                        local newPath = (path ~= "" and path .. "_" or "") .. i
                        renderListRow(tbl, tostring(newPath), parentLevel + 1)
                    end
                else
                    -- Render flat lists
                    local listRow = addTableRow(tbl)
                    local listCell = listRow:tag('td')
                        :attr('colspan', 2)
                        :addClass('navboxMobile-list-content') -- Base content class
                        :addClass('navboxMobile-list-level' .. parentLevel) -- Correct level
                        :addClass((i % 2 == 1) and 'navboxMobile-odd' or 'navboxMobile-even') -- Odd/even class
                        :addClass(args.listclass or '') -- Apply `listclass` if defined in the template
                        :wikitext(processItem(args[listKey], args.nowrapitems))
                end
            end
        end
    end
end





--
--   Tracking categories
--

local function needsHorizontalLists()
    if border == 'child' or border == 'subgroup'  or args.tracking == 'no' then return false end

    local listClasses = {'plainlist', 'hlist', 'hlist hnum', 'hlist hwrap', 'hlist vcard', 'vcard hlist', 'hlist vevent'}
    for _, cls in ipairs(listClasses) do
        if args.listclass == cls or args.bodyclass == cls then
            return false
        end
    end

    return true
end

local function hasBackgroundColors()
    return mw.ustring.match(args.titlestyle or '', 'background') 
        or mw.ustring.match(args.groupstyle or '', 'background') 
        or mw.ustring.match(args.basestyle or '', 'background')
end

local function isIllegible()
    local styleratio = require('Module:Color contrast')._styleratio

    for key, style in pairs(args) do
        if tostring(key):match("style$") then
            if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
                return true 
            end
        end
    end
    return false
end

local function getTrackingCategories()
    local cats = {}
    if needsHorizontalLists() then table.insert(cats, cfg.category.horizontal_lists) end
    if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end
    if isIllegible() then table.insert(cats, cfg.category.illegible) end
    if border == 'subgroup' then table.insert(cats, cfg.category.subgroup) end
    if args.tracking == 'no' then table.insert(cats, cfg.category.no_tracking) end
    return cats
end

local function renderTrackingCategories(builder)
    local title = mw.title.getCurrentTitle()
    if title.namespace ~= 10 then return end -- Not in template space
    local subpage = title.subpageText
    if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end

    for _, cat in ipairs(getTrackingCategories()) do
        builder:wikitext('[[Category:' .. cat .. ']]')
    end
end

--
--   Main navboxMobile tables
--
local function renderMainTable()
    local tbl = mw.html.create('table')
        :addClass('nowraplinks')
        :addClass(cfg.class.navbox)
        :addClass(args.bodyclass)
        :addClass(args.listclass or '') -- Apply the `listclass` argument globally if applicable
        :cssText(args.bodystyle)
        :cssText(args.style)
        :css('margin-top', '0')
        :css('margin-bottom', '0')

    -- Add the title row
    renderTitleRow(tbl)

    -- Add the above row
    renderAboveRow(tbl)

    -- Render all group and list rows
    for _, listnum in ipairs(listnums) do
        local listKey = "list" .. listnum
        local groupKey = "group" .. listnum

        if args[listKey] then
            -- Render group row
            local groupRow = addTableRow(tbl)
            local groupCell = groupRow:tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-group-content')
                :addClass('navboxMobile-group-level1')
                :addClass(args.listclass or '') -- Apply `listclass` for group rows
                :wikitext(processItem(args[groupKey]))

            -- Render list row
            if args[listKey] == 'child' then
                renderListRow(tbl, tostring(listnum), 2)
            else
                local listRow = addTableRow(tbl)
                listRow:tag('td')
                    :attr('colspan', 2)
                    :addClass('navboxMobile-list-content')
                    :addClass((listnum % 2 == 1) and 'navboxMobile-odd' or 'navboxMobile-even')
                    :addClass(args.listclass or '') -- Apply `listclass` to flat rows
                    :wikitext(processItem(args[listKey]))
            end
        end
    end

    -- Add the below row
    renderBelowRow(tbl)

    return tbl
end



--
--   Main NavboxMobile Function
--
-- In the main navboxMobile function
function p._navboxMobile(navboxMobileArgs)
    args = navboxMobileArgs

    -- Reset state variables to prevent state leakage between invocations
    tableRowAdded = false
    listnums = {}

    -- Move and collect hiding templatestyles
    local hiding_templatestyles = move_hiding_templatestyles(args)

    -- Collect top-level list numbers (list1, list2, etc.)
    for k, v in pairs(args) do
        if type(k) == 'string' then
            local listnum = k:match('^list(%d+)$')
            if listnum then table.insert(listnums, tonumber(listnum)) end
        end
    end
    table.sort(listnums)

    border = trim(args.border or args[1] or '')
    if border == cfg.keyword.border_child then
        border = cfg.keyword.border_subgroup
    end

    -- Render the main body of the navboxMobile
    local tbl = renderMainTable()

    -- Create the final HTML with styles
    local res = mw.html.create()
    res:node(add_navbox_mobile_styles(hiding_templatestyles))
    res:node(tbl)

    return tostring(res)
end


--
--   Main NavboxMobile Function
--
function p.navboxMobile(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    args = getArgs(frame, {wrappers = 'Template:Navbox'})

    -- Read the arguments in the order they'll be output in, to make references number in the right order.
    local _
    _ = args.title
    _ = args.above
    for i = 1, 20 do
        _ = args["group" .. tostring(i)]
        _ = args["list" .. tostring(i)]
    end
    _ = args.below

    return p._navboxMobile(args)
end

return p