Module:CRTModel: Difference between revisions

Add variants display to infobox (via update-page on MediaWiki MCP Server)
Change "Add accessory" button to link to Create_an_accessory with crt_models parameter (via update-page on MediaWiki MCP Server)
 
(44 intermediate revisions by the same user not shown)
Line 56: Line 56:
fullDiv:wikitext(valueHtmlOrWikitext)
fullDiv:wikitext(valueHtmlOrWikitext)
end
end
end
-- Helper function to extract model code from "Brand Model" format
-- If the accessory starts with the same brand as the CRT, strip it for cleaner display
local function getDisplayTextWithoutBrand(itemName, deviceBrand)
if not itemName then return nil end
if not deviceBrand then return itemName end
-- Check if item name starts with the device brand followed by a space
local brandPrefix = deviceBrand .. ' '
if itemName:sub(1, #brandPrefix) == brandPrefix then
-- Return just the model code portion
return itemName:sub(#brandPrefix + 1)
end
-- Otherwise return the full name
return itemName
end
-- Helper to query inputs/outputs/control ports for a specific location
local function queryIOByLocation(frame, fullPageName, ioType, location)
local locationProp
if ioType == 'input' then
locationProp = 'Has input location'
elseif ioType == 'output' then
locationProp = 'Has output location'
else -- control
locationProp = 'Has control port location'
end
local locationFilter = ''
if location then
locationFilter = ' [[' .. locationProp .. '::' .. location .. ']]\n'
end
local signalProp, countProp, connectorProp, notesProp, labelProp, templateName
local extraProps = ''
if ioType == 'input' then
signalProp = 'Has video signal format'
countProp = 'Has input count'
connectorProp = 'Has connector type'
notesProp = 'Has input notes'
labelProp = 'Has input label'
templateName = 'CRT input row'
extraProps = ' |?Has audio type=audio\n' ..
' |?Has audio connector=audio_connector\n' ..
' |?Has audio shared with=audio_shared\n'
elseif ioType == 'output' then
signalProp = 'Has output signal format'
countProp = 'Has output count'
connectorProp = 'Has output connector'
notesProp = 'Has output notes'
labelProp = 'Has output label'
templateName = 'CRT output row'
else -- control
signalProp = 'Has control signal format'
countProp = 'Has control port count'
connectorProp = 'Has control port connector'
notesProp = 'Has control port notes'
labelProp = 'Has control port label'
templateName = 'CRT control port row'
extraProps = ' |?Has control port direction=direction\n'
end
local queryWikitext =
'{{#ask:\n' ..
' [[Has CRT model::' .. fullPageName .. ']]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
locationFilter ..
' |?' .. countProp .. '=count\n' ..
' |?' .. signalProp .. '=signal\n' ..
' |?' .. connectorProp .. '=connector\n' ..
' |?' .. labelProp .. '=label\n' ..
' |?' .. notesProp .. '=notes\n' ..
extraProps ..
' |format=template\n' ..
' |template=' .. templateName .. '\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
return trim(result)
end
-- Helper to discover all distinct location values for a given IO type
-- Returns an ordered list of location strings, sorted with common locations first
local function discoverLocations(frame, fullPageName, ioType)
local locationProp, signalProp
if ioType == 'input' then
locationProp = 'Has input location'
signalProp = 'Has video signal format'
elseif ioType == 'output' then
locationProp = 'Has output location'
signalProp = 'Has output signal format'
else -- control
locationProp = 'Has control port location'
signalProp = 'Has control signal format'
end
local queryWikitext =
'{{#ask:\n' ..
' [[Has CRT model::' .. fullPageName .. ']]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
' [[' .. locationProp .. '::+]]\n' ..
' |?' .. locationProp .. '=location\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
-- Parse distinct locations from broadtable rows
local seen = {}
local locations = {}
-- Preferred display order for common locations
local preferredOrder = {rear = 1, front = 2, side = 3, top = 4, bottom = 5, internal = 6}
if result then
for rowContent in result: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] = subobject name, cells[2] = location value
if #cells >= 2 and cells[2] then
local loc = mw.text.trim(cells[2])
-- Normalize to lowercase for deduplication
local locLower = loc:lower()
if locLower ~= '' and not seen[locLower] then
seen[locLower] = true
table.insert(locations, locLower)
end
end
end
end
-- Sort: preferred locations first (in defined order), then remaining alphabetically
table.sort(locations, function(a, b)
local orderA = preferredOrder[a] or 1000
local orderB = preferredOrder[b] or 1000
if orderA ~= orderB then
return orderA < orderB
end
return a < b
end)
return locations
end
-- Capitalize first letter of a location string for display
local function locationLabel(loc)
if not loc or loc == '' then return '' end
return loc:sub(1, 1):upper() .. loc:sub(2)
end
end


Line 70: Line 228:
local model_code = trim(args.model_code)
local model_code = trim(args.model_code)
local variants = trim(args.variants)
local variants = trim(args.variants)
local market = trim(args.market)
local series = trim(args.series)
local series = trim(args.series)
local image_main = trim(args.image_main)
local image_main = trim(args.image_main)
local model_3d = trim(args.model_3d)
local model_3d = trim(args.model_3d)
local crt_type = trim(args.crt_type)
local crt_type = trim(args.crt_type)
local form_factor = trim(args.form_factor)
local built_in_devices = trim(args.built_in_devices)
local screen_size_in = trim(args.screen_size_in)
local screen_size_in = trim(args.screen_size_in)
local aspect_ratio = trim(args.aspect_ratio)
local aspect_ratio = trim(args.aspect_ratio)
local tvl = trim(args.tvl)
local tvl = trim(args.tvl)
local chassis = trim(args.chassis)
local chassis = trim(args.chassis)
local chassis_family = trim(args.chassis_family)
local remote_control = trim(args.remote_control)
local remote_control = trim(args.remote_control)
local height = trim(args.height)
local height = trim(args.height)
Line 83: Line 245:
local depth = trim(args.depth)
local depth = trim(args.depth)
local weight = trim(args.weight)
local weight = trim(args.weight)
local standards_semantic = trim(args.standards_semantic)
-- Power specifications (frequency and power consumption still from template args)
local frequency = trim(args.frequency)
local power_avg = trim(args.power_avg)
local power_max = trim(args.power_max)
-- TV system support
local av_system = trim(args.av_system)
local tuner_system = trim(args.tuner_system)
-- Scan range
local h_scan_min = trim(args.h_scan_min)
local h_scan_max = trim(args.h_scan_max)
local v_scan_min = trim(args.v_scan_min)
local v_scan_max = trim(args.v_scan_max)
-- Speakers
local speaker_type = trim(args.speaker_type)
local speaker_watts = trim(args.speaker_watts)
local subwoofer = trim(args.subwoofer)
-- Release year and MSRP
local release_year = trim(args.release_year)
local msrp = trim(args.msrp)
-- Tube specifications
local mask_type = trim(args.mask_type)
local pitch = trim(args.pitch)
local phosphor_type = trim(args.phosphor_type)
local gun_type = trim(args.gun_type)
local deflection_angle = trim(args.deflection_angle)
local neck_diameter = trim(args.neck_diameter)
local visible_w = trim(args.visible_w)
local visible_h = trim(args.visible_h)
local visible_diag = trim(args.visible_diag)


-- Use frame:extensionTag to create a proper <style> tag that won't be escaped
-- Use frame:extensionTag to create a proper <style> tag that won't be escaped
local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:CRT model/styles.css'})
local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'})


local container = mw.html.create('div')
local container = mw.html.create('div')
Line 101: Line 297:
'infobox-title'
'infobox-title'
)
)
-- Type badge (breadcrumb) — plainlinks hides external icon, $wgNoFollowDomainExceptions handles nofollow
local badgeWikitext = '[[CRT Search|CRT]]'
if crt_type then
local typeUrl = mw.title.new('CRT Search'):fullUrl({crt_type = crt_type})
badgeWikitext = badgeWikitext .. ' › <span class="plainlinks">[' .. typeUrl .. ' ' .. crt_type .. ']</span>'
end
addFullRow(container, badgeWikitext, '', false, 'infobox-type-badge')


if image_main then
if image_main then
Line 146: Line 350:
if variants then
if variants then
addRow(container, 'Variants', variants, false)
addRow(container, 'Variants', variants, false)
end
-- Market row
if market then
addRow(container, 'Market', market, false)
end
end


Line 155: Line 364:
local seriesHtml = frame:preprocess(seriesWikitext)
local seriesHtml = frame:preprocess(seriesWikitext)
addRow(container, 'Series', seriesHtml, true)
addRow(container, 'Series', seriesHtml, true)
end
-- Release year row
if release_year then
addRow(container, 'Released', release_year, false)
end
-- MSRP row
if msrp then
addRow(container, 'MSRP', msrp, false)
end
end


Line 160: Line 379:
addRow(container, 'Type', crt_type, false)
addRow(container, 'Type', crt_type, false)
end
end
-- Form factor row (only show if not "Standard")
if form_factor and form_factor ~= 'Standard' then
addRow(container, 'Form factor', form_factor, false)
end
-- Built-in devices row
if built_in_devices then
addRow(container, 'Built-in', built_in_devices, false)
end
if screen_size_in then
if screen_size_in then
addRow(container, 'Screen size', screen_size_in .. '″', false)
addRow(container, 'Screen size', screen_size_in .. '″', false)
Line 170: Line 400:
end
end


if chassis then
-- Chassis row: display both label chassis and family if available
local chassisWikitext = string.format(
if chassis or chassis_family then
'{{#formredlink: target=%s |form=Chassis |existing page link text=%s }}',
local chassisDisplay = ''
chassis, chassis
)
if chassis then
local chassisHtml = frame:preprocess(chassisWikitext)
local chassisWikitext = string.format(
addRow(container, 'Chassis', chassisHtml, true)
'{{#formredlink: target=%s |form=Chassis |existing page link text=%s }}',
chassis, chassis
)
chassisDisplay = frame:preprocess(chassisWikitext)
end
if chassis_family then
-- Link directly to the chassis family page (no namespace prefix)
local familyWikitext = string.format(
'{{#formredlink: target=%s |form=Chassis family |existing page link text=%s }}',
chassis_family, chassis_family
)
local familyHtml = frame:preprocess(familyWikitext)
if chassis then
-- Both: show chassis with family in parentheses on next line
chassisDisplay = chassisDisplay .. '<br><small>Family: ' .. familyHtml .. '</small>'
else
-- Only family: show family alone
chassisDisplay = familyHtml .. ' <small>(family)</small>'
end
end
addRow(container, 'Chassis', chassisDisplay, true)
end
end


-- Remote control row: use Accessory form, strip brand prefix if it matches CRT brand
if remote_control then
if remote_control then
local displayText = getDisplayTextWithoutBrand(remote_control, brand)
local remoteWikitext = string.format(
local remoteWikitext = string.format(
'{{#formredlink: target=%s |form=Remote control |existing page link text=%s }}',
'{{#formredlink: target=%s |form=Accessory |existing page link text=%s }}',
remote_control, remote_control
remote_control, displayText
)
)
local remoteHtml = frame:preprocess(remoteWikitext)
local remoteHtml = frame:preprocess(remoteWikitext)
Line 191: Line 446:
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)
-- Tube section: show if we have any tube specs
if mask_type or pitch or phosphor_type or gun_type or deflection_angle or neck_diameter or visible_w or visible_h or visible_diag then
addSectionRow(container, 'Tube')
if mask_type then
addRow(container, 'Mask', mask_type, false)
end
if pitch then
addRow(container, 'Pitch', pitch .. ' mm', false)
end
if phosphor_type then
addRow(container, 'Phosphors', phosphor_type, false)
end
if gun_type then
addRow(container, 'Gun type', gun_type, false)
end
if deflection_angle then
addRow(container, 'Deflection', deflection_angle .. '°', false)
end
if neck_diameter then
addRow(container, 'Neck', neck_diameter .. ' mm', false)
end
-- Visible picture size
if visible_w and visible_h then
addRow(container, 'Visible area', visible_w .. ' × ' .. visible_h .. ' mm', false)
elseif visible_diag then
addRow(container, 'Visible area', visible_diag .. '″ (diagonal)', false)
end
-- Show diagonal even if we have W×H, as supplementary info
if visible_diag and visible_w and visible_h then
addRow(container, 'Visible diag.', visible_diag .. '″', false)
end
end


-- Dimensions section: show if we have height/width/depth OR weight
-- Dimensions section: show if we have height/width/depth OR weight
Line 210: Line 506:
end
end


-- Inputs section: use count query to check if there are any inputs
-- Speakers section: show if we have any speaker specs
local inputsCountWikitext =
if speaker_type or speaker_watts or subwoofer then
addSectionRow(container, 'Speakers')
if speaker_type then
addRow(container, 'Type', speaker_type, false)
end
if speaker_watts then
addRow(container, 'Power', speaker_watts, false)
end
if subwoofer then
addRow(container, 'Subwoofer', subwoofer, false)
end
end
 
-- Power section: query voltage ranges from subobjects
-- First, query for voltage range subobjects
local voltageQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Has CRT model::' .. fullPageName .. ']]\n' ..
' [[Has CRT model::' .. fullPageName .. ']]\n' ..
' [[Has video signal format::+]]\n' ..
' [[Voltage min::+]]\n' ..
' |?Voltage min\n' ..
' |?Voltage max\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |sort=Voltage min\n' ..
' |order=asc\n' ..
'}}'
local voltageResult = frame:preprocess(voltageQueryWikitext)
-- Parse voltage ranges from broadtable result
local voltageRanges = {}
if voltageResult then
for rowContent in voltageResult: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] = subobject, cells[2] = voltage_min, cells[3] = voltage_max
if #cells >= 3 then
local vMin = tonumber(cells[2])
local vMax = tonumber(cells[3])
if vMin or vMax then
table.insert(voltageRanges, {min = vMin, max = vMax})
end
end
end
end
local hasVoltageRanges = #voltageRanges > 0
 
-- Show Power section if we have voltage ranges, frequency, or power consumption
if hasVoltageRanges or frequency or power_avg or power_max then
addSectionRow(container, 'Power')
-- Voltage row: display ranges from subobjects
if hasVoltageRanges then
local rangeStrings = {}
for _, range in ipairs(voltageRanges) do
local rangeStr
if range.min and range.max then
if range.min == range.max then
rangeStr = range.min .. 'V'
else
rangeStr = range.min .. '–' .. range.max .. 'V'
end
elseif range.min then
rangeStr = range.min .. 'V'
elseif range.max then
rangeStr = range.max .. 'V'
end
if rangeStr then
table.insert(rangeStrings, rangeStr)
end
end
if #rangeStrings > 0 then
addRow(container, 'Voltage', table.concat(rangeStrings, ' / '), false)
end
end
-- Frequency row
if frequency then
addRow(container, 'Frequency', frequency, false)
end
-- Power consumption row
if power_avg or power_max then
local powerDisplay
if power_avg and power_max then
powerDisplay = power_avg .. 'W / ' .. power_max .. 'W<br><small>(avg / max)</small>'
elseif power_avg then
powerDisplay = power_avg .. 'W <small>(avg)</small>'
else
powerDisplay = power_max .. 'W <small>(max)</small>'
end
addRow(container, 'Consumption', powerDisplay, true)
end
end
 
-- TV Systems section: show if we have AV system or tuner system
if av_system or tuner_system then
addSectionRow(container, 'TV Systems')
if av_system then
local systems = {}
for system in av_system:gmatch('[^,]+') do
local s = trim(system)
if s then
table.insert(systems, '[[' .. s .. '|' .. s .. ']]')
end
end
if #systems > 0 then
addRow(container, 'AV system', table.concat(systems, ', '), true)
end
end
if tuner_system then
local systems = {}
for system in tuner_system:gmatch('[^,]+') do
local s = trim(system)
if s then
table.insert(systems, '[[' .. s .. '|' .. s .. ']]')
end
end
if #systems > 0 then
addRow(container, 'Tuner', table.concat(systems, ', '), true)
end
end
end
 
-- Scan Range section
if h_scan_min or h_scan_max or v_scan_min or v_scan_max then
addSectionRow(container, 'Scan Range')
if h_scan_min or h_scan_max then
local hDisplay
if h_scan_min and h_scan_max then
if h_scan_min == h_scan_max then
hDisplay = h_scan_min .. ' kHz'
else
hDisplay = h_scan_min .. '–' .. h_scan_max .. ' kHz'
end
elseif h_scan_min then
hDisplay = h_scan_min .. ' kHz'
else
hDisplay = h_scan_max .. ' kHz'
end
addRow(container, 'Horizontal', hDisplay, false)
end
if v_scan_min or v_scan_max then
local vDisplay
if v_scan_min and v_scan_max then
if v_scan_min == v_scan_max then
vDisplay = v_scan_min .. ' Hz'
else
vDisplay = v_scan_min .. '–' .. v_scan_max .. ' Hz'
end
elseif v_scan_min then
vDisplay = v_scan_min .. ' Hz'
else
vDisplay = v_scan_max .. ' Hz'
end
addRow(container, 'Vertical', vDisplay, false)
end
end
 
-- Inputs, Outputs, and Control Ports sections
local function renderIOSection(sectionTitle, ioType)
local locations = discoverLocations(frame, fullPageName, ioType)
local locationData = {}
 
for _, loc in ipairs(locations) do
local content = queryIOByLocation(frame, fullPageName, ioType, loc)
if content and content ~= '' then
table.insert(locationData, {location = loc, content = content})
end
end
 
if #locationData > 0 then
addSectionRow(container, sectionTitle)
for _, data in ipairs(locationData) do
local labelDiv = container:tag('div')
labelDiv:addClass('infobox-label')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext(locationLabel(data.location))
 
local valueDiv = container:tag('div')
valueDiv:addClass('infobox-value')
css(valueDiv, 'padding:0.2em 0.4em;')
valueDiv:node(mw.html.create('span'):wikitext(data.content))
end
end
end
 
renderIOSection('Inputs', 'input')
renderIOSection('Outputs', 'output')
renderIOSection('Control Ports', 'control')
 
-- Accessories section
addSectionRow(container, 'Accessories')
 
local addAccessoryUrl = mw.uri.fullUrl(
"Create an accessory",
{
crt_models = pageName
}
)
 
local accessoriesCountWikitext =
'{{#ask:\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related CRT model::' .. fullPageName .. ']]\n' ..
' [[Has accessory type::!Remote control]]\n' ..
' |format=count\n' ..
'}}'
local accessoryCount = frame:preprocess(accessoriesCountWikitext)
local hasAccessories = accessoryCount and tonumber(trim(accessoryCount)) and tonumber(trim(accessoryCount)) > 0
 
local accDiv = container:tag('div')
css(accDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
 
if hasAccessories then
local accessoriesQueryWikitext =
'{{#ask:\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related CRT model::' .. fullPageName .. ']]\n' ..
' [[Has accessory type::!Remote control]]\n' ..
' |?Has accessory type=type\n' ..
' |?Has model code=model\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local accessoriesResult = frame:preprocess(accessoriesQueryWikitext)
 
local accessoriesByType = {}
local typeOrder = {}
 
if accessoriesResult then
for rowContent in accessoriesResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
end
 
if #cells >= 2 then
local accPageName = cells[1]
local accType = cells[2] or 'Accessory'
 
if accPageName then
if not accessoriesByType[accType] then
accessoriesByType[accType] = {}
table.insert(typeOrder, accType)
end
 
table.insert(accessoriesByType[accType], {
page = accPageName
})
end
end
end
end
 
local accGrid = accDiv:tag('div')
css(accGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
 
for _, accType in ipairs(typeOrder) do
local accs = accessoriesByType[accType]
if accs and #accs > 0 then
local accLinks = {}
for _, acc in ipairs(accs) do
table.insert(accLinks, '[[' .. acc.page .. ']]')
end
 
local labelDiv = accGrid:tag('div')
labelDiv:addClass('infobox-label')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext(accType)
 
local valueDiv = accGrid:tag('div')
valueDiv:addClass('infobox-value')
css(valueDiv, 'padding:0.2em 0.4em;')
valueDiv:wikitext(table.concat(accLinks, '<br>'))
end
end
 
local addAccDiv = mw.html.create('div')
addAccDiv:css('margin-top', '0.5em'):css('text-align', 'center')
addAccDiv:wikitext('[' .. tostring(addAccessoryUrl) .. ' <span class="infobox-button infobox-button-secondary">Add accessory</span>]')
accDiv:node(addAccDiv)
else
local addAccDiv = mw.html.create('div')
addAccDiv:css('text-align', 'center')
addAccDiv:wikitext('[' .. tostring(addAccessoryUrl) .. ' <span class="infobox-button infobox-button-secondary">Add accessory</span>]')
accDiv:node(addAccDiv)
end
 
-- Parts section
addSectionRow(container, 'Parts')
local addPartUrl = mw.uri.fullUrl(
"Create a part",
{
crt_models = pageName
}
)
local partsCountWikitext =
'{{#ask:\n' ..
' [[Category:Parts]]\n' ..
' [[Has related CRT model::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
' |format=count\n' ..
'}}'
'}}'
local inputCount = frame:preprocess(inputsCountWikitext)
local partsCount = frame:preprocess(partsCountWikitext)
local hasInputs = inputCount and tonumber(trim(inputCount)) and tonumber(trim(inputCount)) > 0
local hasParts = partsCount and tonumber(trim(partsCount)) and tonumber(trim(partsCount)) > 0
if hasInputs then
local partsDiv = container:tag('div')
local askWikitext =
css(partsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
if hasParts then
local partsQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Has CRT model::' .. fullPageName .. ']]\n' ..
' [[Category:Parts]]\n' ..
' [[Has video signal format::+]]\n' ..
' [[Has related CRT model::' .. fullPageName .. ']]\n' ..
' |?Has video signal format=signal\n' ..
' |?Has part type=type\n' ..
' |?Has connector type=connector\n' ..
' |?Has part subtype=subtype\n' ..
' |?Has input count=count\n' ..
' |?Has model code=model\n' ..
' |?Has input location=location\n' ..
' |format=broadtable\n' ..
' |format=ul\n' ..
' |headers=hide\n' ..
' |template=CRT input inline\n' ..
' |link=none\n' ..
' |named args=yes\n' ..
' |searchlabel=\n' ..
'}}'
'}}'
local askHtml = frame:preprocess(askWikitext)
local partsResult = frame:preprocess(partsQueryWikitext)
local partsByType = {}
local partTypeOrder = {}
addSectionRow(container, 'Inputs')
if partsResult then
addFullRow(container, askHtml, 'padding:0.2em 0.4em;', true)
for rowContent in partsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
end
if #cells >= 2 then
local partPageName = cells[1]
local partType = cells[2] or 'Part'
local partSubtype = cells[3]
local groupKey = partType
if partSubtype and partSubtype ~= '' then
groupKey = partSubtype .. ' ' .. partType
end
if partPageName then
if not partsByType[groupKey] then
partsByType[groupKey] = {}
table.insert(partTypeOrder, groupKey)
end
table.insert(partsByType[groupKey], {
page = partPageName
})
end
end
end
end
local partsGrid = partsDiv:tag('div')
css(partsGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
for _, partType in ipairs(partTypeOrder) do
local parts = partsByType[partType]
if parts and #parts > 0 then
local partLinks = {}
for _, part in ipairs(parts) do
table.insert(partLinks, '[[' .. part.page .. ']]')
end
local labelDiv = partsGrid:tag('div')
labelDiv:addClass('infobox-label')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext(partType)
local valueDiv = partsGrid:tag('div')
valueDiv:addClass('infobox-value')
css(valueDiv, 'padding:0.2em 0.4em;')
valueDiv:wikitext(table.concat(partLinks, '<br>'))
end
end
local addPartDiv = mw.html.create('div')
addPartDiv:css('margin-top', '0.5em'):css('text-align', 'center')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add part</span>]')
partsDiv:node(addPartDiv)
else
local addPartDiv = mw.html.create('div')
addPartDiv:css('text-align', 'center')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add part</span>]')
partsDiv:node(addPartDiv)
end
end


-- Only display Standards section if standards_semantic has content
-- Mods section
if standards_semantic then
addSectionRow(container, 'Standards')
addFullRow(container, standards_semantic, 'padding:0.2em 0.4em;', false)
end
 
-- Mods section: Query for mod subobjects and display
local modsQueryWikitext =
local modsQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 261: Line 931:
local modsResult = frame:preprocess(modsQueryWikitext)
local modsResult = frame:preprocess(modsQueryWikitext)
-- Parse mods from broadtable result
local mods = {}
local mods = {}
Line 302: Line 971:
end
end
-- Display mods section if there are any
if #mods > 0 then
if #mods > 0 then
addSectionRow(container, 'Mods')
addSectionRow(container, 'Mods')
Line 315: Line 983:
local li = ul:tag('li')
local li = ul:tag('li')
-- Determine display text: use description if available, otherwise type
local displayText = mod.description or mod.type
local displayText = mod.description or mod.type
-- Check if mod is not feasible
local isFeasible = mod.feasible ~= 'No'
local isFeasible = mod.feasible ~= 'No'
if isFeasible then
if isFeasible then
-- Normal link for feasible mods using wikitext external link
li:wikitext('[' .. mod.url .. ' ' .. displayText .. ']')
li:wikitext('[' .. mod.url .. ' ' .. displayText .. ']')
else
else
-- Red styling with X for impossible mods
local wrapper = li:tag('span')
local wrapper = li:tag('span')
wrapper:addClass('mod-not-feasible')
wrapper:addClass('mod-not-feasible')
Line 339: Line 1,002:
addSectionRow(container, 'Documents')
addSectionRow(container, 'Documents')
-- Build the upload link with pseudorandom unique identifier
local timestamp = os.time()
local timestamp = os.time()
math.randomseed(timestamp + pageId) -- Seed with timestamp + page ID for better randomness
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
Line 353: Line 1,015:
)
)
-- Query for document count
local documentsCountWikitext =
local documentsCountWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 363: Line 1,024:
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 div
local docsDiv = container:tag('div')
local docsDiv = container:tag('div')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
if hasDocuments then
if hasDocuments then
-- Use #ask to get documents
local docsQueryWikitext =
local docsQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 383: Line 1,042:
local docsResult = frame:preprocess(docsQueryWikitext)
local docsResult = frame:preprocess(docsQueryWikitext)
-- Create UL element
local ul = mw.html.create('ul')
local ul = mw.html.create('ul')
ul:css('margin', '0'):css('padding-left', '1.5em')
ul:css('margin', '0'):css('padding-left', '1.5em')
-- Parse the HTML table - extract data from <td> tags
if docsResult then
if docsResult then
-- Match each table row
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
-- Extract content from each <td>
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
-- Strip any remaining HTML tags and get just the text
local cleanContent = cellContent:gsub('<[^>]+>', '')
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, cleanContent)
table.insert(cells, cleanContent)
end
end
-- cells[1] = page name, cells[2] = type, cells[3] = URL, cells[4] = file, cells[5] = title
if #cells >= 2 then
if #cells >= 2 then
local docType = trim(cells[2])
local docType = trim(cells[2])
Line 406: Line 1,059:
local docTitle = trim(cells[5])
local docTitle = trim(cells[5])
-- Determine what to link to (URL or file)
local linkTarget = nil
local linkTarget = nil
if docUrl then
if docUrl then
linkTarget = docUrl
linkTarget = docUrl
elseif docFile then
elseif docFile then
-- Extract filename from "File:..." format if present
local filename = docFile:match('File:(.+)') or docFile
local filename = docFile:match('File:(.+)') or docFile
linkTarget = tostring(mw.uri.fullUrl('File:' .. filename))
linkTarget = tostring(mw.uri.fullUrl('File:' .. filename))
end
end


-- Always link to the wiki page
local docPageName = trim(cells[1])
local docPageName = trim(cells[1])
if docPageName then
if docPageName then
-- Determine display text priority: custom title > type > default
local displayText = 'Document'
local displayText = 'Document'
if docTitle then
if docTitle then
Line 428: Line 1,077:
end
end
-- Create list item with link to wiki page
local li = ul:tag('li')
local li = ul:tag('li')
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
Line 438: Line 1,086:
docsDiv:node(ul)
docsDiv:node(ul)
-- 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 class="infobox-button infobox-button-secondary">Add a document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]')
docsDiv:node(uploadDiv)
docsDiv:node(uploadDiv)
else
else
-- 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 class="infobox-button infobox-button-secondary">Add a document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]')
docsDiv:node(uploadDiv)
docsDiv:node(uploadDiv)
end
end


-- Links section: Query for link subobjects and display by type
-- Links section
local linksQueryWikitext =
local linksQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 466: Line 1,112:
local linksResult = frame:preprocess(linksQueryWikitext)
local linksResult = frame:preprocess(linksQueryWikitext)
-- Parse links and organize by type
local linksByType = {
local linksByType = {
Manufacturer = {},
Manufacturer = {},
Line 475: Line 1,120:
if linksResult then
if linksResult then
-- Match each table row
for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
-- Extract content from each <td>
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
-- Strip any remaining HTML tags and get just the text
local cleanContent = cellContent:gsub('<[^>]+>', '')
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
table.insert(cells, trim(cleanContent))
end
end
-- cells[1] = page/subobject, cells[2] = type, cells[3] = source, cells[4] = URL
if #cells >= 4 then
if #cells >= 4 then
local linkType = cells[2]
local linkType = cells[2]
Line 501: Line 1,142:
end
end
-- Display links section if there are any links
if hasLinks then
if hasLinks then
addSectionRow(container, 'Archival Links')
addSectionRow(container, 'Archival Links')
-- Add links in order: Database, Manufacturer, Retailer
local linkTypeOrder = {'Database', 'Manufacturer', 'Retailer'}
local typeOrder = {'Database', 'Manufacturer', 'Retailer'}
for _, linkType in ipairs(linkTypeOrder) do
for _, linkType in ipairs(typeOrder) do
if #linksByType[linkType] > 0 then
if #linksByType[linkType] > 0 then
-- If multiple links of the same type, combine them in one row
local linksHtml = {}
local linksHtml = {}
for _, link in ipairs(linksByType[linkType]) do
for _, link in ipairs(linksByType[linkType]) do
Line 522: Line 1,160:
addSectionRow(container, 'Contribute')
addSectionRow(container, 'Contribute')
-- Build the edit link URL
local editUrl = mw.uri.fullUrl(
local editUrl = mw.uri.fullUrl(
"Special:FormStart",
"Special:FormStart",
Line 531: Line 1,168:
)
)
-- Create the edit button div
local editDiv = container:tag('div')
local editDiv = container:tag('div')
css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
-- Add edit button with primary action styling (using class for night mode compatibility)
editDiv:wikitext('[' .. tostring(editUrl) .. ' <span class="infobox-button infobox-button-primary">Edit this data</span>]')
editDiv:wikitext('[' .. tostring(editUrl) .. ' <span class="infobox-button infobox-button-primary">Edit this data</span>]')


-- Return templatestyles tag + container HTML
return styleTag .. tostring(container)
return styleTag .. tostring(container)
end
end


return p
return p
MediaWiki Appliance - Powered by TurnKey Linux