Module:AVDevice: Difference between revisions

Connectivity labels: use infobox-label-top class for non-stretching labels, remove inline align-self (via update-page on MediaWiki MCP Server)
Restore pre-compression source from rev 8199 (with comments, formatting, safe gsub patterns). Only change: args.oem_manufacturer → args.oem (the intended rename from rev 8313, without the destructive compression) (via update-page on MediaWiki MCP Server)
 
(23 intermediate revisions by the same user not shown)
Line 56: Line 56:
fullDiv:wikitext(valueHtmlOrWikitext)
fullDiv:wikitext(valueHtmlOrWikitext)
end
end
end
-- Helper function to strip brand prefix from item name if it matches device brand
local function getDisplayTextWithoutBrand(itemName, deviceBrand)
if not itemName then return nil end
if not deviceBrand then return itemName end
local brandPrefix = deviceBrand .. ' '
if itemName:sub(1, #brandPrefix) == brandPrefix then
return itemName:sub(#brandPrefix + 1)
end
return itemName
end
end


Line 95: Line 108:
end
end
return '$' .. formatted
return '$' .. formatted
end
-- Helper to format related models as wiki links, stripping brand prefix
local function formatRelatedModels(value, deviceBrand)
if not value then return nil end
local items = {}
for item in value:gmatch('[^,]+') do
local trimmed = trim(item)
if trimmed then
local displayText = getDisplayTextWithoutBrand(trimmed, deviceBrand)
table.insert(items, '[[' .. trimmed .. '|' .. displayText .. ']]')
end
end
if #items == 0 then return nil end
return table.concat(items, '<br>')
end
end


Line 140: Line 168:
end
end


-- Helper to count inputs/outputs for a specific location
-- Helper to discover all distinct location values for a given IO type
local function countIOByLocation(frame, fullPageName, ioType, location)
-- Returns an ordered list of location strings, sorted with common locations first
local locationFilter = ''
local function discoverLocations(frame, fullPageName, ioType)
if location then
local locationProp, signalProp
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n'
if ioType == 'input' then
locationProp = 'Has input location'
signalProp = 'Has video signal format'
else -- output
locationProp = 'Has output location'
signalProp = 'Has output signal format'
end
end
 
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format'
local queryWikitext =
local countWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Has AV device::' .. fullPageName .. ']]\n' ..
' [[Has AV device::' .. fullPageName .. ']]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
locationFilter ..
' [[' .. locationProp .. '::+]]\n' ..
' |format=count\n' ..
' |?' .. locationProp .. '=location\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
'}}'
local result = frame:preprocess(countWikitext)
local count = tonumber(trim(result)) or 0
return count
end


-- Helper to build location-grouped I/O content for a value cell
local result = frame:preprocess(queryWikitext)
-- Returns HTML string with stripe groups per location, or nil if empty
 
local function buildLocationGroupedIO(frame, fullPageName, ioType, locations, locationLabels)
-- Parse distinct locations from broadtable rows
local html = ''
local seen = {}
local locations = {}
for _, loc in ipairs(locations) do
 
local count = countIOByLocation(frame, fullPageName, ioType, loc)
-- Preferred display order for common locations
if count > 0 then
local preferredOrder = {rear = 1, front = 2, side = 3, top = 4, bottom = 5, internal = 6}
local locContent = queryIOByLocation(frame, fullPageName, ioType, loc)
 
if locContent and locContent ~= '' then
if result then
html = html ..
for rowContent in result:gmatch('<tr[^>]*>(.-)</tr>') do
'<div class="infobox-io-location-group">' ..
local cells = {}
'<div class="infobox-io-location-header">' .. locationLabels[loc] .. '</div>' ..
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
'<div class="infobox-io-location-content">' .. locContent .. '</div>' ..
local cleanContent = cellContent:gsub('<[^>]+>', '')
'</div>'
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
end
end
end
 
if html == '' then return nil end
-- Sort: preferred locations first (in defined order), then remaining alphabetically
return html
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 197: Line 251:
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)
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)
local market = trim(args.market)
local supported_formats = trim(args.supported_formats)
local supported_formats = trim(args.supported_formats)
local recording_formats = trim(args.recording_formats)
local recording_formats = trim(args.recording_formats)
Line 215: Line 271:
local depth = trim(args.depth)
local depth = trim(args.depth)
local weight = trim(args.weight)
local weight = trim(args.weight)
local related_models = trim(args.related_models)


-- Use frame:extensionTag to create a proper <style> tag
-- Use frame:extensionTag to create a proper <style> tag
Line 232: Line 289:
'infobox-title'
'infobox-title'
)
)
-- Type badge (breadcrumb) — plainlinks hides external icon, $wgNoFollowDomainExceptions handles nofollow
local badgeWikitext = '[[AV Device Search|AV device]]'
if device_type then
local typeUrl = tostring(mw.uri.fullUrl('AV Device Search', {device_type = device_type}))
badgeWikitext = badgeWikitext .. ' › <span class="plainlinks">[' .. typeUrl .. ' ' .. device_type .. ']</span>'
end
addFullRow(container, badgeWikitext, '', false, 'infobox-type-badge')


if image_main then
if image_main then
Line 254: Line 319:
local brandHtml = frame:preprocess(brandWikitext)
local brandHtml = frame:preprocess(brandWikitext)
addRow(container, 'Brand', brandHtml, true)
addRow(container, 'Brand', brandHtml, true)
end
-- OEM row (shown only when set)
if oem_manufacturer then
local oemWikitext = string.format(
'{{#formredlink: target=%s |form=Manufacturer |existing page link text=%s }}',
oem_manufacturer, oem_manufacturer
)
local oemHtml = frame:preprocess(oemWikitext)
addRow(container, 'OEM', oemHtml, true)
end
end


Line 260: Line 335:
end
end


-- Market row
if market then
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
addRow(container, 'Type', '[[' .. device_type .. ']]', true)
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', typeWikitext, true)
end
end


Line 292: Line 375:
if formattedAV then
if formattedAV then
addRow(container, 'AV system', formattedAV, false)
addRow(container, 'AV system', formattedAV, false)
end
end
-- Related models
if related_models then
local formattedRelated = formatRelatedModels(related_models, brand)
if formattedRelated then
addRow(container, 'Related', formattedRelated, true)
end
end
end
end
Line 318: Line 409:
end
end


-- Connectivity section - Inputs and Outputs as main rows,
-- Inputs and Outputs sections (matching CRT model style)
-- with location stripe groups inside each value cell
local function renderIOSection(sectionTitle, ioType)
local locations = {'front', 'rear', 'side'}
local locations = discoverLocations(frame, fullPageName, ioType)
local locationLabels = {front = 'Front', rear = 'Rear', side = 'Side'}
local locationData = {}
local inputsHtml = buildLocationGroupedIO(frame, fullPageName, 'input', locations, locationLabels)
local outputsHtml = buildLocationGroupedIO(frame, fullPageName, 'output', locations, locationLabels)


if inputsHtml or outputsHtml then
for _, loc in ipairs(locations) do
addSectionRow(container, 'Connectivity')
local content = queryIOByLocation(frame, fullPageName, ioType, loc)
if content and content ~= '' then
-- Custom rows for Inputs/Outputs: label top-aligned via CSS class,
table.insert(locationData, {location = loc, content = content})
-- value has no padding so location group header aligns flush with label
end
if inputsHtml then
end
local labelDiv = container:tag('div')
labelDiv:addClass('infobox-label')
labelDiv:addClass('infobox-label-top')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext('Inputs')


local valueDiv = container:tag('div')
if #locationData > 0 then
valueDiv:addClass('infobox-value')
addSectionRow(container, sectionTitle)
css(valueDiv, 'padding:0;')
for _, data in ipairs(locationData) do
valueDiv:node(mw.html.create('span'):wikitext(inputsHtml))
local labelDiv = container:tag('div')
end
labelDiv:addClass('infobox-label')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
if outputsHtml then
labelDiv:wikitext(locationLabel(data.location))
local labelDiv = container:tag('div')
labelDiv:addClass('infobox-label')
labelDiv:addClass('infobox-label-top')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext('Outputs')


local valueDiv = container:tag('div')
local valueDiv = container:tag('div')
valueDiv:addClass('infobox-value')
valueDiv:addClass('infobox-value')
css(valueDiv, 'padding:0;')
css(valueDiv, 'padding:0.2em 0.4em;')
valueDiv:node(mw.html.create('span'):wikitext(outputsHtml))
valueDiv:node(mw.html.create('span'):wikitext(data.content))
end
end
end
end
end
renderIOSection('Inputs', 'input')
renderIOSection('Outputs', 'output')


-- Features section
-- Features section
Line 513: Line 595:
end
end


-- Archival Links section - query AV link subobjects
-- Accessories section: Always show (like Parts/Documents), with "Add accessory" button
local linksCountWikitext =
addSectionRow(container, 'Accessories')
 
-- Build the add accessory link pointing to Create_an_accessory with av_devices parameter
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' ..
'{{#ask:\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[Category:Accessories]]\n' ..
' [[Model link URL::+]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
' |format=count\n' ..
'}}'
'}}'
local linkCount = frame:preprocess(linksCountWikitext)
local accessoryCount = frame:preprocess(accessoriesCountWikitext)
local hasLinks = linkCount and tonumber(trim(linkCount)) and tonumber(trim(linkCount)) > 0
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
-- 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 accessoriesResult = frame:preprocess(accessoriesQueryWikitext)
 
-- Parse accessories from broadtable result and group by type
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
 
-- cells[1] = page name, cells[2] = type, cells[3] = model code
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


if hasLinks then
table.insert(accessoriesByType[accType], {
addSectionRow(container, 'Archival Links')
page = accPageName
})
local linksListWikitext =
end
end
end
end
 
-- Create a nested grid for accessory rows
local accGrid = accDiv:tag('div')
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
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
 
-- Add "Add accessory" button
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
-- Just show the add accessory link as a button
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')
-- Build the add part link pointing to Create_a_part with av_devices parameter
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 hasParts = partsCount and tonumber(trim(partsCount)) and tonumber(trim(partsCount)) > 0
local partsDiv = container:tag('div')
css(partsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
if hasParts then
-- Query parts and display grouped by type/subtype
local partsQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[Category:Parts]]\n' ..
' [[Model link URL::+]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |?Model link type=type\n' ..
' |?Has part type=type\n' ..
' |?Model link source=source\n' ..
' |?Has part subtype=subtype\n' ..
' |?Model link URL=url\n' ..
' |?Has model code=model\n' ..
' |format=template\n' ..
' |format=broadtable\n' ..
' |template=AV device link row\n' ..
' |headers=hide\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
' |link=none\n' ..
'}}'
'}}'
local linksResult = frame:preprocess(linksListWikitext)
local partsResult = frame:preprocess(partsQueryWikitext)
-- Parse parts from broadtable result and group by type
local partsByType = {}
local partTypeOrder = {}
local linksDiv = container:tag('div')
if partsResult then
css(linksDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
for rowContent in partsResult:gmatch('<tr[^>]*>(.-)</tr>') do
linksDiv:wikitext(linksResult)
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
end
-- cells[1] = page name, cells[2] = type, cells[3] = subtype, cells[4] = model code
if #cells >= 2 then
local partPageName = cells[1]
local partType = cells[2] or 'Part'
local partSubtype = cells[3]
-- Use subtype + type as the grouping key if subtype exists
local groupKey = partType
if partSubtype and partSubtype ~= '' then
groupKey = partSubtype .. ' ' .. partType
end
if partPageName then
-- Track type order for consistent display
if not partsByType[groupKey] then
partsByType[groupKey] = {}
table.insert(partTypeOrder, groupKey)
end
table.insert(partsByType[groupKey], {
page = partPageName
})
end
end
end
end
-- Create a nested grid for part rows
local partsGrid = partsDiv:tag('div')
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
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
-- Add "Add part" button
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
-- Just show the add part link as a button
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
 
-- Archival Links section: Query for link subobjects and display by type
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' ..
'}}'
local linksResult = frame:preprocess(linksQueryWikitext)
-- Parse links and organize by type
local linksByType = {
Manufacturer = {},
Retailer = {},
Database = {}
}
local hasLinks = false
if linksResult then
for rowContent in linksResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, trim(cleanContent))
end
-- cells[1] = page/subobject, cells[2] = type, cells[3] = source, cells[4] = URL
if #cells >= 4 then
local linkType = cells[2]
local linkSource = cells[3]
local linkUrl = cells[4]
if linkType and linkSource and linkUrl and linkUrl ~= '' then
if linksByType[linkType] then
table.insert(linksByType[linkType], {source = linkSource, url = linkUrl})
hasLinks = true
end
end
end
end
end
if hasLinks then
addSectionRow(container, 'Archival Links')
local linkTypeOrder = {'Database', 'Manufacturer', 'Retailer'}
for _, linkType in ipairs(linkTypeOrder) do
if #linksByType[linkType] > 0 then
local linksHtml = {}
for _, link in ipairs(linksByType[linkType]) do
table.insert(linksHtml, '[' .. link.url .. ' ' .. link.source .. ']')
end
addRow(container, linkType, table.concat(linksHtml, '<br>'), true)
end
end
end
end


Line 602: Line 937:
if #cells >= 2 then
if #cells >= 2 then
local docType = trim(cells[2])
local docType = trim(cells[2])
local docUrl = trim(cells[3])
local docFile = trim(cells[4])
local docTitle = trim(cells[5])
local docTitle = trim(cells[5])
local docPageName = trim(cells[1])
local docPageName = trim(cells[1])
Line 626: Line 959:
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)
Line 632: Line 965:
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)
MediaWiki Appliance - Powered by TurnKey Linux