Module:CRTModel: Difference between revisions
Add link=none to outputs query to prevent unwanted linking of count/location (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) |
||
| (36 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) | ||
| Line 80: | Line 239: | ||
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 105: | Line 265: | ||
local speaker_watts = trim(args.speaker_watts) | local speaker_watts = trim(args.speaker_watts) | ||
local subwoofer = trim(args.subwoofer) | 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: | local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'}) | ||
local container = mw.html.create('div') | local container = mw.html.create('div') | ||
| Line 122: | 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 167: | 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 176: | 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 202: | 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 | ||
local chassisDisplay = '' | |||
) | if chassis then | ||
local chassisWikitext = string.format( | |||
addRow(container, 'Chassis', | '{{#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= | '{{#formredlink: target=%s |form=Accessory |existing page link text=%s }}', | ||
remote_control, | remote_control, displayText | ||
) | ) | ||
local remoteHtml = frame:preprocess(remoteWikitext) | local remoteHtml = frame:preprocess(remoteWikitext) | ||
| Line 223: | 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 309: | Line 573: | ||
if range.min and range.max then | if range.min and range.max then | ||
if range.min == range.max then | if range.min == range.max then | ||
rangeStr = range.min .. 'V' | rangeStr = range.min .. 'V' | ||
else | else | ||
rangeStr = range.min .. '–' .. range.max .. 'V' | rangeStr = range.min .. '–' .. range.max .. 'V' | ||
end | end | ||
| Line 326: | Line 588: | ||
if #rangeStrings > 0 then | if #rangeStrings > 0 then | ||
addRow(container, 'Voltage', table.concat(rangeStrings, ' / '), false) | addRow(container, 'Voltage', table.concat(rangeStrings, ' / '), false) | ||
end | end | ||
| Line 354: | Line 615: | ||
addSectionRow(container, 'TV Systems') | addSectionRow(container, 'TV Systems') | ||
if av_system then | if av_system then | ||
local systems = {} | local systems = {} | ||
for system in av_system:gmatch('[^,]+') do | for system in av_system:gmatch('[^,]+') do | ||
| Line 369: | Line 628: | ||
end | end | ||
if tuner_system then | if tuner_system then | ||
local systems = {} | local systems = {} | ||
for system in tuner_system:gmatch('[^,]+') do | for system in tuner_system:gmatch('[^,]+') do | ||
| Line 385: | Line 642: | ||
end | end | ||
-- Scan Range section | -- Scan Range section | ||
if h_scan_min or h_scan_max or v_scan_min or v_scan_max then | if h_scan_min or h_scan_max or v_scan_min or v_scan_max then | ||
addSectionRow(container, 'Scan Range') | addSectionRow(container, 'Scan Range') | ||
if h_scan_min or h_scan_max then | if h_scan_min or h_scan_max then | ||
local hDisplay | local hDisplay | ||
if h_scan_min and h_scan_max then | if h_scan_min and h_scan_max then | ||
if h_scan_min == h_scan_max then | if h_scan_min == h_scan_max then | ||
hDisplay = h_scan_min .. ' kHz' | hDisplay = h_scan_min .. ' kHz' | ||
else | else | ||
hDisplay = h_scan_min .. '–' .. h_scan_max .. ' kHz' | hDisplay = h_scan_min .. '–' .. h_scan_max .. ' kHz' | ||
end | end | ||
| Line 408: | Line 662: | ||
end | end | ||
if v_scan_min or v_scan_max then | if v_scan_min or v_scan_max then | ||
local vDisplay | local vDisplay | ||
if v_scan_min and v_scan_max then | if v_scan_min and v_scan_max then | ||
if v_scan_min == v_scan_max then | if v_scan_min == v_scan_max then | ||
vDisplay = v_scan_min .. ' Hz' | vDisplay = v_scan_min .. ' Hz' | ||
else | else | ||
vDisplay = v_scan_min .. '–' .. v_scan_max .. ' Hz' | vDisplay = v_scan_min .. '–' .. v_scan_max .. ' Hz' | ||
end | end | ||
| Line 428: | Line 679: | ||
end | end | ||
-- Inputs section | -- Inputs, Outputs, and Control Ports sections | ||
local | 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' .. | '{{#ask:\n' .. | ||
' [[Has CRT model::' .. fullPageName .. ']]\n' .. | ' [[Category:Accessories]]\n' .. | ||
' [[Has | ' [[Has related CRT model::' .. fullPageName .. ']]\n' .. | ||
' [[Has accessory type::!Remote control]]\n' .. | |||
' |format=count\n' .. | ' |format=count\n' .. | ||
'}}' | '}}' | ||
local | local accessoryCount = frame:preprocess(accessoriesCountWikitext) | ||
local | local hasAccessories = accessoryCount and tonumber(trim(accessoryCount)) and tonumber(trim(accessoryCount)) > 0 | ||
if | local accDiv = container:tag('div') | ||
local | css(accDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;') | ||
if hasAccessories then | |||
local accessoriesQueryWikitext = | |||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
' [[Has CRT model::' .. fullPageName .. ']]\n' .. | ' [[Category:Accessories]]\n' .. | ||
' [[Has | ' [[Has related CRT model::' .. fullPageName .. ']]\n' .. | ||
' |?Has | ' [[Has accessory type::!Remote control]]\n' .. | ||
' |?Has accessory type=type\n' .. | |||
' |?Has | ' |?Has model code=model\n' .. | ||
' |format=broadtable\n' .. | |||
' |format= | ' |headers=hide\n' .. | ||
' | | |||
' |link=none\n' .. | ' |link=none\n' .. | ||
'}}' | '}}' | ||
local | 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 | end | ||
-- | -- Parts section | ||
local | addSectionRow(container, 'Parts') | ||
local addPartUrl = mw.uri.fullUrl( | |||
"Create a part", | |||
{ | |||
crt_models = pageName | |||
} | |||
) | |||
local partsCountWikitext = | |||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
' [[ | ' [[Category:Parts]]\n' .. | ||
' [[Has | ' [[Has related CRT model::' .. fullPageName .. ']]\n' .. | ||
' |format=count\n' .. | ' |format=count\n' .. | ||
'}}' | '}}' | ||
local | local partsCount = frame:preprocess(partsCountWikitext) | ||
local | local hasParts = partsCount and tonumber(trim(partsCount)) and tonumber(trim(partsCount)) > 0 | ||
if | local partsDiv = container:tag('div') | ||
local | css(partsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;') | ||
if hasParts then | |||
local partsQueryWikitext = | |||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
' [[ | ' [[Category:Parts]]\n' .. | ||
' [[Has | ' [[Has related CRT model::' .. fullPageName .. ']]\n' .. | ||
' |?Has part type=type\n' .. | |||
' |?Has part subtype=subtype\n' .. | |||
' |?Has | ' |?Has model code=model\n' .. | ||
' |?Has | ' |format=broadtable\n' .. | ||
' |?Has | ' |headers=hide\n' .. | ||
' |format= | |||
' | | |||
' |link=none\n' .. | ' |link=none\n' .. | ||
'}}' | '}}' | ||
local | local partsResult = frame:preprocess(partsQueryWikitext) | ||
local partsByType = {} | |||
local partTypeOrder = {} | |||
if partsResult then | |||
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 | ||
-- Mods section | -- Mods section | ||
local modsQueryWikitext = | local modsQueryWikitext = | ||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
| Line 507: | Line 931: | ||
local modsResult = frame:preprocess(modsQueryWikitext) | local modsResult = frame:preprocess(modsQueryWikitext) | ||
local mods = {} | local mods = {} | ||
| Line 548: | Line 971: | ||
end | end | ||
if #mods > 0 then | if #mods > 0 then | ||
addSectionRow(container, 'Mods') | addSectionRow(container, 'Mods') | ||
| Line 561: | Line 983: | ||
local li = ul:tag('li') | local li = ul:tag('li') | ||
local displayText = mod.description or mod.type | local displayText = mod.description or mod.type | ||
local isFeasible = mod.feasible ~= 'No' | local isFeasible = mod.feasible ~= 'No' | ||
if isFeasible then | if isFeasible then | ||
li:wikitext('[' .. mod.url .. ' ' .. displayText .. ']') | li:wikitext('[' .. mod.url .. ' ' .. displayText .. ']') | ||
else | else | ||
local wrapper = li:tag('span') | local wrapper = li:tag('span') | ||
wrapper:addClass('mod-not-feasible') | wrapper:addClass('mod-not-feasible') | ||
| Line 585: | Line 1,002: | ||
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 | ||
| Line 599: | Line 1,015: | ||
) | ) | ||
local documentsCountWikitext = | local documentsCountWikitext = | ||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
| Line 609: | 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 | ||
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' .. | '{{#ask:\n' .. | ||
| Line 629: | Line 1,042: | ||
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('<[^>]+>', '') | local cleanContent = cellContent:gsub('<[^>]+>', '') | ||
table.insert(cells, cleanContent) | table.insert(cells, cleanContent) | ||
end | end | ||
if #cells >= 2 then | if #cells >= 2 then | ||
local docType = trim(cells[2]) | local docType = trim(cells[2]) | ||
| Line 652: | Line 1,059: | ||
local docTitle = trim(cells[5]) | local docTitle = trim(cells[5]) | ||
local linkTarget = nil | local linkTarget = nil | ||
if docUrl then | if docUrl then | ||
linkTarget = docUrl | linkTarget = docUrl | ||
elseif docFile then | elseif docFile then | ||
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 | ||
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 | ||
| Line 674: | Line 1,077: | ||
end | end | ||
local li = ul:tag('li') | local li = ul:tag('li') | ||
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]') | li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]') | ||
| Line 684: | Line 1,086: | ||
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 | uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]') | ||
docsDiv:node(uploadDiv) | docsDiv:node(uploadDiv) | ||
else | else | ||
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 | uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add document</span>]') | ||
docsDiv:node(uploadDiv) | docsDiv:node(uploadDiv) | ||
end | end | ||
-- Links section | -- Links section | ||
local linksQueryWikitext = | local linksQueryWikitext = | ||
'{{#ask:\n' .. | '{{#ask:\n' .. | ||
| Line 712: | Line 1,112: | ||
local linksResult = frame:preprocess(linksQueryWikitext) | local linksResult = frame:preprocess(linksQueryWikitext) | ||
local linksByType = { | local linksByType = { | ||
Manufacturer = {}, | Manufacturer = {}, | ||
| Line 721: | Line 1,120: | ||
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('<[^>]+>', '') | local cleanContent = cellContent:gsub('<[^>]+>', '') | ||
table.insert(cells, trim(cleanContent)) | table.insert(cells, trim(cleanContent)) | ||
end | end | ||
if #cells >= 4 then | if #cells >= 4 then | ||
local linkType = cells[2] | local linkType = cells[2] | ||
| Line 747: | Line 1,142: | ||
end | end | ||
if hasLinks then | if hasLinks then | ||
addSectionRow(container, 'Archival Links') | addSectionRow(container, 'Archival Links') | ||
local linkTypeOrder = {'Database', 'Manufacturer', 'Retailer'} | |||
local | for _, linkType in ipairs(linkTypeOrder) do | ||
for _, linkType in ipairs( | |||
if #linksByType[linkType] > 0 then | if #linksByType[linkType] > 0 then | ||
local linksHtml = {} | local linksHtml = {} | ||
for _, link in ipairs(linksByType[linkType]) do | for _, link in ipairs(linksByType[linkType]) do | ||
| Line 768: | Line 1,160: | ||
addSectionRow(container, 'Contribute') | addSectionRow(container, 'Contribute') | ||
local editUrl = mw.uri.fullUrl( | local editUrl = mw.uri.fullUrl( | ||
"Special:FormStart", | "Special:FormStart", | ||
| Line 777: | Line 1,168: | ||
) | ) | ||
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;') | ||
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 styleTag .. tostring(container) | return styleTag .. tostring(container) | ||
end | end | ||
return p | return p | ||