Module:Inventory slot: Difference between revisions
Actually, we were just using an old version of minetip. Updated to support an even newer version of minetip. Now passes along idData to the main sprite function rather than using the base function, so this gets its tracking categories. |
Reverted my changes on this for now Tag: Manual revert |
||
| (81 intermediate revisions by 9 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local i18n = { | |||
modLink = '$1:$2', | |||
moduleAliases = [[Module:Inventory slot/Aliases]], | |||
moduleRandom = [[Module:Random]], | |||
-- List of special prefixes which should be handled by | |||
-- other modules (such as being moved outside links) | |||
prefixes = { | |||
any = 'Any', | |||
matching = 'Matching', | |||
damaged = 'Damaged', | |||
unwaxed = 'Unwaxed', | |||
}, | |||
suffixes = { | |||
be = 'BE', | |||
lce = 'LCE', | |||
}, | |||
} | |||
p.i18n = i18n | |||
local random = require( i18n.moduleRandom ).random | |||
local aliases = mw.loadData( i18n.moduleAliases ) | |||
local pageName = mw.title.getCurrentTitle().text | |||
--[[Splits a given text into fragments separated by semicolons that are not | |||
inside square brackets. Written by AttemptToCallNil for the Russian wiki | |||
--]] | |||
local function splitOnUnenclosedSemicolons(text) | |||
local semicolon, lbrace, rbrace = (";[]"):byte(1, 3) | |||
local nesting = false | |||
local splitStart = 1 | |||
local frameIndex = 1 | |||
local frames = {} | |||
for index = 1, text:len() do | |||
local byte = text:byte(index) | |||
if byte == semicolon and not nesting then | |||
frames[frameIndex] = text:sub(splitStart, index - 1) | |||
frameIndex = frameIndex + 1 | |||
splitStart = index + 1 | |||
elseif byte == lbrace then | |||
assert(not nesting, "Excessive square brackets found") | |||
nesting = true | |||
elseif byte == rbrace then | |||
assert(nesting, "Unbalanced square brackets found") | |||
nesting = false | |||
end | |||
end | |||
assert(not nesting, "Unbalanced square brackets found") | |||
frames[frameIndex] = text:sub(splitStart, text:len()) | |||
for index = 1, #frames do | |||
frames[index] = (frames[index]:gsub("^%s+", ""):gsub("%s+$", "")) -- faster mw.text.trim | |||
end | |||
return frames | |||
end | |||
-- Performs a simple recursive clone of a table's values | |||
local function cloneTable( origTable ) | |||
local newTable = {} | |||
for k, v in pairs( origTable ) do | |||
if type( v ) == 'table' then | |||
v = cloneTable( v ) | |||
end | |||
newTable[k] = v | |||
end | |||
return newTable | |||
end | |||
--[[Merges a list, or inserts a string | |||
or table into a table | |||
--]] | |||
local function mergeList( parentTable, content ) | |||
local i = #parentTable + 1 | |||
if content[1] then | |||
-- Merge list into table | |||
for _, v in ipairs( content ) do | |||
parentTable[i] = v | |||
i = i + 1 | |||
end | |||
else | |||
-- Add strings or tables to table | |||
parentTable[i] = content | |||
end | |||
end | |||
-- Creates the HTML for an item | |||
local function makeItem( frame, i, args ) | |||
local item = mw.html.create( 'span' ):addClass( 'invslot-item' ) | |||
if args.imgclass then | |||
item:addClass( args.imgclass ) | |||
end | |||
if frame.name == '' then | |||
return item | |||
end | |||
local category | |||
local title = frame.title or mw.text.trim( args.title or '' ) | |||
local num = frame.num | |||
local description = frame.text | |||
local mod = frame.mod or 'Minecraft' | |||
local name = frame.name | |||
if frame.name:match( '%.gif$' ) and frame.name:match( '%.png$' ) then | |||
name = frame.name | |||
mod = '' | |||
end | |||
local minecraft = mod == "Minecraft" or mod == "minecraft" | |||
local img = frame.img | |||
if not img then | |||
if name:match( '%.gif$' ) or name:match( '%.png$' ) then | |||
img = name | |||
-- Remove file extension from name | |||
name = name:sub( 0, -5 ) | |||
elseif minecraft then | |||
img = 'Invicon ' .. name .. '.png' | |||
elseif mod then | |||
img = mod .. ' ' .. name .. '.png' | |||
else | |||
img = name | |||
end | |||
end | |||
if name:match( ' Revision %d+' ) then | |||
name = name:gsub( ' Revision %d+', '' ) | |||
end | |||
local link = args.link or '' | |||
if link == '' then | |||
link = i18n.modLink:gsub( '%$1', mod ):gsub( '%$2', name ) | |||
elseif link:lower() == 'none' then | |||
link = nil | |||
end | |||
if link == pageName then | |||
link = nil | |||
end | |||
local formattedTitle | |||
local plainTitle | |||
if title == '' then | |||
plainTitle = name | |||
elseif title:lower() ~= 'none' then | |||
plainTitle = title:gsub( '\\\\', '\' ):gsub( '\\&', '&' ) | |||
local formatPatterns = {'&[0-9a-jl-qs-vyzr]', '&#%x%x%x%x%x%x', '&$%x%x%x'} | |||
for _, formatPattern in ipairs( formatPatterns ) do | |||
if plainTitle:match( formatPattern ) then | |||
formattedTitle = title | |||
plainTitle = plainTitle:gsub( formatPattern, '' ) | |||
end | |||
end | |||
if plainTitle == '' then | |||
plainTitle = name | |||
else | |||
plainTitle = plainTitle:gsub( '\', '\\' ):gsub( '&', '&' ) | |||
end | |||
elseif link then | |||
formattedTitle = '' | |||
end | |||
item:attr{ | |||
['data-minetip-title'] = formattedTitle, | |||
['data-minetip-text'] = description | |||
} | |||
-- & is re-escaped because mw.html treats attributes | |||
-- as plain text, but MediaWiki doesn't | |||
local escapedTitle = ( plainTitle or '' ):gsub( '&', '&' ) | |||
local altText = img .. ': Inventory sprite for ' .. name .. ' in Minecraft as shown in-game' | |||
if link then | |||
altText = altText .. ' linking to ' .. link | |||
end | |||
if formattedTitle or plainTitle or link then | |||
altText = altText .. ' with description: ' .. ( formattedTitle or plainTitle or link ) | |||
if description then | |||
altText = altText .. ' ' .. description:gsub( '/', ' ' ) | |||
end | |||
altText = altText:gsub( '&[0-9a-jl-qs-wr]', '' ) | |||
end | |||
item:addClass( 'invslot-item-image' ) | |||
:wikitext( '[[File:', img, '|32x32px|link=', link or '', '|alt=', altText, '|', escapedTitle, ']]' ) | |||
if num and num > 1 and num < 1000 then | |||
if link then | |||
item:wikitext( '[[', link, '|' ) | |||
end | |||
local number = item | |||
:tag( 'span' ) | |||
:addClass( 'invslot-stacksize' ) | |||
:attr{ title = plainTitle } | |||
:wikitext( num ) | |||
if args.numstyle then | |||
number:cssText( args.numstyle ) | |||
end | |||
if link then | |||
item:wikitext( ']]' ) | |||
end | |||
end | |||
item:wikitext( category ) | |||
return item | |||
end | |||
-- Main entry point | |||
function p.slot( f ) | function p.slot( f ) | ||
local args = f.args or f | local args = f.args or f | ||
| Line 7: | Line 218: | ||
end | end | ||
args[1] = mw.text.trim( args[1] or '' ) | if not args.parsed then | ||
args[1] = mw.text.trim( args[1] or '' ) | |||
end | |||
local modData = { | |||
aliases = args.modaliases or '', | |||
default = args.mod | |||
} | |||
if | if modData.aliases ~= '' then | ||
modData.aliases = mw.loadData( 'Module:' .. modData.aliases ) | |||
else | else | ||
modData.aliases = nil | |||
end | |||
if args.mod == '' then | |||
modData.default = nil | |||
end | end | ||
if | local frames | ||
if args.parsed then | |||
frames = args[1] | |||
elseif args[1] ~= '' then | |||
local randomise = args.class == 'invslot-large' and 'never' or nil | |||
frames = p.parseFrameText( args[1], randomise, false, modData ) | |||
end | end | ||
local animated = frames and #frames > 1 | |||
local animated = | |||
local imgClass = args.imgclass | local imgClass = args.imgclass | ||
local numStyle = args.numstyle | local numStyle = args.numstyle | ||
| Line 62: | Line 254: | ||
if args.style then | if args.style then | ||
body:cssText( args.style ) | body:cssText( args.style ) | ||
end | |||
if ( args.default or '' ) ~= '' then | |||
body:addClass( 'invslot-default-' .. string.lower( args.default ):gsub( ' ', '-' ) ) | |||
end | end | ||
--mw.logObject( frames ) | |||
body | if not frames then | ||
return tostring( body ) | |||
end | end | ||
local | local activeFrame = frames.randomise == true and random( #frames ) or 1 | ||
for frame in | for i, frame in ipairs( frames ) do | ||
local item | local item | ||
if frame | -- Table is a list, must contain subframes | ||
item = body:tag( 'span' ):addClass( ' | if frame[1] then | ||
if | item = body:tag( 'span' ):addClass( 'animated-subframe' ) | ||
local subActiveFrame = frame.randomise == true and random( #frame ) or 1 | |||
for sI, sFrame in ipairs( frame ) do | |||
local sItem = makeItem( sFrame, sI, args ) | |||
item:node( sItem ) | |||
if sI == subActiveFrame then | |||
sItem:addClass( 'animated-active' ) | |||
end | |||
end | end | ||
else | |||
item = makeItem( frame, i, args ) | |||
body:node( item ) | |||
end | |||
if i == activeFrame and animated then | |||
item:addClass( 'animated-active' ) | |||
end | |||
end | |||
return tostring( body ) | |||
end | |||
--[[Parses the frame text into a table of frames and subframes, | |||
expanding aliases (and optionally retaining a reference), and | |||
deciding if the slot can be randomised | |||
--]] | |||
function p.parseFrameText( framesText, randomise, aliasReference, modData ) | |||
local frames = { randomise = randomise } | |||
local subframes = {} | |||
local subframe | |||
local expandedAliases | |||
local splitFrames = splitOnUnenclosedSemicolons( framesText ) | |||
for i, frameText in ipairs( splitFrames ) do | |||
frameText = frameText:gsub( '^%s*{%s*', function() | |||
subframe = true | |||
return '' | |||
end ) | |||
if subframe then | |||
frameText = frameText:gsub( '%s*}%s*$', function() | |||
subframe = 'last' | |||
return '' | |||
end ) | |||
end | end | ||
local frame = p.makeFrame( frameText, modData and modData.default ) | |||
local newFrame = frame | |||
if aliases or modData.aliases then | |||
local id = frame.name | |||
if frame.mod then | |||
local | id = frame.mod .. ':' .. id | ||
end | |||
local | local alias = modData and modData.aliases and modData.aliases[id] or | ||
if | aliases and aliases[id] | ||
if alias then | |||
if | newFrame = p.getAlias( alias, frame ) | ||
if aliasReference then | |||
local curFrame = #frames + 1 | |||
local aliasData = { frame = frame, length = #newFrame } | |||
if subframe then | |||
if not subframes.aliasReference then | |||
subframes.aliasReference = {} | |||
end | |||
subframes.aliasReference[#subframes + 1] = aliasData | |||
else | |||
if not expandedAliases then | |||
expandedAliases = {} | |||
end | |||
expandedAliases[curFrame] = aliasData | |||
end | |||
end | end | ||
end | |||
end | |||
if subframe then | |||
mergeList( subframes, newFrame ) | |||
-- Randomise starting frame for "Any *" aliases, as long as the alias is the only subframe | |||
if frames.randomise ~= 'never' and subframes.randomise == nil and | |||
frame.name:match( '^' .. i18n.prefixes.any .. ' ' ) then | |||
subframes.randomise = true | |||
else | else | ||
subframes.randomise = false | |||
end | end | ||
if frames.randomise ~= 'never' then | |||
frames.randomise = false | |||
end | end | ||
if subframe == 'last' then | |||
-- No point having a subframe containing a single frame, | |||
-- or the subframe being the only frame | |||
if | if #subframes == 1 or #splitFrames == i and #frames == 0 then | ||
local lastFrame = #frames | |||
mergeList( frames, subframes ) | |||
-- Inherit the randomise flag if it's the only frame | |||
if #splitFrames == 1 then | |||
frames.randomise = subframes.randomise | |||
end | |||
-- Append alias reference data, if present | |||
if aliasReference and subframes.aliasReference then | |||
if not expandedAliases then | |||
expandedAliases = {} | |||
end | |||
for i, aliasRefData in pairs(subframes.aliasReference) do | |||
expandedAliases[lastFrame + i] = aliasRefData | |||
end | |||
end | |||
else | else | ||
table.insert( frames, subframes ) | |||
end | end | ||
subframes = {} | |||
subframe = nil | |||
end | end | ||
else | |||
-- Randomise starting frame for "Any *" aliases, as long as the alias is the only frame | |||
if frames.randomise ~= 'never' and frame.name:match( '^' .. i18n.prefixes.any .. ' ' ) then | |||
frames.randomise = true | |||
if | |||
else | else | ||
frames.randomise = false | |||
end | end | ||
mergeList( frames, newFrame ) | |||
end | end | ||
end | end | ||
return | frames.aliasReference = expandedAliases | ||
return frames | |||
end | end | ||
function p. | --[[Returns a new table with the parts of the parent frame | ||
-- If the frame | added to the alias | ||
if | --]] | ||
function p.getAlias( aliasFrames, parentFrame ) | |||
-- If alias is just a name, return the parent frame with the new name | |||
if type( aliasFrames ) == 'string' then | |||
local expandedFrame = mw.clone( parentFrame ) | |||
expandedFrame.name = aliasFrames | |||
return { expandedFrame } | |||
end | |||
-- Single frame alias, put in list | |||
if aliasFrames.name then | |||
aliasFrames = { aliasFrames } | |||
end | end | ||
local expandedFrames = {} | local expandedFrames = {} | ||
for aliasFrame in | for i, aliasFrame in ipairs( aliasFrames ) do | ||
local | local expandedFrame | ||
if type( aliasFrame ) == 'string' then | |||
expandedFrame = { name = aliasFrame } | |||
else | |||
expandedFrame = cloneTable( aliasFrame ) | |||
end | |||
expandedFrame.title = parentFrame.title or expandedFrame.title | |||
expandedFrame.mod = parentFrame.mod or expandedFrame.mod | |||
expandedFrame.num = parentFrame.num or expandedFrame.num | |||
expandedFrame.text = parentFrame.text or expandedFrame.text | |||
expandedFrames[i] = expandedFrame | |||
end | end | ||
return table.concat( | return expandedFrames | ||
end | |||
function p.expandAlias( parentFrame, alias ) | |||
return p.getAlias( alias, parentFrame ) | |||
end | |||
function p.stringifyFrame( frame ) | |||
if not frame.name then | |||
return '' | |||
end | |||
return string.format( | |||
'[%s]%s,%s[%s]', | |||
frame.title or '', | |||
frame.name, | |||
frame.num or '', | |||
frame.text or '' | |||
) | |||
end | |||
function p.stringifyFrames( frames ) | |||
for i, frame in ipairs( frames ) do | |||
frames[i] = p.stringifyFrame( frame ) | |||
end | |||
return table.concat( frames, ';' ) | |||
end | end | ||
function p. | -- Splits up the frame text into its parts | ||
function p.makeFrame( frameText, mod ) | |||
-- Simple frame with no parts | |||
if not frameText:match( '[%[:,]' ) then | |||
return { | |||
mod = mod, | |||
name = mw.text.trim( frameText ), | |||
} | |||
end | |||
frameText = frameText:gsub( '%s*([%[%]:,;])%s*', '%1' ) | |||
local frame = {} | |||
frame.title = frameText:match( '^%[([^%]]+)%]' ) | |||
frame.mod = frameText:match( '([^:%]]+):' ) or mod | |||
local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 } | local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 } | ||
if | if frame.mod and vanilla[mw.ustring.lower( frame.mod )] or frame.mod == '' then | ||
frame.mod = nil | |||
end | end | ||
local nameStart = ( | local nameStart = ( frameText:find( ':' ) or frameText:find( '%]' ) or 0 ) + 1 | ||
if nameStart - 1 == # | if nameStart - 1 == #frameText then | ||
nameStart = 1 | nameStart = 1 | ||
end | end | ||
frame.name = frameText:sub( nameStart, ( frameText:find( '[,%[]', nameStart ) or 0 ) - 1 ) | |||
frame.num = math.floor( frameText:match( ',(%d+)' ) or 0 ) | |||
if | if frame.num == 0 then | ||
frame.num = nil | |||
end | end | ||
frame.text = frameText:match( '%[([^%]]+)%]$' ) | |||
return | return frame | ||
end | |||
function p.getParts( frameText, mod ) | |||
return p.makeFrame( frameText, mod ) | |||
end | end | ||
return p | return p | ||