Module:AVDevice: Difference between revisions

Add oem_manufacturer parameter extraction and display row in infobox (after Brand, before Model) (via update-page on MediaWiki MCP Server)
Rename args.oem_manufacturer to args.oem to match CRT model convention (via update-page on MediaWiki MCP Server)
Line 58: Line 58:
end
end


-- Helper function to strip brand prefix from item name if it matches device brand
local function getDisplayTextWithoutBrand(itemName, deviceBrand)
local function getDisplayTextWithoutBrand(itemName, deviceBrand)
if not itemName then return nil end
if not itemName then return nil end
if not deviceBrand then return itemName end
if not deviceBrand then return itemName end
local brandPrefix = deviceBrand .. ' '
local brandPrefix = deviceBrand .. ' '
if itemName:sub(1, #brandPrefix) == brandPrefix then
if itemName:sub(1, #brandPrefix) == brandPrefix then
return itemName:sub(#brandPrefix + 1)
return itemName:sub(#brandPrefix + 1)
end
end
return itemName
return itemName
end
end


-- Helper function to format comma-separated values as a bulleted list or inline
local function formatList(value, inline)
local function formatList(value, inline)
if not value then return nil end
if not value then return nil end
Line 77: Line 73:
for item in value:gmatch('[^,]+') do
for item in value:gmatch('[^,]+') do
local trimmed = trim(item)
local trimmed = trim(item)
if trimmed then
if trimmed then table.insert(items, trimmed) end
table.insert(items, trimmed)
end
end
end
if #items == 0 then return nil end
if #items == 0 then return nil end
Line 87: Line 81:
local ul = mw.html.create('ul')
local ul = mw.html.create('ul')
ul:css('margin', '0'):css('padding-left', '1.2em')
ul:css('margin', '0'):css('padding-left', '1.2em')
for _, item in ipairs(items) do
for _, item in ipairs(items) do ul:tag('li'):wikitext(item) end
ul:tag('li'):wikitext(item)
end
return tostring(ul)
return tostring(ul)
end
end
end
end


-- Helper to format MSRP with dollar sign and commas
local function formatMSRP(value)
local function formatMSRP(value)
if not value then return nil end
if not value then return nil end
local num = tonumber(value)
local num = tonumber(value)
if not num then return '$' .. value end
if not num then return '$' .. value end
-- Format with commas
local formatted = string.format("%.2f", num)
local formatted = string.format("%.2f", num)
local intPart, decPart = formatted:match("^(%d+)%.(%d+)$")
local intPart, decPart = formatted:match("^(%d+)%.(%d+)$")
Line 110: Line 100:
end
end


-- Helper to format related models as wiki links, stripping brand prefix
local function formatRelatedModels(value, deviceBrand)
local function formatRelatedModels(value, deviceBrand)
if not value then return nil end
if not value then return nil end
Line 125: Line 114:
end
end


-- Helper to query inputs/outputs for a specific location
local function queryIOByLocation(frame, fullPageName, ioType, location)
local function queryIOByLocation(frame, fullPageName, ioType, location)
local locationFilter = ''
local locationFilter = ''
Line 131: Line 119:
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n'
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n'
end
end
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format'
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format'
local countProp = ioType == 'input' and 'Has input count' or 'Has output count'
local countProp = ioType == 'input' and 'Has input count' or 'Has output count'
Line 138: Line 125:
local labelProp = ioType == 'input' and 'Has input label' or 'Has output label'
local labelProp = ioType == 'input' and 'Has input label' or 'Has output label'
local templateName = ioType == 'input' and 'AV device input row' or 'AV device output row'
local templateName = ioType == 'input' and 'AV device input row' or 'AV device output row'
-- Build audio property queries (only for inputs)
local audioProps = ''
local audioProps = ''
if ioType == 'input' then
if ioType == 'input' then
Line 146: Line 131:
' |?Has audio shared with=audio_shared\n'
' |?Has audio shared with=audio_shared\n'
end
end
local queryWikitext =
local queryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 163: Line 147:
' |link=none\n' ..
' |link=none\n' ..
'}}'
'}}'
local result = frame:preprocess(queryWikitext)
local result = frame:preprocess(queryWikitext)
return trim(result)
return trim(result)
end
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 function discoverLocations(frame, fullPageName, ioType)
local locationProp, signalProp
local locationProp, signalProp
Line 175: Line 156:
locationProp = 'Has input location'
locationProp = 'Has input location'
signalProp = 'Has video signal format'
signalProp = 'Has video signal format'
else -- output
else
locationProp = 'Has output location'
locationProp = 'Has output location'
signalProp = 'Has output signal format'
signalProp = 'Has output signal format'
end
end
local queryWikitext =
local queryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
Line 190: Line 170:
' |link=none\n' ..
' |link=none\n' ..
'}}'
'}}'
local result = frame:preprocess(queryWikitext)
local result = frame:preprocess(queryWikitext)
-- Parse distinct locations from broadtable rows
local seen = {}
local seen = {}
local locations = {}
local locations = {}
-- Preferred display order for common locations
local preferredOrder = {rear = 1, front = 2, side = 3, top = 4, bottom = 5, internal = 6}
local preferredOrder = {rear = 1, front = 2, side = 3, top = 4, bottom = 5, internal = 6}
if result then
if result then
for rowContent in result:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in result:gmatch('<tr[^>]*>(.-)</tr>') do
Line 207: Line 181:
table.insert(cells, trim(cleanContent))
table.insert(cells, trim(cleanContent))
end
end
-- cells[1] = subobject name, cells[2] = location value
if #cells >= 2 and cells[2] then
if #cells >= 2 and cells[2] then
local loc = mw.text.trim(cells[2])
local loc = mw.text.trim(cells[2])
-- Normalize to lowercase for deduplication
local locLower = loc:lower()
local locLower = loc:lower()
if locLower ~= '' and not seen[locLower] then
if locLower ~= '' and not seen[locLower] then
Line 219: Line 191:
end
end
end
end
-- Sort: preferred locations first (in defined order), then remaining alphabetically
table.sort(locations, function(a, b)
table.sort(locations, function(a, b)
local orderA = preferredOrder[a] or 1000
local orderA = preferredOrder[a] or 1000
local orderB = preferredOrder[b] or 1000
local orderB = preferredOrder[b] or 1000
if orderA ~= orderB then
if orderA ~= orderB then return orderA < orderB end
return orderA < orderB
end
return a < b
return a < b
end)
end)
return locations
return locations
end
end


-- Capitalize first letter of a location string for display
local function locationLabel(loc)
local function locationLabel(loc)
if not loc or loc == '' then return '' end
if not loc or loc == '' then return '' end
Line 248: Line 214:
local pageId = title.id or 0
local pageId = title.id or 0


-- Extract parameters
local brand = trim(args.brand)
local brand = trim(args.brand)
local model_code = trim(args.model_code)
local model_code = trim(args.model_code)
local oem_manufacturer = trim(args.oem_manufacturer)
local oem_manufacturer = trim(args.oem)
local device_type = trim(args.device_type)
local device_type = trim(args.device_type)
local format_family = trim(args.format_family)
local format_family = trim(args.format_family)
Line 273: Line 238:
local related_models = trim(args.related_models)
local related_models = trim(args.related_models)


-- Use frame:extensionTag to create a proper <style> tag
local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'})
local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'})


Line 281: Line 245:
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;')
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')
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) — plainlinks hides external icon, $wgNoFollowDomainExceptions handles nofollow
local badgeWikitext = '[[AV Device Search|AV device]]'
local badgeWikitext = '[[AV Device Search|AV device]]'
if device_type then
if device_type then
Line 300: Line 256:
if image_main then
if image_main then
local file = image_main:gsub('^File:', '')
local file = image_main:gsub('^File:', '')
addFullRow(
addFullRow(container, string.format('[[File:%s|frameless|300px]]', file), 'text-align:center; padding:0;', false, 'infobox-image')
container,
string.format('[[File:%s|frameless|300px]]', file),
'text-align:center; padding:0;',
false,
'infobox-image'
)
end
end


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


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


-- OEM Manufacturer row (shown only when different from brand)
if oem_manufacturer then
if oem_manufacturer then
local oemWikitext = string.format(
local oemWikitext = string.format('{{#formredlink: target=%s |form=Manufacturer |existing page link text=%s }}', oem_manufacturer, oem_manufacturer)
'{{#formredlink: target=%s |form=Manufacturer |existing page link text=%s }}',
addRow(container, 'OEM', frame:preprocess(oemWikitext), true)
oem_manufacturer, oem_manufacturer
)
local oemHtml = frame:preprocess(oemWikitext)
addRow(container, 'OEM', oemHtml, true)
end
 
if model_code then
addRow(container, 'Model', model_code, false)
end
end


-- Market row
if model_code then addRow(container, 'Model', model_code, false) end
if market then
if market then addRow(container, 'Market', market, false) end
addRow(container, 'Market', market, false)
end


-- Type row: link to AV Device Search with filter, not to a (possibly non-existent) article
if device_type then
if device_type then
local typeSearchUrl = tostring(mw.uri.fullUrl('AV Device Search', {device_type = device_type}))
local typeSearchUrl = tostring(mw.uri.fullUrl('AV Device Search', {device_type = device_type}))
local typeWikitext = '<span class="plainlinks">[' .. typeSearchUrl .. ' ' .. device_type .. ']</span>'
addRow(container, 'Type', '<span class="plainlinks">[' .. typeSearchUrl .. ' ' .. device_type .. ']</span>', true)
addRow(container, 'Type', typeWikitext, true)
end
end


-- Format family with link to category
if format_family then
if format_family then
addRow(container, 'Format family', '[[:Category:' .. format_family .. ' devices|' .. format_family .. ']]', true)
addRow(container, 'Format family', '[[:Category:' .. format_family .. ' devices|' .. format_family .. ']]', true)
end
end


-- Year info
if year_released or year_discontinued then
if year_released or year_discontinued then
local yearStr = ''
local yearStr = ''
if year_released and year_discontinued then
if year_released and year_discontinued then yearStr = year_released .. ' – ' .. year_discontinued
yearStr = year_released .. ' – ' .. year_discontinued
elseif year_released then yearStr = year_released .. ' –'
elseif year_released then
elseif year_discontinued then yearStr = '– ' .. year_discontinued end
yearStr = year_released .. ' –'
elseif year_discontinued then
yearStr = '– ' .. year_discontinued
end
addRow(container, 'Years', yearStr, false)
addRow(container, 'Years', yearStr, false)
end
end


-- MSRP
if msrp then addRow(container, 'MSRP', formatMSRP(msrp), false) end
if msrp then
addRow(container, 'MSRP', formatMSRP(msrp), false)
end


-- AV System support
if av_system then
if av_system then
local formattedAV = formatList(av_system, true)
local formattedAV = formatList(av_system, true)
if formattedAV then
if formattedAV then addRow(container, 'AV system', formattedAV, false) end
addRow(container, 'AV system', formattedAV, false)
end
end
end


-- Related models
if related_models then
if related_models then
local formattedRelated = formatRelatedModels(related_models, brand)
local formattedRelated = formatRelatedModels(related_models, brand)
if formattedRelated then
if formattedRelated then addRow(container, 'Related', formattedRelated, true) end
addRow(container, 'Related', formattedRelated, true)
end
end
end


-- ECID row
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)


-- Format compatibility section (only show if tape-based device)
if supported_formats or recording_formats then
if supported_formats or recording_formats then
addSectionRow(container, 'Format Compatibility')
addSectionRow(container, 'Format Compatibility')
if supported_formats then
if supported_formats then
local formattedSupported = formatList(supported_formats, true)
local f = formatList(supported_formats, true)
if formattedSupported then
if f then addRow(container, 'Plays', f, false) end
addRow(container, 'Plays', formattedSupported, false)
end
end
end
if recording_formats then
if recording_formats then
local formattedRecording = formatList(recording_formats, true)
local f = formatList(recording_formats, true)
if formattedRecording then
if f then addRow(container, 'Records', f, false) end
addRow(container, 'Records', formattedRecording, false)
end
end
end
end
end


-- Inputs and Outputs sections (matching CRT model style)
local function renderIOSection(sectionTitle, ioType)
local function renderIOSection(sectionTitle, ioType)
local locations = discoverLocations(frame, fullPageName, ioType)
local locations = discoverLocations(frame, fullPageName, ioType)
local locationData = {}
local locationData = {}
for _, loc in ipairs(locations) do
for _, loc in ipairs(locations) do
local content = queryIOByLocation(frame, fullPageName, ioType, loc)
local content = queryIOByLocation(frame, fullPageName, ioType, loc)
Line 420: Line 327:
end
end
end
end
if #locationData > 0 then
if #locationData > 0 then
addSectionRow(container, sectionTitle)
addSectionRow(container, sectionTitle)
Line 428: Line 334:
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext(locationLabel(data.location))
labelDiv:wikitext(locationLabel(data.location))
local valueDiv = container:tag('div')
local valueDiv = container:tag('div')
valueDiv:addClass('infobox-value')
valueDiv:addClass('infobox-value')
Line 440: Line 345:
renderIOSection('Outputs', 'output')
renderIOSection('Outputs', 'output')


-- Features section
if features then
if features then
addSectionRow(container, 'Features')
addSectionRow(container, 'Features')
Line 446: Line 350:
end
end


-- Dimensions section
if height or width or depth or weight then
if height or width or depth or weight then
addSectionRow(container, 'Dimensions')
addSectionRow(container, 'Dimensions')
if height or width or depth then
if height or width or depth then
local h = height or '–'
addRow(container, 'H × W × D', string.format('%s × %s × %s', height or '–', width or '–', depth or '–'), false)
local w = width or '–'
local d = depth or '–'
addRow(container, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
end
end
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 }}'
Line 463: Line 361:
end
end


-- Power section - query voltage ranges and display power specs
local voltageQueryWikitext =
local voltageQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n [[Has AV device::' .. pageName .. ']]\n [[Voltage min::+]]\n |?Voltage min=vmin\n |?Voltage max=vmax\n |format=template\n |template=AV device voltage row\n |named args=yes\n |link=none\n}}'
' [[Has AV device::' .. pageName .. ']]\n' ..
' [[Voltage min::+]]\n' ..
' |?Voltage min=vmin\n' ..
' |?Voltage max=vmax\n' ..
' |format=template\n' ..
' |template=AV device voltage row\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
'}}'
local voltageResult = frame:preprocess(voltageQueryWikitext)
local voltageResult = frame:preprocess(voltageQueryWikitext)
local hasVoltage = voltageResult and trim(voltageResult) and trim(voltageResult) ~= ''
local hasVoltage = voltageResult and trim(voltageResult) and trim(voltageResult) ~= ''
Line 480: Line 368:
if hasVoltage or frequency or power_avg or power_max then
if hasVoltage or frequency or power_avg or power_max then
addSectionRow(container, 'Power')
addSectionRow(container, 'Power')
if hasVoltage then addRow(container, 'Voltage', voltageResult, true) end
if hasVoltage then
addRow(container, 'Voltage', voltageResult, true)
end
if frequency then
if frequency then
local formattedFreq = formatList(frequency, true)
local formattedFreq = formatList(frequency, true)
if formattedFreq then
if formattedFreq then addRow(container, 'AC frequency', formattedFreq, false) end
addRow(container, 'AC frequency', formattedFreq, false)
end
end
end
if power_avg or power_max then
if power_avg or power_max then
local powerStr = ''
local powerStr = ''
if power_avg and power_max then
if power_avg and power_max then powerStr = power_avg .. 'W (avg) / ' .. power_max .. 'W (max)'
powerStr = power_avg .. 'W (avg) / ' .. power_max .. 'W (max)'
elseif power_avg then powerStr = power_avg .. 'W'
elseif power_avg then
elseif power_max then powerStr = power_max .. 'W (max)' end
powerStr = power_avg .. 'W'
elseif power_max then
powerStr = power_max .. 'W (max)'
end
addRow(container, 'Power', powerStr, false)
addRow(container, 'Power', powerStr, false)
end
end
end
end


-- Time Base Correction section - query AV TBC subobjects
local tbcCountWikitext = '{{#ask:\n [[TBC type::+]]\n [[-Has subobject::' .. fullPageName .. ']]\n |format=count\n}}'
local tbcCountWikitext =
'{{#ask:\n' ..
' [[TBC type::+]]\n' ..
' [[-Has subobject::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local tbcCountResult = frame:preprocess(tbcCountWikitext)
local tbcCountResult = frame:preprocess(tbcCountWikitext)
local hasTBC = tbcCountResult and tonumber(trim(tbcCountResult)) and tonumber(trim(tbcCountResult)) > 0
local hasTBC = tbcCountResult and tonumber(trim(tbcCountResult)) and tonumber(trim(tbcCountResult)) > 0
Line 517: Line 388:
if hasTBC then
if hasTBC then
addSectionRow(container, 'Time Base Correction')
addSectionRow(container, 'Time Base Correction')
 
local tbcQueryWikitext = '{{#ask:\n [[TBC type::+]]\n [[-Has subobject::' .. fullPageName .. ']]\n |?TBC type\n |?TBC buffer size\n |?TBC notes\n |format=broadtable\n |headers=hide\n |link=none\n}}'
local tbcQueryWikitext =
'{{#ask:\n' ..
' [[TBC type::+]]\n' ..
' [[-Has subobject::' .. fullPageName .. ']]\n' ..
' |?TBC type\n' ..
' |?TBC buffer size\n' ..
' |?TBC notes\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local tbcResult = frame:preprocess(tbcQueryWikitext)
local tbcResult = frame:preprocess(tbcQueryWikitext)
if tbcResult then
if tbcResult then
local entryIndex = 0
for rowContent in tbcResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in tbcResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, cellContent:gsub('<[^>]+>', ''))
table.insert(cells, cleanContent)
end
end
if #cells >= 2 then
if #cells >= 2 then
local tbcType = trim(cells[2])
local tbcType = trim(cells[2])
local tbcBuffer = trim(cells[3])
local tbcBuffer = trim(cells[3])
local tbcNotes = trim(cells[4])
local tbcNotes = trim(cells[4])
if tbcType then
if tbcType then
entryIndex = entryIndex + 1
-- Create a full-width grouped container with nested grid
local group = container:tag('div')
local group = container:tag('div')
group:addClass('infobox-tbc-group')
group:addClass('infobox-tbc-group')
css(group, 'grid-column: 1 / -1; display:grid; grid-template-columns:auto 1fr; gap:3px;')
css(group, 'grid-column: 1 / -1; display:grid; grid-template-columns:auto 1fr; gap:3px;')
 
local tl = group:tag('div'):addClass('infobox-label')
-- Type row
css(tl, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
local typeLabelDiv = group:tag('div')
tl:wikitext('Type')
typeLabelDiv:addClass('infobox-label')
local tv = group:tag('div'):addClass('infobox-value')
css(typeLabelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
css(tv, 'padding:0.2em 0.4em;')
typeLabelDiv:wikitext('Type')
tv:wikitext(tbcType)
 
local typeValueDiv = group:tag('div')
typeValueDiv:addClass('infobox-value')
css(typeValueDiv, 'padding:0.2em 0.4em;')
typeValueDiv:wikitext(tbcType)
 
-- Buffer row (conditional)
if tbcBuffer then
if tbcBuffer then
local bufLabelDiv = group:tag('div')
local bl = group:tag('div'):addClass('infobox-label')
bufLabelDiv:addClass('infobox-label')
css(bl, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
css(bufLabelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
bl:wikitext('Buffer')
bufLabelDiv:wikitext('Buffer')
local bv = group:tag('div'):addClass('infobox-value')
 
css(bv, 'padding:0.2em 0.4em;')
local bufValueDiv = group:tag('div')
bv:wikitext(tbcBuffer)
bufValueDiv:addClass('infobox-value')
css(bufValueDiv, 'padding:0.2em 0.4em;')
bufValueDiv:wikitext(tbcBuffer)
end
end
-- Notes row (conditional)
if tbcNotes then
if tbcNotes then
local notesLabelDiv = group:tag('div')
local nl = group:tag('div'):addClass('infobox-label')
notesLabelDiv:addClass('infobox-label')
css(nl, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
css(notesLabelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
nl:wikitext('Notes')
notesLabelDiv:wikitext('Notes')
local nv = group:tag('div'):addClass('infobox-value')
 
css(nv, 'padding:0.2em 0.4em; font-size:0.92em;')
local notesValueDiv = group:tag('div')
nv:wikitext(tbcNotes)
notesValueDiv:addClass('infobox-value')
css(notesValueDiv, 'padding:0.2em 0.4em; font-size:0.92em;')
notesValueDiv:wikitext(tbcNotes)
end
end
end
end
Line 595: Line 432:
end
end


-- Accessories section: Always show (like Parts/Documents), with "Add accessory" button
-- Accessories section
addSectionRow(container, 'Accessories')
addSectionRow(container, 'Accessories')
 
local addAccessoryUrl = mw.uri.fullUrl("Create an accessory", {av_devices = pageName})
-- Build the add accessory link pointing to Create_an_accessory with av_devices parameter
local accessoriesCountWikitext = '{{#ask:\n [[Category:Accessories]]\n [[Has related AV device::' .. fullPageName .. ']]\n |format=count\n}}'
local addAccessoryUrl = mw.uri.fullUrl(
"Create an accessory",
{
av_devices = pageName
}
)
 
-- Query for accessories linked via Has related AV device
-- Unlike CRT models which have a dedicated remote_control field,
-- AV devices show all accessory types including remote controls here.
local accessoriesCountWikitext =
'{{#ask:\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local accessoryCount = frame:preprocess(accessoriesCountWikitext)
local accessoryCount = frame:preprocess(accessoriesCountWikitext)
local hasAccessories = accessoryCount and tonumber(trim(accessoryCount)) and tonumber(trim(accessoryCount)) > 0
local hasAccessories = accessoryCount and tonumber(trim(accessoryCount)) and tonumber(trim(accessoryCount)) > 0
local accDiv = container:tag('div')
local accDiv = container:tag('div')
css(accDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
css(accDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')


if hasAccessories then
if hasAccessories then
-- Query accessories and display grouped by type
local accessoriesQueryWikitext = '{{#ask:\n [[Category:Accessories]]\n [[Has related AV device::' .. fullPageName .. ']]\n |?Has accessory type=type\n |?Has model code=model\n |format=broadtable\n |headers=hide\n |link=none\n}}'
local accessoriesQueryWikitext =
'{{#ask:\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\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 accessoriesResult = frame:preprocess(accessoriesQueryWikitext)
-- Parse accessories from broadtable result and group by type
local accessoriesByType = {}
local accessoriesByType = {}
local typeOrder = {}
local typeOrder = {}
if accessoriesResult then
if accessoriesResult then
for rowContent in accessoriesResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in accessoriesResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cellContent:gsub('<[^>]+>', '')))
table.insert(cells, trim(cleanContent))
end
end
-- cells[1] = page name, cells[2] = type, cells[3] = model code
if #cells >= 2 then
if #cells >= 2 then
local accPageName = cells[1]
local accPageName = cells[1]
local accType = cells[2] or 'Accessory'
local accType = cells[2] or 'Accessory'
if accPageName then
if accPageName then
if not accessoriesByType[accType] then
if not accessoriesByType[accType] then
Line 657: Line 460:
table.insert(typeOrder, accType)
table.insert(typeOrder, accType)
end
end
 
table.insert(accessoriesByType[accType], {page = accPageName})
table.insert(accessoriesByType[accType], {
page = accPageName
})
end
end
end
end
end
end
end
end
-- Create a nested grid for accessory rows
local accGrid = accDiv:tag('div')
local accGrid = accDiv:tag('div')
css(accGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
css(accGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
-- Display as label/value rows grouped by type (like Parts)
for _, accType in ipairs(typeOrder) do
for _, accType in ipairs(typeOrder) do
local accs = accessoriesByType[accType]
local accs = accessoriesByType[accType]
if accs and #accs > 0 then
if accs and #accs > 0 then
local accLinks = {}
local accLinks = {}
for _, acc in ipairs(accs) do
for _, acc in ipairs(accs) do table.insert(accLinks, '[[' .. acc.page .. ']]') end
table.insert(accLinks, '[[' .. acc.page .. ']]')
local ld = accGrid:tag('div'):addClass('infobox-label')
end
css(ld, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
 
ld:wikitext(accType)
local labelDiv = accGrid:tag('div')
local vd = accGrid:tag('div'):addClass('infobox-value')
labelDiv:addClass('infobox-label')
css(vd, 'padding:0.2em 0.4em;')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
vd:wikitext(table.concat(accLinks, '<br>'))
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
end
end
-- Add "Add accessory" button
local addAccDiv = mw.html.create('div')
local addAccDiv = mw.html.create('div')
addAccDiv:css('margin-top', '0.5em'):css('text-align', 'center')
addAccDiv:css('margin-top', '0.5em'):css('text-align', 'center')
Line 697: Line 485:
accDiv:node(addAccDiv)
accDiv:node(addAccDiv)
else
else
-- Just show the add accessory link as a button
local addAccDiv = mw.html.create('div')
local addAccDiv = mw.html.create('div')
addAccDiv:css('text-align', 'center')
addAccDiv:css('text-align', 'center')
Line 706: Line 493:
-- Parts section
-- Parts section
addSectionRow(container, 'Parts')
addSectionRow(container, 'Parts')
local addPartUrl = mw.uri.fullUrl("Create a part", {av_devices = pageName})
-- Build the add part link pointing to Create_a_part with av_devices parameter
local partsCountWikitext = '{{#ask:\n [[Category:Parts]]\n [[Has related AV device::' .. fullPageName .. ']]\n |format=count\n}}'
local addPartUrl = mw.uri.fullUrl(
"Create a part",
{
av_devices = pageName
}
)
-- Query for parts linked via Has related AV device
local partsCountWikitext =
'{{#ask:\n' ..
' [[Category:Parts]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local partsCount = frame:preprocess(partsCountWikitext)
local partsCount = frame:preprocess(partsCountWikitext)
local hasParts = partsCount and tonumber(trim(partsCount)) and tonumber(trim(partsCount)) > 0
local hasParts = partsCount and tonumber(trim(partsCount)) and tonumber(trim(partsCount)) > 0
local partsDiv = container:tag('div')
local partsDiv = container:tag('div')
css(partsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
css(partsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
 
if hasParts then
if hasParts then
-- Query parts and display grouped by type/subtype
local partsQueryWikitext = '{{#ask:\n [[Category:Parts]]\n [[Has related AV device::' .. fullPageName .. ']]\n |?Has part type=type\n |?Has part subtype=subtype\n |?Has model code=model\n |format=broadtable\n |headers=hide\n |link=none\n}}'
local partsQueryWikitext =
'{{#ask:\n' ..
' [[Category:Parts]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |?Has part type=type\n' ..
' |?Has part subtype=subtype\n' ..
' |?Has model code=model\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local partsResult = frame:preprocess(partsQueryWikitext)
local partsResult = frame:preprocess(partsQueryWikitext)
-- Parse parts from broadtable result and group by type
local partsByType = {}
local partsByType = {}
local partTypeOrder = {}
local partTypeOrder = {}
if partsResult then
if partsResult then
for rowContent in partsResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in partsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cellContent:gsub('<[^>]+>', '')))
table.insert(cells, trim(cleanContent))
end
end
-- cells[1] = page name, cells[2] = type, cells[3] = subtype, cells[4] = model code
if #cells >= 2 then
if #cells >= 2 then
local partPageName = cells[1]
local partPageName = cells[1]
local partType = cells[2] or 'Part'
local partType = cells[2] or 'Part'
local partSubtype = cells[3]
local partSubtype = cells[3]
-- Use subtype + type as the grouping key if subtype exists
local groupKey = partType
local groupKey = partType
if partSubtype and partSubtype ~= '' then
if partSubtype and partSubtype ~= '' then groupKey = partSubtype .. ' ' .. partType end
groupKey = partSubtype .. ' ' .. partType
end
if partPageName then
if partPageName then
-- Track type order for consistent display
if not partsByType[groupKey] then
if not partsByType[groupKey] then
partsByType[groupKey] = {}
partsByType[groupKey] = {}
table.insert(partTypeOrder, groupKey)
table.insert(partTypeOrder, groupKey)
end
end
table.insert(partsByType[groupKey], {page = partPageName})
table.insert(partsByType[groupKey], {
page = partPageName
})
end
end
end
end
end
end
end
end
-- Create a nested grid for part rows
local partsGrid = partsDiv:tag('div')
local partsGrid = partsDiv:tag('div')
css(partsGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
css(partsGrid, 'display:grid; grid-template-columns:auto 1fr; gap:3px;')
-- Display as label/value rows grouped by type
for _, partType in ipairs(partTypeOrder) do
for _, partType in ipairs(partTypeOrder) do
local parts = partsByType[partType]
local parts = partsByType[partType]
if parts and #parts > 0 then
if parts and #parts > 0 then
local partLinks = {}
local partLinks = {}
for _, part in ipairs(parts) do
for _, part in ipairs(parts) do table.insert(partLinks, '[[' .. part.page .. ']]') end
table.insert(partLinks, '[[' .. part.page .. ']]')
local ld = partsGrid:tag('div'):addClass('infobox-label')
end
css(ld, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
ld:wikitext(partType)
local labelDiv = partsGrid:tag('div')
local vd = partsGrid:tag('div'):addClass('infobox-value')
labelDiv:addClass('infobox-label')
css(vd, 'padding:0.2em 0.4em;')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
vd:wikitext(table.concat(partLinks, '<br>'))
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
end
end
-- Add "Add part" button
local addPartDiv = mw.html.create('div')
local addPartDiv = mw.html.create('div')
addPartDiv:css('margin-top', '0.5em'):css('text-align', 'center')
addPartDiv:css('margin-top', '0.5em'):css('text-align', 'center')
Line 813: Line 547:
partsDiv:node(addPartDiv)
partsDiv:node(addPartDiv)
else
else
-- Just show the add part link as a button
local addPartDiv = mw.html.create('div')
local addPartDiv = mw.html.create('div')
addPartDiv:css('text-align', 'center')
addPartDiv:css('text-align', 'center')
Line 820: Line 553:
end
end


-- Archival Links section: Query for link subobjects and display by type
-- Archival Links section
local linksQueryWikitext =
local linksQueryWikitext = '{{#ask:\n [[AV device::' .. fullPageName .. ']]\n |?Model link type\n |?Model link source\n |?Model link URL\n |format=broadtable\n |headers=hide\n |link=none\n}}'
'{{#ask:\n' ..
' [[AV device::' .. 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)
local linksResult = frame:preprocess(linksQueryWikitext)
local linksByType = {Manufacturer = {}, Retailer = {}, Database = {}}
-- Parse links and organize by type
local linksByType = {
Manufacturer = {},
Retailer = {},
Database = {}
}
local hasLinks = false
local hasLinks = false
if linksResult then
if linksResult then
for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cellContent:gsub('<[^>]+>', '')))
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]
local linkSource = cells[3]
local linkSource = cells[3]
local linkUrl = cells[4]
local linkUrl = cells[4]
if linkType and linkSource and linkUrl and linkUrl ~= '' and linksByType[linkType] then
if linkType and linkSource and linkUrl and linkUrl ~= '' then
table.insert(linksByType[linkType], {source = linkSource, url = linkUrl})
if linksByType[linkType] then
hasLinks = true
table.insert(linksByType[linkType], {source = linkSource, url = linkUrl})
hasLinks = true
end
end
end
end
end
end
end
end
end
if hasLinks then
if hasLinks then
addSectionRow(container, 'Archival Links')
addSectionRow(container, 'Archival Links')
for _, linkType in ipairs({'Database', 'Manufacturer', 'Retailer'}) do
local linkTypeOrder = {'Database', 'Manufacturer', 'Retailer'}
for _, linkType in ipairs(linkTypeOrder) do
if #linksByType[linkType] > 0 then
if #linksByType[linkType] > 0 then
local linksHtml = {}
local linksHtml = {}
Line 882: Line 590:
-- Documents section
-- Documents section
addSectionRow(container, 'Documents')
addSectionRow(container, 'Documents')
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", {form = "Document", target = target, ["Document[av_devices]"] = pageName})
"Special:FormEdit",
local documentsCountWikitext = '{{#ask:\n [[Category:Documents]]\n [[Has related AV device::' .. fullPageName .. ']]\n |format=count\n}}'
{
form = "Document",
target = target,
["Document[av_devices]"] = pageName
}
)
-- Query for document count
local documentsCountWikitext =
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local documentCount = frame:preprocess(documentsCountWikitext)
local documentCount = frame:preprocess(documentsCountWikitext)
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
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
local docsQueryWikitext =
local docsQueryWikitext = '{{#ask:\n [[Category:Documents]]\n [[Has related AV device::' .. 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}}'
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related AV device::' .. 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)
local docsResult = frame:preprocess(docsQueryWikitext)
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')
if docsResult then
if docsResult then
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, cellContent:gsub('<[^>]+>', ''))
table.insert(cells, cleanContent)
end
end
if #cells >= 2 then
if #cells >= 2 then
local docType = trim(cells[2])
local docType = trim(cells[2])
local docTitle = trim(cells[5])
local docTitle = trim(cells[5])
local docPageName = trim(cells[1])
local docPageName = trim(cells[1])
if docPageName then
if docPageName then
local displayText = 'Document'
local displayText = 'Document'
if docTitle then
if docTitle then displayText = docTitle
displayText = docTitle
elseif docType then displayText = docType end
elseif docType then
ul:tag('li'):wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
displayText = docType
end
local li = ul:tag('li')
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
end
end
end
end
end
end
end
end
docsDiv:node(ul)
docsDiv:node(ul)
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 document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]')
docsDiv:node(uploadDiv)
docsDiv:node(uploadDiv)
else
else
Line 966: Line 634:
uploadDiv:css('text-align', 'center')
uploadDiv:css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]')
docsDiv:node(uploadDiv)
docsDiv:node(uploadDiv)
end
end
Line 972: Line 639:
-- Contribute section
-- Contribute section
addSectionRow(container, 'Contribute')
addSectionRow(container, 'Contribute')
local editUrl = mw.uri.fullUrl("Special:FormStart", {form = "AV device", page_name = pageName})
local editUrl = mw.uri.fullUrl(
"Special:FormStart",
{
form = "AV device",
page_name = pageName
}
)
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;')
MediaWiki Appliance - Powered by TurnKey Linux