No edit summary
No edit summary
Line 20: Line 20:
end
end


local function addSectionRow(tbl, title)
local function addSectionRow(container, title)
local tr = tbl:tag('tr')
local section = container:tag('div')
css(tr:tag('td'), 'text-align:center; padding:0.5em; background:#e0e0e0; font-weight:bold;')
css(section, 'grid-column: 1 / -1; text-align:center; padding:0.5em; background:#e0e0e0; font-weight:bold;')
:attr('colspan', '2')
section:wikitext(title)
:wikitext(title)
end
end


local function addRow(tbl, label, valueHtmlOrWikitext, isRawHtml)
local function addRow(container, label, valueHtmlOrWikitext, isRawHtml)
local tr = tbl:tag('tr')
local labelDiv = container:tag('div')
css(tr:tag('th'), 'text-align:left; padding:0.2em 0.4em; max-width:6em; white-space:nowrap;')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
:wikitext(label)
labelDiv:wikitext(label)


local td = tr:tag('td')
local valueDiv = container:tag('div')
css(td, 'padding:0.2em 0.4em; word-break:break-word;')
css(valueDiv, 'padding:0.2em 0.4em;')


-- After preprocess, what we have is HTML. Use :node() to insert it as HTML.
if isRawHtml then
if isRawHtml then
td:node(mw.html.create('span'):wikitext(valueHtmlOrWikitext))
valueDiv:node(mw.html.create('span'):wikitext(valueHtmlOrWikitext))
else
else
td:wikitext(valueHtmlOrWikitext)
valueDiv:wikitext(valueHtmlOrWikitext)
end
end
end
end


local function addFullRow(tbl, valueHtmlOrWikitext, cssText, isRawHtml)
local function addFullRow(container, valueHtmlOrWikitext, cssText, isRawHtml)
local tr = tbl:tag('tr')
local fullDiv = container:tag('div')
local td = tr:tag('td'):attr('colspan', '2')
css(fullDiv, 'grid-column: 1 / -1; ' .. (cssText or ''))
css(td, cssText or '')


if isRawHtml then
if isRawHtml then
-- For raw HTML, insert directly without the span wrapper
fullDiv:wikitext(valueHtmlOrWikitext)
td:wikitext(valueHtmlOrWikitext)
else
else
td:wikitext(valueHtmlOrWikitext)
fullDiv:wikitext(valueHtmlOrWikitext)
end
end
end
end
Line 79: Line 75:
local standards_semantic = trim(args.standards_semantic)
local standards_semantic = trim(args.standards_semantic)


local tbl = mw.html.create('table')
local container = mw.html.create('div')
tbl:addClass('infobox')
container:addClass('infobox')
tbl:addClass('infobox-crt-model')
container:addClass('infobox-crt-model')
css(tbl, 'float:right; clear:right; margin:0 0 1em 1em; width:22em; border:1px solid #a2a9b1; border-spacing:3px; background:#f8f9fa; font-size:88%; line-height:1.5em;')
css(container, 'float:right; clear:right; margin:0 0 1em 1em; width:22em; border:1px solid #a2a9b1; background:#f8f9fa; font-size:88%; line-height:1.5em; display:grid; grid-template-columns:auto 1fr; gap:3px; padding:3px;')


-- Title row (no background color, centered, bold)
-- Title row (no background color, centered, bold)
addFullRow(
addFullRow(
tbl,
container,
(brand or '') .. ' ' .. (model_code or ''),
(brand or '') .. ' ' .. (model_code or ''),
'text-align:center; font-size:125%; font-weight:bold; padding:0.5em;',
'text-align:center; font-size:125%; font-weight:bold; padding:0.5em;',
Line 95: Line 91:
local file = image_main:gsub('^File:', '')
local file = image_main:gsub('^File:', '')
addFullRow(
addFullRow(
tbl,
container,
string.format('[[File:%s|frameless|300px]]', file),
string.format('[[File:%s|frameless|300px]]', file),
'text-align:center; padding:0;',
'text-align:center; padding:0;',
Line 106: Line 102:
local file = model_3d:gsub('^File:', '')
local file = model_3d:gsub('^File:', '')
addFullRow(
addFullRow(
tbl,
container,
string.format('[[File:%s]]', file),
string.format('[[File:%s]]', file),
'text-align:center; padding:0;',
'text-align:center; padding:0;',
Line 113: Line 109:
end
end


addSectionRow(tbl, 'Overview')
addSectionRow(container, 'Overview')


-- Brand row using #formredlink (must preprocess)
-- Brand row using #formredlink (must preprocess)
Line 122: Line 118:
)
)
local brandHtml = frame:preprocess(brandWikitext)
local brandHtml = frame:preprocess(brandWikitext)
addRow(tbl, 'Brand', brandHtml, true)
addRow(container, 'Brand', brandHtml, true)
end
end


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


if crt_type then
if crt_type then
addRow(tbl, 'Type', crt_type, false)
addRow(container, 'Type', crt_type, false)
end
end
if screen_size_in then
if screen_size_in then
addRow(tbl, 'Screen size', screen_size_in .. '″', false)
addRow(container, 'Screen size', screen_size_in .. '″', false)
end
end
if tvl then
if tvl then
addRow(tbl, 'TVL', tvl, false)
addRow(container, 'TVL', tvl, false)
end
end


Line 147: Line 143:
)
)
local chassisHtml = frame:preprocess(chassisWikitext)
local chassisHtml = frame:preprocess(chassisWikitext)
addRow(tbl, 'Chassis', chassisHtml, true)
addRow(container, 'Chassis', chassisHtml, true)
end
end


-- ECID row: build as a normal external link wikitext; preprocess not required, but fine.
-- ECID row: build as a normal external link wikitext; preprocess not required, but fine.
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(tbl, 'ECID', frame:preprocess(ecidWikitext), true)
addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)


-- Dimensions section: show if we have height/width/depth OR weight
-- Dimensions section: show if we have height/width/depth OR weight
if height or width or depth or weight then
if height or width or depth or weight then
addSectionRow(tbl, 'Dimensions')
addSectionRow(container, 'Dimensions')
if height or width or depth then
if height or width or depth then
Line 162: Line 158:
local w = width or '–'
local w = width or '–'
local d = depth or '–'
local d = depth or '–'
addRow(tbl, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
addRow(container, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
end
end
Line 168: Line 164:
if weight then
if weight then
local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
addRow(tbl, 'Weight', frame:preprocess(weightWikitext), true)
addRow(container, 'Weight', frame:preprocess(weightWikitext), true)
end
end
end
end
Line 188: Line 184:
-- Only display Inputs section if the query returned content (not just whitespace)
-- Only display Inputs section if the query returned content (not just whitespace)
if askHtml and trim(askHtml) then
if askHtml and trim(askHtml) then
addSectionRow(tbl, 'Inputs')
addSectionRow(container, 'Inputs')
addFullRow(tbl, askHtml, 'padding:0.2em 0.4em;', true)
addFullRow(container, askHtml, 'padding:0.2em 0.4em;', true)
end
end


-- Only display Standards section if standards_semantic has content
-- Only display Standards section if standards_semantic has content
if standards_semantic then
if standards_semantic then
addSectionRow(tbl, 'Standards')
addSectionRow(container, 'Standards')
addFullRow(tbl, standards_semantic, 'padding:0.2em 0.4em;', false)
addFullRow(container, standards_semantic, 'padding:0.2em 0.4em;', false)
end
end


     -- Documents section
     -- Documents section
addSectionRow(tbl, 'Documents')
addSectionRow(container, 'Documents')
-- Build the upload link   
-- Build the upload link   
Line 222: Line 218:
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
-- Create the content row
-- Create the content div
local tr = tbl:tag('tr')
local docsDiv = container:tag('div')
local td = tr:tag('td'):attr('colspan', '2')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
css(td, 'padding:0.2em 0.4em;')
if hasDocuments then
if hasDocuments then
Line 246: Line 241:
-- Parse the HTML table - extract data from <td> tags
-- Parse the HTML table - extract data from <td> tags
-- Pattern: <td ...>page</td><td ...>type</td><td ...>URL</td>
if docsResult then
if docsResult then
-- Match each table row
-- Match each table row
Line 274: Line 268:
end
end
td:node(ul)
docsDiv:node(ul)
-- Add upload link as a button - Tailwind-inspired style
-- Add upload link as a button
local uploadDiv = mw.html.create('div')
local uploadDiv = mw.html.create('div')
uploadDiv:css('margin-top', '0.5em'):css('text-align', 'center')
uploadDiv:css('margin-top', '0.5em'):css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload a document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload a document</span>]')
td:node(uploadDiv)
docsDiv:node(uploadDiv)
else
else
-- Just show the upload link as a button - Tailwind-inspired style
-- Just show the upload link as a button
local uploadDiv = mw.html.create('div')
local uploadDiv = mw.html.create('div')
uploadDiv:css('text-align', 'center')
uploadDiv:css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload a document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload a document</span>]')
td:node(uploadDiv)
docsDiv:node(uploadDiv)
end
end


Line 341: Line 335:
-- Display links section if there are any links
-- Display links section if there are any links
if hasLinks then
if hasLinks then
addSectionRow(tbl, 'Archival Links')
addSectionRow(container, 'Archival Links')
-- Add links in order: Database, Manufacturer, Retailer
-- Add links in order: Database, Manufacturer, Retailer
Line 352: Line 346:
table.insert(linksHtml, '[' .. link.url .. ' ' .. link.source .. ']')
table.insert(linksHtml, '[' .. link.url .. ' ' .. link.source .. ']')
end
end
addRow(tbl, linkType, table.concat(linksHtml, '<br>'), true)
addRow(container, linkType, table.concat(linksHtml, '<br>'), true)
end
end
end
end
Line 358: Line 352:


-- Contribute section
-- Contribute section
addSectionRow(tbl, 'Contribute')
addSectionRow(container, 'Contribute')
-- Build the edit link URL
-- Build the edit link URL
Line 369: Line 363:
)
)
-- Create the edit button row
-- Create the edit button div
local editTr = tbl:tag('tr')
local editDiv = container:tag('div')
local editTd = editTr:tag('td'):attr('colspan', '2')
css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
css(editTd, 'padding:0.2em 0.4em; text-align:center;')
-- Add edit button with primary action styling (blue tones)
-- Add edit button with primary action styling
editTd:wikitext('[' .. tostring(editUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#2563eb;color:#ffffff;border:1px solid #2563eb;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;cursor:pointer;">Edit this data</span>]')
editDiv:wikitext('[' .. tostring(editUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#2563eb;color:#ffffff;border:1px solid #2563eb;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;cursor:pointer;">Edit this data</span>]')


return tostring(tbl)
return tostring(container)
end
end


return p
return p

Revision as of 02:59, 26 December 2025

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

-- Module:CRTModel
-- Builds the CRT infobox as strict HTML to avoid whitespace -> <p><br></p>
-- Uses frame:preprocess() so parser functions like #ask and #formredlink evaluate.

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')
	css(section, 'grid-column: 1 / -1; text-align:center; padding:0.5em; background:#e0e0e0; font-weight:bold;')
	section:wikitext(title)
end

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

	local valueDiv = container:tag('div')
	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)
	local fullDiv = container:tag('div')
	css(fullDiv, 'grid-column: 1 / -1; ' .. (cssText or ''))

	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

	local brand = trim(args.brand)
	local model_code = trim(args.model_code)
	local image_main = trim(args.image_main)
	local model_3d = trim(args.model_3d)
	local crt_type = trim(args.crt_type)
	local screen_size_in = trim(args.screen_size_in)
	local tvl = trim(args.tvl)
	local chassis = trim(args.chassis)
	local height = trim(args.height)
	local width = trim(args.width)
	local depth = trim(args.depth)
	local weight = trim(args.weight)
	local standards_semantic = trim(args.standards_semantic)

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

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

	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
		)
	end

	-- 3D model display (right below main photo)
	if model_3d then
		local file = model_3d:gsub('^File:', '')
		addFullRow(
			container,
			string.format('[[File:%s]]', file),
			'text-align:center; padding:0;',
			false
		)
	end

	addSectionRow(container, 'Overview')

	-- Brand row using #formredlink (must preprocess)
	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)
	else
		addRow(container, 'Model', '', false)
	end

	if crt_type then
		addRow(container, 'Type', crt_type, false)
	end
	if screen_size_in then
		addRow(container, 'Screen size', screen_size_in .. '″', false)
	end
	if tvl then
		addRow(container, 'TVL', tvl, false)
	end

	if chassis then
		local chassisWikitext = string.format(
			'{{#formredlink: target=%s |form=Chassis |existing page link text=%s }}',
			chassis, chassis
		)
		local chassisHtml = frame:preprocess(chassisWikitext)
		addRow(container, 'Chassis', chassisHtml, true)
	end

	-- ECID row: build as a normal external link wikitext; preprocess not required, but fine.
	local ecidWikitext = string.format('[{{fullurl:%s|curid=%d}} EC%d]', pageName, pageId, pageId)
	addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)

	-- Dimensions section: show if we have height/width/depth OR weight
	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
		
		-- Weight row: uses #show, so preprocess it
		if weight then
			local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
			addRow(container, 'Weight', frame:preprocess(weightWikitext), true)
		end
	end

	-- Inputs section: preprocess #ask so it renders, but only show section if there are results
	local askWikitext =
		'{{#ask:\n' ..
		' [[Has CRT model::' .. fullPageName .. ']]\n' ..
		' |?Has video signal format=signal\n' ..
		' |?Has connector type=connector\n' ..
		' |?Has input count=count\n' ..
		' |?Has input location=location\n' ..
		' |format=ul\n' ..
		' |template=CRT input inline\n' ..
		' |named args=yes\n' ..
		' |searchlabel=\n' ..
		'}}'
	local askHtml = frame:preprocess(askWikitext)
	-- Only display Inputs section if the query returned content (not just whitespace)
	if askHtml and trim(askHtml) then
		addSectionRow(container, 'Inputs')
		addFullRow(container, askHtml, 'padding:0.2em 0.4em;', true)
	end

	-- Only display Standards section if standards_semantic has content
	if standards_semantic then
		addSectionRow(container, 'Standards')
		addFullRow(container, standards_semantic, 'padding:0.2em 0.4em;', false)
	end

    -- Documents section
	addSectionRow(container, 'Documents')
	
	-- Build the upload link  
	local target = pageName .. " - document"
	local uploadUrl = mw.uri.fullUrl(
		"Special:FormEdit",
		{
			form = "Document",
			target = target,
			["Document[crt_model]"] = pageName
		}
	)
	
	-- Query for document count
	local documentsCountWikitext =
		'{{#ask:\n' ..
		' [[Category:Documents]]\n' ..
		' [[Has related CRT model::' .. 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 - it returns an HTML table
		local docsQueryWikitext =
			'{{#ask:\n' ..
			' [[Category:Documents]]\n' ..
			' [[Has related CRT model::' .. fullPageName .. ']]\n' ..
			' |?Has document type\n' ..
			' |?Has document URL\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 - extract data from <td> tags
		if docsResult then
			-- Match each table row
			for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
				local cells = {}
				-- Extract content from each <td>
				for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
					-- Strip any remaining HTML tags and get just the text
					local cleanContent = cellContent:gsub('<[^>]+>', '')
					table.insert(cells, trim(cleanContent))
				end
				
				-- cells[1] = page name, cells[2] = type, cells[3] = URL
				if #cells >= 3 then
					local docType = cells[2]
					local docUrl = cells[3]
					
					if docUrl and docUrl ~= '' then
						local displayText = (docType and docType ~= '') and docType or 'Document'
						
						-- Create list item with link
						local li = ul:tag('li')
						li:wikitext('[' .. docUrl .. ' ' .. 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 style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload 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 style="display:inline-block;padding:0.5rem 1rem;background-color:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;">Upload a document</span>]')
		
		docsDiv:node(uploadDiv)
	end

	-- Links section: Query for link subobjects and display by type
	local linksQueryWikitext =
		'{{#ask:\n' ..
		' [[CRT model::' .. 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
		-- Match each table row
		for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
			local cells = {}
			-- Extract content from each <td>
			for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
				-- Strip any remaining HTML tags and get just the text
				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
	
	-- Display links section if there are any links
	if hasLinks then
		addSectionRow(container, 'Archival Links')
		
		-- Add links in order: Database, Manufacturer, Retailer
		local typeOrder = {'Database', 'Manufacturer', 'Retailer'}
		for _, linkType in ipairs(typeOrder) do
			if #linksByType[linkType] > 0 then
				-- If multiple links of the same type, combine them in one row
				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')
	
	-- Build the edit link URL
	local editUrl = mw.uri.fullUrl(
		"Special:FormStart",
		{
			form = "CRT model",
			page_name = pageName
		}
	)
	
	-- Create the edit button div
	local editDiv = container:tag('div')
	css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
	
	-- Add edit button with primary action styling
	editDiv:wikitext('[' .. tostring(editUrl) .. ' <span style="display:inline-block;padding:0.5rem 1rem;background-color:#2563eb;color:#ffffff;border:1px solid #2563eb;border-radius:0.375rem;font-size:0.875rem;font-weight:500;text-decoration:none;cursor:pointer;">Edit this data</span>]')

	return tostring(container)
end

return p
MediaWiki Appliance - Powered by TurnKey Linux