Module:AVDevice: Difference between revisions

Add Accessories section to AV device infobox (matching CRT model pattern, using Has related AV device) (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)
 
(19 intermediate revisions by the same user not shown)
Line 108: 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 153: 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 210: 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 228: 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 245: 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 267: 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 273: 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 305: 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 331: 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
-- Connectivity rows: label stretches full height (default grid behavior),
table.insert(locationData, {location = loc, content = content})
-- value has no padding so location groups sit flush
end
if inputsHtml then
end
local labelDiv = container:tag('div')
labelDiv:addClass('infobox-label')
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')
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 524: Line 595:
end
end


-- Accessories section: Query for non-remote accessories linked via Has related AV device
-- Accessories section: Always show (like Parts/Documents), with "Add accessory" button
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 =
local accessoriesCountWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[Category:Accessories]]\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' [[Has accessory type::!Remote control]]\n' ..
' |format=count\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')
css(accDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
 
if hasAccessories then
if hasAccessories then
addSectionRow(container, 'Accessories')
-- Query accessories and display grouped by type
-- Query accessories and display grouped by type
local accessoriesQueryWikitext =
local accessoriesQueryWikitext =
Line 543: Line 627:
' [[Category:Accessories]]\n' ..
' [[Category:Accessories]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' [[Has accessory type::!Remote control]]\n' ..
' |?Has accessory type=type\n' ..
' |?Has accessory type=type\n' ..
' |?Has model code=model\n' ..
' |?Has model code=model\n' ..
Line 551: Line 634:
'}}'
'}}'
local accessoriesResult = frame:preprocess(accessoriesQueryWikitext)
local accessoriesResult = frame:preprocess(accessoriesQueryWikitext)
 
-- Parse accessories from broadtable result and group by type
-- 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
Line 563: Line 646:
table.insert(cells, trim(cleanContent))
table.insert(cells, trim(cleanContent))
end
end
 
-- cells[1] = page name, cells[2] = type, cells[3] = model code
-- 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
-- Track type order for consistent display
if not accessoriesByType[accType] then
if not accessoriesByType[accType] then
accessoriesByType[accType] = {}
accessoriesByType[accType] = {}
table.insert(typeOrder, accType)
table.insert(typeOrder, accType)
end
end
 
table.insert(accessoriesByType[accType], {
table.insert(accessoriesByType[accType], {
page = accPageName
page = accPageName
Line 583: Line 665:
end
end
end
end
 
-- Display as label/value rows grouped by type (like Archival Links)
-- 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
for _, accType in ipairs(typeOrder) do
local accs = accessoriesByType[accType]
local accs = accessoriesByType[accType]
Line 590: Line 676:
local accLinks = {}
local accLinks = {}
for _, acc in ipairs(accs) do
for _, acc in ipairs(accs) do
-- Show just "Brand Model" as the link text (the page name)
table.insert(accLinks, '[[' .. acc.page .. ']]')
table.insert(accLinks, '[[' .. acc.page .. ']]')
end
end
addRow(container, accType, table.concat(accLinks, '<br>'), true)
 
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
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
end


Line 601: Line 707:
addSectionRow(container, 'Parts')
addSectionRow(container, 'Parts')
-- Build the add part link with prefilled AV device
-- Build the add part link pointing to Create_a_part with av_devices parameter
local partTimestamp = os.time()
math.randomseed(partTimestamp + pageId + 1)
local partRandom = math.random(1000, 9999)
local partTarget = partTimestamp .. "-" .. partRandom
local addPartUrl = mw.uri.fullUrl(
local addPartUrl = mw.uri.fullUrl(
"Special:FormEdit",
"Create a part",
{
{
form = "Part",
av_devices = pageName
target = partTarget,
["Part[av_devices]"] = pageName
}
}
)
)
Line 662: Line 762:
-- Use subtype + type as the grouping key if subtype exists
-- Use subtype + type as the grouping key if subtype exists
-- Keep proper capitalization (e.g., "Frame synchronizer IC" not "Frame synchronizer ic")
local groupKey = partType
local groupKey = partType
if partSubtype and partSubtype ~= '' then
if partSubtype and partSubtype ~= '' then
Line 693: Line 792:
local partLinks = {}
local partLinks = {}
for _, part in ipairs(parts) do
for _, part in ipairs(parts) do
-- Just show the page name (which is "Brand Model"), no description
table.insert(partLinks, '[[' .. part.page .. ']]')
table.insert(partLinks, '[[' .. part.page .. ']]')
end
end
Line 709: Line 807:
end
end
-- Add "Add a part" button
-- 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')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a part</span>]')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add part</span>]')
partsDiv:node(addPartDiv)
partsDiv:node(addPartDiv)
else
else
Line 718: Line 816:
local addPartDiv = mw.html.create('div')
local addPartDiv = mw.html.create('div')
addPartDiv:css('text-align', 'center')
addPartDiv:css('text-align', 'center')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a part</span>]')
addPartDiv:wikitext('[' .. tostring(addPartUrl) .. ' <span class="infobox-button infobox-button-secondary">Add part</span>]')
partsDiv:node(addPartDiv)
partsDiv:node(addPartDiv)
end
end


-- Archival Links section - query AV link subobjects
-- Archival Links section: Query for link subobjects and display by type
local linksCountWikitext =
local linksQueryWikitext =
'{{#ask:\n' ..
'{{#ask:\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[Model link URL::+]]\n' ..
' |?Model link type\n' ..
' |format=count\n' ..
' |?Model link source\n' ..
' |?Model link URL\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
'}}'
local linkCount = frame:preprocess(linksCountWikitext)
local linksResult = frame:preprocess(linksQueryWikitext)
local hasLinks = linkCount and tonumber(trim(linkCount)) and tonumber(trim(linkCount)) > 0
 
-- 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
if hasLinks then
addSectionRow(container, 'Archival Links')
addSectionRow(container, 'Archival Links')
local linksListWikitext =
local linkTypeOrder = {'Database', 'Manufacturer', 'Retailer'}
'{{#ask:\n' ..
for _, linkType in ipairs(linkTypeOrder) do
' [[AV device::' .. fullPageName .. ']]\n' ..
if #linksByType[linkType] > 0 then
' [[Model link URL::+]]\n' ..
local linksHtml = {}
' |?Model link type=type\n' ..
for _, link in ipairs(linksByType[linkType]) do
' |?Model link source=source\n' ..
table.insert(linksHtml, '[' .. link.url .. ' ' .. link.source .. ']')
' |?Model link URL=url\n' ..
end
' |format=template\n' ..
addRow(container, linkType, table.concat(linksHtml, '<br>'), true)
' |template=AV device link row\n' ..
end
' |named args=yes\n' ..
end
' |link=none\n' ..
'}}'
local linksResult = frame:preprocess(linksListWikitext)
local linksDiv = container:tag('div')
css(linksDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
linksDiv:wikitext(linksResult)
end
end


Line 811: 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 835: 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 841: 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