Add Archival Links section to infobox, querying Accessory link subobjects (via update-page on MediaWiki MCP Server)
Change type badge to breadcrumb style: Accessory › [accessory_type] (via update-page on MediaWiki MCP Server)
 
(6 intermediate revisions by the same user not shown)
Line 72: Line 72:
local description = trim(args.description)
local description = trim(args.description)
local image_main = trim(args.image_main)
local image_main = trim(args.image_main)
-- Physical specifications
local height = trim(args.height)
local width = trim(args.width)
local depth = trim(args.depth)
local weight = trim(args.weight)
-- Option card specific
-- Option card specific
Line 95: Line 101:
'infobox-title'
'infobox-title'
)
)
-- Type badge (breadcrumb)
local badgeWikitext = '[[:Category:Accessories|Accessory]]'
if accessory_type then
badgeWikitext = badgeWikitext .. ' › ' .. accessory_type
end
addFullRow(container, badgeWikitext, '', false, 'infobox-type-badge')


if image_main then
if image_main then
Line 143: Line 156:
local ecidWikitext = string.format('[{{fullurl:%s|curid=%d}} EC%d]', pageName, pageId, pageId)
local ecidWikitext = string.format('[{{fullurl:%s|curid=%d}} EC%d]', pageName, pageId, pageId)
addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)
addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)
-- Dimensions section
if height or width or depth or weight then
addSectionRow(container, 'Dimensions')
if height or width or depth then
local h = height or '–'
local w = width or '–'
local d = depth or '–'
addRow(container, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
end
if weight then
local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
addRow(container, 'Weight', frame:preprocess(weightWikitext), true)
end
end


-- Compatible CRT models section
-- Compatible CRT models section
local crtCountWikitext =
-- Collect CRT model names from both sources: crt_models field and forward Has accessory links
local crtNames = {}
local crtSeen = {}
-- Source 1: crt_models field on this accessory page
local crt_models = trim(args.crt_models)
if crt_models then
for crt in crt_models:gmatch('[^,]+') do
local c = trim(crt)
if c and not crtSeen[c] then
crtSeen[c] = true
table.insert(crtNames, c)
end
end
end
-- Source 2: CRT models that reference this accessory via Has accessory (e.g. remote_control field)
local forwardQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Category:CRT models]]\n' ..
' [[Category:CRT models]]\n' ..
' [[Has accessory::' .. fullPageName .. ']]\n' ..
' [[Has accessory::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
'}}'
local crtCountFromForward = frame:preprocess(crtCountWikitext)
local forwardResult = frame:preprocess(forwardQueryWikitext)
-- Also check reverse links (Has related CRT model set on this accessory page)
if forwardResult then
local crtReverseCountWikitext =
for rowContent in forwardResult:gmatch('<tr[^>]*>(.-)</tr>') do
'{{#ask:\n' ..
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
' [[Category:CRT models]]\n' ..
local cleanContent = cellContent:gsub('<[^>]+>', '')
' [[Has related accessory::' .. fullPageName .. ']]\n' ..
local c = trim(cleanContent)
' |format=count\n' ..
if c and not crtSeen[c] then
'}}'
crtSeen[c] = true
local crtCountFromReverse = frame:preprocess(crtReverseCountWikitext)
table.insert(crtNames, c)
end
-- Query for CRTs that have this accessory via forward link (remote_control field)
break -- only need first cell (page name)
-- OR via Has related CRT model property set on this accessory
end
local hasCRTs = (tonumber(trim(crtCountFromForward)) or 0) > 0
end
end
-- Also query CRTs listed in the accessory's crt_models field
local crt_models = trim(args.crt_models)
local hasCRTsFromField = crt_models and crt_models ~= ''
if hasCRTs or hasCRTsFromField then
if #crtNames > 0 then
addSectionRow(container, 'Compatible CRT Models')
addSectionRow(container, 'Compatible CRT Models')
Line 176: Line 222:
css(crtDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
css(crtDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
-- Query CRTs that reference this accessory via Has accessory property
local ul = crtDiv:tag('ul')
-- (This handles forward references like remote_control field)
ul:css('margin', '0'):css('padding-left', '1.5em')
local crtQueryWikitext =
'{{#ask:\n' ..
' [[Category:CRT models]]\n' ..
' [[Has accessory::' .. fullPageName .. ']]\n' ..
' |?Has image#70px;x70px=Photo\n' ..
' |?Has model code=Model\n' ..
' |?Has screen size inches=Size\n' ..
' |format=table\n' ..
' |class=wikitable\n' ..
' |mainlabel=CRT model\n' ..
' |link=subject\n' ..
'}}'
-- If we have CRTs from the crt_models field, also query those
for _, crtName in ipairs(crtNames) do
if hasCRTsFromField then
local li = ul:tag('li')
-- Parse crt_models and create OR conditions
li:wikitext('[[' .. crtName .. ']]')
local crtConditions = {}
for crt in crt_models:gmatch('[^,]+') do
local c = trim(crt)
if c then
table.insert(crtConditions, '[[' .. c .. ']]')
end
end
if #crtConditions > 0 then
crtQueryWikitext =
'{{#ask:\n' ..
' [[Category:CRT models]]\n' ..
' <q>' .. table.concat(crtConditions, ' OR ') .. '</q>\n' ..
' |?Has image#70px;x70px=Photo\n' ..
' |?Has model code=Model\n' ..
' |?Has screen size inches=Size\n' ..
' |format=table\n' ..
' |class=wikitable\n' ..
' |mainlabel=CRT model\n' ..
' |link=subject\n' ..
'}}'
end
end
end
local crtHtml = frame:preprocess(crtQueryWikitext)
crtDiv:wikitext(crtHtml)
end
end


Line 225: Line 234:
local av_devices = trim(args.av_devices)
local av_devices = trim(args.av_devices)
if av_devices and av_devices ~= '' then
if av_devices and av_devices ~= '' then
addSectionRow(container, 'Compatible AV Devices')
local avDiv = container:tag('div')
css(avDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
-- Parse av_devices and create OR conditions
-- Parse av_devices and create OR conditions
local avNames = {}
local avConditions = {}
local avConditions = {}
for av in av_devices:gmatch('[^,]+') do
for av in av_devices:gmatch('[^,]+') do
local a = trim(av)
local a = trim(av)
if a then
if a then
table.insert(avNames, a)
table.insert(avConditions, '[[' .. a .. ']]')
table.insert(avConditions, '[[' .. a .. ']]')
end
end
Line 240: Line 246:
if #avConditions > 0 then
if #avConditions > 0 then
-- Query for device types
local avQueryWikitext =
local avQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Category:AV devices]]\n' ..
' [[Category:AV devices]]\n' ..
' <q>' .. table.concat(avConditions, ' OR ') .. '</q>\n' ..
' <q>' .. table.concat(avConditions, ' OR ') .. '</q>\n' ..
' |?Has image#70px;x70px=Photo\n' ..
' |?Has device type\n' ..
' |?Has model code=Model\n' ..
' |format=broadtable\n' ..
' |?Has device type=Type\n' ..
' |headers=hide\n' ..
' |format=table\n' ..
' |link=none\n' ..
' |class=wikitable\n' ..
' |mainlabel=AV device\n' ..
' |link=subject\n' ..
'}}'
'}}'
local avHtml = frame:preprocess(avQueryWikitext)
local avResult = frame:preprocess(avQueryWikitext)
avDiv:wikitext(avHtml)
-- Parse results into a lookup of page name -> device type
local deviceTypes = {}
if avResult then
for rowContent in avResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
end
-- cells[1] = page name, cells[2] = device type
if #cells >= 2 and cells[1] and cells[2] then
deviceTypes[cells[1]] = cells[2]
end
end
end
-- Group device names by type
local byType = {}
local typeOrder = {}
local typeSeen = {}
for _, avName in ipairs(avNames) do
local dtype = deviceTypes[avName] or 'Unknown'
if not typeSeen[dtype] then
typeSeen[dtype] = true
table.insert(typeOrder, dtype)
byType[dtype] = {}
end
table.insert(byType[dtype], avName)
end
if #typeOrder > 0 then
addSectionRow(container, 'Compatible AV Devices')
for _, dtype in ipairs(typeOrder) do
local deviceLinks = {}
for _, avName in ipairs(byType[dtype]) do
table.insert(deviceLinks, '[[' .. avName .. ']]')
end
addRow(container, dtype, table.concat(deviceLinks, '<br>'), true)
end
end
end
end
end
end
Line 260: Line 306:
addSectionRow(container, 'Documents')
addSectionRow(container, 'Documents')
-- Build upload link
-- Build upload link with prefilled accessory
local timestamp = os.time()
local timestamp = os.time()
math.randomseed(timestamp + pageId)
math.randomseed(timestamp + pageId)
local random = math.random(1000, 9999)
local random = math.random(1000, 9999)
local target = "Document:" .. timestamp .. "-" .. random
local target = "Document:" .. timestamp .. "-" .. random
local uploadUrl = mw.uri.fullUrl(
local uploadUrl = mw.uri.fullUrl(
"Special:FormEdit",
"Special:FormEdit",
{
{
form = "Document",
form = "Document",
target = target
target = target,
["Document[accessories]"] = pageName
}
}
)
)
-- Query for document count
local documentsCountWikitext =
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related accessory::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local documentCount = frame:preprocess(documentsCountWikitext)
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
-- Create the content div
local docsDiv = container:tag('div')
local docsDiv = container:tag('div')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
docsDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
if hasDocuments then
-- Use #ask to get documents
local docsQueryWikitext =
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related accessory::' .. fullPageName .. ']]\n' ..
' |?Has document type\n' ..
' |?Has document URL\n' ..
' |?Has document file\n' ..
' |?Has document title\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local docsResult = frame:preprocess(docsQueryWikitext)
-- Create UL element
local ul = mw.html.create('ul')
ul:css('margin', '0'):css('padding-left', '1.5em')
-- Parse the HTML table
if docsResult then
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, cleanContent)
end
-- cells[1] = page name, cells[2] = type, cells[3] = URL, cells[4] = file, cells[5] = title
if #cells >= 2 then
local docType = trim(cells[2])
local docTitle = trim(cells[5])
local docPageName = trim(cells[1])
if docPageName then
local displayText = 'Document'
if docTitle then
displayText = docTitle
elseif docType then
displayText = docType
end
local li = ul:tag('li')
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
end
end
end
end
docsDiv:node(ul)
-- Add upload link as a button
local uploadDiv = mw.html.create('div')
uploadDiv:css('margin-top', '0.5em'):css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
docsDiv:node(uploadDiv)
else
-- Just show the upload link as a button
local uploadDiv = mw.html.create('div')
uploadDiv:css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
docsDiv:node(uploadDiv)
end


-- Archival Links section: Query for link subobjects and display by type
-- Archival Links section: Query for link subobjects and display by type

Latest revision as of 17:20, 16 February 2026

Documentation for this module may be created at Module:Accessory/doc

-- Module:Accessory
-- Builds the Accessory infobox (remotes, stands, option cards, etc.)

local p = {}

local function trim(s)
	if s == nil then return nil end
	s = tostring(s)
	s = s:gsub('^%s+', ''):gsub('%s+$', '')
	if s == '' then return nil end
	return s
end

local function css(node, cssText)
	if cssText and cssText ~= '' then
		node:cssText(cssText)
	end
	return node
end

local function addSectionRow(container, title)
	local section = container:tag('div')
	section:addClass('infobox-section-header')
	css(section, 'grid-column: 1 / -1; text-align:center; padding:0.5em; font-weight:bold;')
	section:wikitext(title)
end

local function addRow(container, label, valueHtmlOrWikitext, isRawHtml)
	local labelDiv = container:tag('div')
	labelDiv:addClass('infobox-label')
	css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
	labelDiv:wikitext(label)

	local valueDiv = container:tag('div')
	valueDiv:addClass('infobox-value')
	css(valueDiv, 'padding:0.2em 0.4em;')

	if isRawHtml then
		valueDiv:node(mw.html.create('span'):wikitext(valueHtmlOrWikitext))
	else
		valueDiv:wikitext(valueHtmlOrWikitext)
	end
end

local function addFullRow(container, valueHtmlOrWikitext, cssText, isRawHtml, extraClass)
	local fullDiv = container:tag('div')
	css(fullDiv, 'grid-column: 1 / -1; ' .. (cssText or ''))
	if extraClass then
		fullDiv:addClass(extraClass)
	end

	if isRawHtml then
		fullDiv:wikitext(valueHtmlOrWikitext)
	else
		fullDiv:wikitext(valueHtmlOrWikitext)
	end
end

function p.infobox(frame)
	local parent = frame:getParent()
	local args = parent and parent.args or frame.args

	local title = mw.title.getCurrentTitle()
	local pageName = title.text
	local fullPageName = title.fullText
	local pageId = title.id or 0

	-- Core fields
	local brand = trim(args.brand)
	local model_code = trim(args.model_code)
	local accessory_type = trim(args.accessory_type)
	local description = trim(args.description)
	local image_main = trim(args.image_main)
	
	-- Physical specifications
	local height = trim(args.height)
	local width = trim(args.width)
	local depth = trim(args.depth)
	local weight = trim(args.weight)
	
	-- Option card specific
	local card_signals = trim(args.card_signals)
	
	-- Media specific
	local media_format = trim(args.media_format)

	-- Load stylesheet
	local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'})

	local container = mw.html.create('div')
	container:addClass('infobox')
	container:addClass('infobox-accessory')
	css(container, 'float:right; clear:right; margin:0 0 1em 1em; width:22em; font-size:88%; line-height:1.5em; display:grid; grid-template-columns:auto 1fr; gap:3px; padding:3px;')

	-- Title row
	addFullRow(
		container,
		(brand or '') .. ' ' .. (model_code or ''),
		'text-align:center; font-size:125%; font-weight:bold; padding:0.5em;',
		false,
		'infobox-title'
	)

	-- Type badge (breadcrumb)
	local badgeWikitext = '[[:Category:Accessories|Accessory]]'
	if accessory_type then
		badgeWikitext = badgeWikitext .. ' › ' .. accessory_type
	end
	addFullRow(container, badgeWikitext, '', false, 'infobox-type-badge')

	if image_main then
		local file = image_main:gsub('^File:', '')
		addFullRow(
			container,
			string.format('[[File:%s|frameless|300px]]', file),
			'text-align:center; padding:0;',
			false,
			'infobox-image'
		)
	end

	addSectionRow(container, 'Overview')

	-- Brand row using #formredlink
	if brand then
		local brandWikitext = string.format(
			'{{#formredlink: target=%s |form=Manufacturer |existing page link text=%s }}',
			brand, brand
		)
		local brandHtml = frame:preprocess(brandWikitext)
		addRow(container, 'Brand', brandHtml, true)
	end

	if model_code then
		addRow(container, 'Model', model_code, false)
	end

	if accessory_type then
		addRow(container, 'Type', accessory_type, false)
	end

	if description then
		addRow(container, 'Description', description, false)
	end

	-- Type-specific fields
	if accessory_type == 'Option card' and card_signals then
		addRow(container, 'Signals', card_signals, false)
	end

	if accessory_type == 'Media' and media_format then
		addRow(container, 'Format', media_format, false)
	end

	-- ECID row
	local ecidWikitext = string.format('[{{fullurl:%s|curid=%d}} EC%d]', pageName, pageId, pageId)
	addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)

	-- Dimensions section
	if height or width or depth or weight then
		addSectionRow(container, 'Dimensions')
		
		if height or width or depth then
			local h = height or '–'
			local w = width or '–'
			local d = depth or '–'
			addRow(container, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
		end
		
		if weight then
			local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
			addRow(container, 'Weight', frame:preprocess(weightWikitext), true)
		end
	end

	-- Compatible CRT models section
	-- Collect CRT model names from both sources: crt_models field and forward Has accessory links
	local crtNames = {}
	local crtSeen = {}
	
	-- Source 1: crt_models field on this accessory page
	local crt_models = trim(args.crt_models)
	if crt_models then
		for crt in crt_models:gmatch('[^,]+') do
			local c = trim(crt)
			if c and not crtSeen[c] then
				crtSeen[c] = true
				table.insert(crtNames, c)
			end
		end
	end
	
	-- Source 2: CRT models that reference this accessory via Has accessory (e.g. remote_control field)
	local forwardQueryWikitext =
		'{{#ask:\n' ..
		' [[Category:CRT models]]\n' ..
		' [[Has accessory::' .. fullPageName .. ']]\n' ..
		' |format=broadtable\n' ..
		' |headers=hide\n' ..
		' |link=none\n' ..
		'}}'
	local forwardResult = frame:preprocess(forwardQueryWikitext)
	
	if forwardResult then
		for rowContent in forwardResult:gmatch('<tr[^>]*>(.-)</tr>') do
			for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
				local cleanContent = cellContent:gsub('<[^>]+>', '')
				local c = trim(cleanContent)
				if c and not crtSeen[c] then
					crtSeen[c] = true
					table.insert(crtNames, c)
				end
				break -- only need first cell (page name)
			end
		end
	end
	
	if #crtNames > 0 then
		addSectionRow(container, 'Compatible CRT Models')
		
		local crtDiv = container:tag('div')
		css(crtDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
		
		local ul = crtDiv:tag('ul')
		ul:css('margin', '0'):css('padding-left', '1.5em')
		
		for _, crtName in ipairs(crtNames) do
			local li = ul:tag('li')
			li:wikitext('[[' .. crtName .. ']]')
		end
	end

	-- Compatible AV devices section
	local av_devices = trim(args.av_devices)
	if av_devices and av_devices ~= '' then
		-- Parse av_devices and create OR conditions
		local avNames = {}
		local avConditions = {}
		for av in av_devices:gmatch('[^,]+') do
			local a = trim(av)
			if a then
				table.insert(avNames, a)
				table.insert(avConditions, '[[' .. a .. ']]')
			end
		end
		
		if #avConditions > 0 then
			-- Query for device types
			local avQueryWikitext =
				'{{#ask:\n' ..
				' [[Category:AV devices]]\n' ..
				' <q>' .. table.concat(avConditions, ' OR ') .. '</q>\n' ..
				' |?Has device type\n' ..
				' |format=broadtable\n' ..
				' |headers=hide\n' ..
				' |link=none\n' ..
				'}}'
			local avResult = frame:preprocess(avQueryWikitext)
			
			-- Parse results into a lookup of page name -> device type
			local deviceTypes = {}
			if avResult then
				for rowContent in avResult:gmatch('<tr[^>]*>(.-)</tr>') do
					local cells = {}
					for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
						local cleanContent = cellContent:gsub('<[^>]+>', '')
						table.insert(cells, trim(cleanContent))
					end
					-- cells[1] = page name, cells[2] = device type
					if #cells >= 2 and cells[1] and cells[2] then
						deviceTypes[cells[1]] = cells[2]
					end
				end
			end
			
			-- Group device names by type
			local byType = {}
			local typeOrder = {}
			local typeSeen = {}
			
			for _, avName in ipairs(avNames) do
				local dtype = deviceTypes[avName] or 'Unknown'
				if not typeSeen[dtype] then
					typeSeen[dtype] = true
					table.insert(typeOrder, dtype)
					byType[dtype] = {}
				end
				table.insert(byType[dtype], avName)
			end
			
			if #typeOrder > 0 then
				addSectionRow(container, 'Compatible AV Devices')
				
				for _, dtype in ipairs(typeOrder) do
					local deviceLinks = {}
					for _, avName in ipairs(byType[dtype]) do
						table.insert(deviceLinks, '[[' .. avName .. ']]')
					end
					addRow(container, dtype, table.concat(deviceLinks, '<br>'), true)
				end
			end
		end
	end

	-- Documents section
	addSectionRow(container, 'Documents')
	
	-- Build upload link with prefilled accessory
	local timestamp = os.time()
	math.randomseed(timestamp + pageId)
	local random = math.random(1000, 9999)
	local target = "Document:" .. timestamp .. "-" .. random
	local uploadUrl = mw.uri.fullUrl(
		"Special:FormEdit",
		{
			form = "Document",
			target = target,
			["Document[accessories]"] = pageName
		}
	)
	
	-- Query for document count
	local documentsCountWikitext =
		'{{#ask:\n' ..
		' [[Category:Documents]]\n' ..
		' [[Has related accessory::' .. fullPageName .. ']]\n' ..
		' |format=count\n' ..
		'}}'
	local documentCount = frame:preprocess(documentsCountWikitext)
	local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
	
	-- Create the content div
	local docsDiv = container:tag('div')
	css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
	
	if hasDocuments then
		-- Use #ask to get documents
		local docsQueryWikitext =
			'{{#ask:\n' ..
			' [[Category:Documents]]\n' ..
			' [[Has related accessory::' .. fullPageName .. ']]\n' ..
			' |?Has document type\n' ..
			' |?Has document URL\n' ..
			' |?Has document file\n' ..
			' |?Has document title\n' ..
			' |format=broadtable\n' ..
			' |headers=hide\n' ..
			' |link=none\n' ..
			'}}'
		local docsResult = frame:preprocess(docsQueryWikitext)
		
		-- Create UL element
		local ul = mw.html.create('ul')
		ul:css('margin', '0'):css('padding-left', '1.5em')
		
		-- Parse the HTML table
		if docsResult then
			for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
				local cells = {}
				for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
					local cleanContent = cellContent:gsub('<[^>]+>', '')
					table.insert(cells, cleanContent)
				end
				
				-- cells[1] = page name, cells[2] = type, cells[3] = URL, cells[4] = file, cells[5] = title
				if #cells >= 2 then
					local docType = trim(cells[2])
					local docTitle = trim(cells[5])
					local docPageName = trim(cells[1])
					
					if docPageName then
						local displayText = 'Document'
						if docTitle then
							displayText = docTitle
						elseif docType then
							displayText = docType
						end
						
						local li = ul:tag('li')
						li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
					end
				end
			end
		end
		
		docsDiv:node(ul)
		
		-- Add upload link as a button
		local uploadDiv = mw.html.create('div')
		uploadDiv:css('margin-top', '0.5em'):css('text-align', 'center')
		uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
		docsDiv:node(uploadDiv)
	else
		-- Just show the upload link as a button
		local uploadDiv = mw.html.create('div')
		uploadDiv:css('text-align', 'center')
		uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
		docsDiv:node(uploadDiv)
	end

	-- Archival Links section: Query for link subobjects and display by type
	local linksQueryWikitext =
		'{{#ask:\n' ..
		' [[Accessory::' .. fullPageName .. ']]\n' ..
		' |?Model link type\n' ..
		' |?Model link source\n' ..
		' |?Model link URL\n' ..
		' |format=broadtable\n' ..
		' |headers=hide\n' ..
		' |link=none\n' ..
		'}}'
	local linksResult = frame:preprocess(linksQueryWikitext)
	
	-- Parse links and organize by type
	local linksByType = {
		Manufacturer = {},
		Retailer = {},
		Database = {}
	}
	local hasLinks = false
	
	if linksResult then
		for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
			local cells = {}
			for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
				local cleanContent = cellContent:gsub('<[^>]+>', '')
				table.insert(cells, trim(cleanContent))
			end
			
			-- cells[1] = page/subobject, cells[2] = type, cells[3] = source, cells[4] = URL
			if #cells >= 4 then
				local linkType = cells[2]
				local linkSource = cells[3]
				local linkUrl = cells[4]
				
				if linkType and linkSource and linkUrl and linkUrl ~= '' then
					if linksByType[linkType] then
						table.insert(linksByType[linkType], {source = linkSource, url = linkUrl})
						hasLinks = true
					end
				end
			end
		end
	end
	
	if hasLinks then
		addSectionRow(container, 'Archival Links')
		
		local typeOrder = {'Database', 'Manufacturer', 'Retailer'}
		for _, linkType in ipairs(typeOrder) do
			if #linksByType[linkType] > 0 then
				local linksHtml = {}
				for _, link in ipairs(linksByType[linkType]) do
					table.insert(linksHtml, '[' .. link.url .. ' ' .. link.source .. ']')
				end
				addRow(container, linkType, table.concat(linksHtml, '<br>'), true)
			end
		end
	end

	-- Contribute section
	addSectionRow(container, 'Contribute')
	
	local editUrl = mw.uri.fullUrl(
		"Special:FormStart",
		{
			form = "Accessory",
			page_name = pageName
		}
	)
	
	local editDiv = container:tag('div')
	css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
	editDiv:wikitext('[' .. tostring(editUrl) .. ' <span class="infobox-button infobox-button-primary">Edit this data</span>]')

	return styleTag .. tostring(container)
end

return p
MediaWiki Appliance - Powered by TurnKey Linux