Module:Recipe: Difference between revisions

No edit summary
No edit summary
 
(20 intermediate revisions by 2 users not shown)
Line 6: Line 6:
local param = {
local param = {
   sCharArg = 'character',
   sCharArg = 'character',
  sKitchArg = 'kitchenware',
   sTypeArg = 'type',
   sTypeArg = 'type',
   sSortArg = 'sort',
   sSortArg = 'sort',
   sSortDelim = ';', -- Needs to be pattern-safe for current usage
   sSortDelim = ';',
   sSortDelim2 = '¤',
   sSortDelim2 = '¤',
   sTimeArg = 'time',
   sTimeArg = 'time',
Line 16: Line 17:


p.param = param
p.param = param
--------------------------------------------------------------------------------
-- Utility
--------------------------------------------------------------------------------


local function padLeft(str, amt)
local function padLeft(str, amt)
Line 26: Line 31:
     sOut = sOut .. 's'
     sOut = sOut .. 's'
   end
   end
   return sOut
   return sOut
end
end


local function normalizeType(s)
local function normalizeText(s)
   return mw.ustring.lower(mw.text.trim(tostring(s or '')))
   return mw.ustring.lower(mw.text.trim(tostring(s or '')))
end
end
Line 37: Line 43:
end
end


local function addRecipe(category, sAmt, sName)
local function addUseCategory(category, name, quality, sort)
   local cardNode = Card._main({
if not name or tostring(name) == '' then
return
end
 
if quality and tostring(quality) ~= '' then
if sort and tostring(sort) ~= '' then
category:wikitext('[[Category:Uses ', quality, '-Quality ', name, '|', sort, ']]')
else
category:wikitext('[[Category:Uses ', quality, '-Quality ', name, ']]')
end
 
return
end
 
if sort and tostring(sort) ~= '' then
category:wikitext('[[Category:Uses ', name, '|', sort, ']]')
else
category:wikitext('[[Category:Uses ', name, ']]')
end
end
 
local function getCurrentPageName(args)
  return args[param.sPage] or args.page or mw.title.getCurrentTitle().text
end
 
local function getQualityClass(quality)
  if not quality or tostring(quality) == '' then
    return nil
  end
 
  return 'card-quality-' .. normalizeText(quality)
end
 
--------------------------------------------------------------------------------
-- Card helpers
--------------------------------------------------------------------------------
 
local function addRecipeCard(category, sAmt, sName, quality)
   local cardArgs = {
     name = sName,
     name = sName,
     text = sAmt,
     text = sAmt,
     show_caption = '1',
     show_caption = '1'
     mobile = 'bulleted'
  }
   })
 
  if quality then
     cardArgs.quality = quality
   end
 
  local cardNode = Card._main(cardArgs)


   category:wikitext(
   addUseCategory(
     '[[Category:Uses ',
     category,
     sName,
     sName,
     '|',
     quality,
     padLeft(tostring(sAmt or ''):gsub('%D', ''), 10),
     padLeft(tostring(sAmt or ''):gsub('%D', ''), 10)
    ']]'
   )
   )


Line 56: Line 104:
end
end


local function addSimpleCard(sName, sText)
local function addMiniCard(category, sName, sText, quality)
   return Card._main({
   local cardArgs = {
     name = sName,
     name = sName,
     text = sText,
     text = sText or '',
     show_caption = '1',
     show_caption = '1',
     mobile = 'bulleted'
     mini = '1'
  }
 
  if quality then
    cardArgs.quality = quality
  end
 
  local cardNode = Card._main(cardArgs)
 
  addUseCategory(category, sName)
 
  return cardNode
end
 
local function addYieldCard(pageTitle, sYield)
  return Card._main({
    name = pageTitle,
    text = sYield,
    show_caption = '1'
   })
   })
end
end
--------------------------------------------------------------------------------
-- Header rendering
--------------------------------------------------------------------------------


local function renderHeader(args, output, category)
local function renderHeader(args, output, category)
   local header = output:tag('div'):addClass('recipe_header')
   local header = output:tag('div'):addClass('recipe-header')
 
   local sType = args[param.sTypeArg]
   local sType = args[param.sTypeArg]


   header:tag('span'):addClass('recipe_header_main')
   header:tag('span'):addClass('recipe-header-main')
    -- Mobile Icon/Text, preserved from original
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sType .. ']]'):done()
    :tag('span'):addClass('mobileHide'):wikitext('<big>' .. sType .. '</big>'):done()
    -- Desktop Icon/Text, preserved from original
     :tag('span'):addClass('mobileHide'):wikitext('<big>' .. sType .. '</big>'):done()
    -- Text
    :tag('span'):addClass('recipe_header_text'):wikitext('[[' .. sType .. ']]'):done()


   category:wikitext('[[Category:' .. sType .. ']]')
   category:wikitext('[[Category:' .. sType .. ']]')
Line 160: Line 224:
   end
   end


   header:tag('span'):addClass('recipe_header_sub')
   header:tag('span'):addClass('recipe-header-sub')
     :tag('span'):addClass('mobileHide'):wikitext(' for '):done()
     :tag('span'):wikitext(' for '):done()
    :tag('span'):addClass('hidden'):wikitext(' for '):done()
     :tag('span'):addClass('recipe-header-text'):wikitext(sTime):done()
     :tag('span'):addClass('recipe_header_text'):wikitext(sTime):done()
end
end


Line 173: Line 236:
   local sCharName = args[param.sCharArg]
   local sCharName = args[param.sCharArg]


   header:tag('span'):addClass('recipe_header_sub')
   header:tag('span'):addClass('recipe-header-sub')
     :tag('span'):addClass('mobileHide'):wikitext(' with '):done()
    :tag('span'):wikitext('[[File:' .. sCharName .. ' Icon.png|24x24px|link=' .. sCharName .. ']]'):done()
     :tag('span'):addClass('hidden'):wikitext('[[File:' .. sCharName .. ' Icon.png|24x24px|link=' .. sCharName .. ']]'):done()
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sCharName .. ']]'):done()
     :tag('span'):addClass('recipe_header_text'):wikitext('[[' .. sCharName .. ']]'):done()
end
 
local function renderKitchenwareHeader(args, header)
  if type(args[param.sKitchArg]) ~= 'string' then
     return
  end
 
  local sKitchName = args[param.sKitchArg]
 
  header:tag('span'):addClass('recipe-header-sub')
    :tag('span'):wikitext('[[File:' .. sKitchName .. ' Icon.png|24x24px|link=' .. sKitchName .. ']]'):done()
     :tag('span'):addClass('recipe-header-text'):wikitext('[[' .. sKitchName .. ']]'):done()
end
end


local function renderYield(args, body)
--------------------------------------------------------------------------------
-- Standard recipe rendering
--------------------------------------------------------------------------------
 
local function isRestrictedStandardParam(sKey)
  for _, keyParam in pairs(param) do
    if keyParam == sKey then
      return true
    end
  end
 
  return false
end
 
local function renderStandardYield(args, body)
   if args[param.sYieldArg] == '0' then
   if args[param.sYieldArg] == '0' then
     return
     return
   end
   end


   local sYield = args[param.sYieldArg] or '1'
   local sYield = args[param.sYieldArg] or nil
   local pageTitle = args.page or mw.title.getCurrentTitle().text
   local pageTitle = getCurrentPageName(args)


   local yieldCard = Card._main({
   local yieldCard = addYieldCard(pageTitle, sYield)
    name = pageTitle,
    text = sYield,
    show_caption = '1',
  })


   body
   body
     :tag('div'):addClass('recipe_body_icon')
     :tag('div'):addClass('recipe-body-icon')
       :wikitext('➜'):done()
       :wikitext('➜'):done()
     :tag('div'):addClass('recipe_body_yield')
     :tag('div'):addClass('recipe-body-yield')
       :node(yieldCard)
       :node(yieldCard)
  -- Preserved from original, though unused.
  local itemText = require('Module:Item').main{
    [1] = pageTitle,
    [2] = '15',
    x = sYield
  }
end
end
--------------------------------------------------------------------------------
-- Standard, non-cooking recipe rendering
--------------------------------------------------------------------------------


local function renderStandardRecipe(args, frame, output, category)
local function renderStandardRecipe(args, frame, output, category)
Line 219: Line 292:
   renderCharacter(args, header)
   renderCharacter(args, header)


   local body = output:tag('div'):addClass('recipe_body')
   local body = output:tag('div'):addClass('recipe-body')


   local sSortDelim = param.sSortDelim
   local sSortDelim = param.sSortDelim


  -- Check if sSortDelim2 exists in sSortArg.
   if ((args[param.sSortArg] or ''):find(param.sSortDelim2) ~= nil) then
   if ((args[param.sSortArg] or ''):find(param.sSortDelim2) ~= nil) then
     sSortDelim = param.sSortDelim2
     sSortDelim = param.sSortDelim2
   end
   end


  -- Process Sort Parameter, split by delimiter.
   for sKey in string.gmatch(args[param.sSortArg] or '', '([^' .. sSortDelim .. ']+)') do
   for sKey in string.gmatch(args[param.sSortArg] or '', '([^' .. sSortDelim .. ']+)') do
     if type(args[sKey]) ~= 'nil' then
     if type(args[sKey]) ~= 'nil' then
       body:node(addRecipe(category, args[sKey], sKey))
       body:node(addRecipeCard(category, args[sKey], sKey))
       tArgsProcessed[sKey] = true
       tArgsProcessed[sKey] = true
     end
     end
   end
   end


  -- Process rest of unrestricted parameters.
   for sKey, sVal in pairs(args) do
   for sKey, sVal in pairs(args) do
    local restricted = false
     if tArgsProcessed[sKey] ~= true and not isRestrictedStandardParam(sKey) then
 
       body:node(addRecipeCard(category, sVal, sKey))
    for _, keyParam in pairs(param) do
      if keyParam == sKey then
        restricted = true
      end
    end
 
     if tArgsProcessed[sKey] ~= true and not restricted then
       body:node(addRecipe(category, sVal, sKey))
     end
     end
   end
   end


   renderYield(args, body)
   renderStandardYield(args, body)


   body:done()
   body:done()
Line 257: Line 319:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Cooking recipe rendering
-- Cooking parsing
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function getMaxCookingItemIndex(args)
  local maxIndex = 0
  for key, _ in pairs(args) do
    local index = tostring(key):match('^item_(%d+)')
    if index then
      index = tonumber(index)
      if index and index > maxIndex then
        maxIndex = index
      end
    end
  end
  return maxIndex
end


local function getCookingIngredient(args, index)
local function getCookingIngredient(args, index)
Line 265: Line 344:
   local ingredient = {
   local ingredient = {
     index = index,
     index = index,
     amount = args[prefix .. '_amount'] or '1',
     amount = args[prefix .. '_amount'] or nil,
     quality = args[prefix .. '_quality'],
     quality = args[prefix .. '_quality'],
     item = args[prefix],
     item = args[prefix],
Line 272: Line 351:
   }
   }


  -- Single type:
  -- |item_1_type = Fish
   if argExists(args, prefix .. '_type') then
   if argExists(args, prefix .. '_type') then
     table.insert(ingredient.types, args[prefix .. '_type'])
     table.insert(ingredient.types, args[prefix .. '_type'])
   end
   end


  -- Multiple types:
  -- |item_1_type_1 = Freshwater Fish
  -- |item_1_type_2 = Saltwater Fish
   local typeIndex = 1
   local typeIndex = 1


Line 288: Line 362:
   end
   end


  -- Multiple accepted specific items:
  -- |item_1_option_1 = Rice
  -- |item_1_option_2 = Wheat
   local optionIndex = 1
   local optionIndex = 1


Line 299: Line 370:


   local hasIngredient =
   local hasIngredient =
     ingredient.item ~= nil
     argExists(args, prefix)
     or #ingredient.types > 0
     or #ingredient.types > 0
     or #ingredient.options > 0
     or #ingredient.options > 0
Line 310: Line 381:
end
end


local function getKitchenware(args)
--------------------------------------------------------------------------------
  local kitchenware = {}
-- Cooking type split-card helpers
--------------------------------------------------------------------------------


   -- Single kitchenware:
local function makeTypeIcon(typeName)
  -- |kitchenware = Cooking Pot
   return '[[File:' .. typeName .. ' Icon.png|74x74px|link=' .. typeName .. ']]'
  if argExists(args, 'kitchenware') then
end
    table.insert(kitchenware, args.kitchenware)
  end


  -- Multiple kitchenware:
local function joinTypeCaption(types)
  -- |kitchenware_1 = Cooking Pot
   local out = {}
  -- |kitchenware_2 = Frying Pan
   local index = 1


   while argExists(args, 'kitchenware_' .. index) do
   for _, typeName in ipairs(types) do
     table.insert(kitchenware, args['kitchenware_' .. index])
     table.insert(out, '[[' .. typeName .. ']]')
    index = index + 1
   end
   end


   return kitchenware
   return table.concat(out, ' / ')
end
end


local function renderCookingTypeRequirement(category, typeBox, amountText, types)
local function renderCookingSplitTypeCard(category, body, ingredient)
   if #types == 1 then
   local qualityClass = getQualityClass(ingredient.quality or 'Gray')
     local typeName = types[1]
 
  local container = body:tag('span')
     :addClass('card-container')
    :addClass('recipe-type-split-card')


    typeBox:tag('div')
  local wrapper = container:tag('span')
      :addClass('recipe_body_cooking_requirement')
    :addClass('card-wrapper')
      :wikitext(amountText .. ' of [[:Category:' .. typeName .. '|' .. typeName .. ']]')


    category:wikitext('[[Category:Uses ', typeName, ']]')
  local cardBody = wrapper:tag('span')
    :addClass('card-body')


     return
  local imageContainer = cardBody:tag('span')
     :addClass('card-image-container')
    :addClass('recipe-type-split-image')
 
  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-a')
    :wikitext(makeTypeIcon(ingredient.types[1]))
    :done()
 
  imageContainer:tag('span')
    :addClass('recipe-type-split-half')
    :addClass('recipe-type-split-half-b')
    :wikitext(makeTypeIcon(ingredient.types[2]))
    :done()
   
if qualityClass then
    imageContainer:addClass(qualityClass)
end
 
  if tostring(ingredient.amount or '') ~= '' then
    container:tag('span')
      :addClass('card-label')
      :wikitext(tostring(ingredient.amount or '1'))
      :done()
  end
 
  container:tag('span')
    :addClass('card-caption')
    :wikitext(joinTypeCaption(ingredient.types))
    :done()
 
  for _, typeName in ipairs(ingredient.types) do
    addUseCategory(category, typeName)
   end
   end
end
--------------------------------------------------------------------------------
-- Cooking display
--------------------------------------------------------------------------------
local function renderCookingSpecificItem(category, body, ingredient)
  body:node(addRecipeCard(
    category,
    ingredient.amount,
    ingredient.item,
    ingredient.quality
  ))
end


   typeBox:tag('div')
local function renderCookingTypeFallback(category, body, ingredient)
     :addClass('recipe_body_cooking_requirement')
   local wrapper = body:tag('div')
     :wikitext(amountText .. ' of any of:')
     :addClass('recipe-body-cooking-ingredient')
     :addClass('recipe-body-cooking-type-cards')


   for _, typeName in ipairs(types) do
   for _, typeName in ipairs(ingredient.types) do
     typeBox:tag('div')
     wrapper:node(addMiniCard(
       :addClass('recipe_body_cooking_type')
       category,
       :wikitext('[[:Category:' .. typeName .. '|' .. typeName .. ']]')
       typeName,
      ingredient.amount,
      ingredient.quality
    ))
  end
end


     category:wikitext('[[Category:Uses ', typeName, ']]')
local function renderCookingTypeCards(category, body, ingredient)
  if #ingredient.types == 2 then
     renderCookingSplitTypeCard(category, body, ingredient)
  else
    renderCookingTypeFallback(category, body, ingredient)
   end
   end
end
end


local function renderCookingIngredient(category, body, ingredient)
local function renderCookingOptionCards(category, body, ingredient)
   local wrapper = body:tag('div')
   local wrapper = body:tag('div')
     :addClass('recipe_body_cooking_ingredient')
     :addClass('recipe-body-cooking-ingredient')
    :addClass('recipe-body-cooking-option-cards')


   local amountText = tostring(ingredient.amount or '1')
   for _, itemName in ipairs(ingredient.options) do
 
    wrapper:node(addMiniCard(
  if ingredient.quality then
      category,
    amountText = amountText .. ' ' .. ingredient.quality
      itemName,
      ingredient.amount,
      ingredient.quality
    ))
   end
   end
end


  -- Specific item:
local function renderCookingIngredient(category, body, ingredient)
  -- |item_1 = Rice
   if ingredient.item then
   if ingredient.item then
     wrapper:node(addRecipe(category, amountText, ingredient.item))
     renderCookingSpecificItem(category, body, ingredient)
     return
     return
   end
   end


  -- Multiple specific item options:
  -- |item_1_option_1 = Rice
  -- |item_1_option_2 = Wheat
   if #ingredient.options > 0 then
   if #ingredient.options > 0 then
     local optionBox = wrapper:tag('div')
     renderCookingOptionCards(category, body, ingredient)
      :addClass('recipe_body_cooking_options')
 
    optionBox:tag('div')
      :addClass('recipe_body_cooking_requirement')
      :wikitext(amountText .. ' of any of:')
 
    for _, itemName in ipairs(ingredient.options) do
      optionBox:node(addRecipe(category, '', itemName))
    end
 
     return
     return
   end
   end


  -- Type-based ingredient:
  -- |item_1_type = Fish
  -- or:
  -- |item_1_type_1 = Freshwater Fish
  -- |item_1_type_2 = Saltwater Fish
   if #ingredient.types > 0 then
   if #ingredient.types > 0 then
     local typeBox = wrapper:tag('div')
     renderCookingTypeCards(category, body, ingredient)
      :addClass('recipe_body_cooking_types')
 
    renderCookingTypeRequirement(category, typeBox, amountText, ingredient.types)
 
     return
     return
   end
   end
end
end


local function renderKitchenware(category, body, args)
local function renderCookingYield(args, category, body)
   local kitchenware = getKitchenware(args)
   if args[param.sYieldArg] == '0' then
 
  if #kitchenware == 0 then
     return
     return
   end
   end


   local wrapper = body:tag('div')
   local sYield = args[param.sYieldArg] or nil
    :addClass('recipe_body_kitchenware')
  local pageTitle = getCurrentPageName(args)


   wrapper:tag('div')
   local yieldCard = addYieldCard(pageTitle, sYield)
    :addClass('recipe_body_kitchenware_label')
    :wikitext('Kitchenware')


   for _, itemName in ipairs(kitchenware) do
   body
    wrapper:node(addSimpleCard(itemName, ''))
    :tag('div'):addClass('recipe-body-icon')
     category:wikitext('[[Category:Uses ', itemName, ']]')
      :wikitext('➜'):done()
  end
     :tag('div'):addClass('recipe-body-yield')
      :node(yieldCard)
end
end


Line 433: Line 539:
   renderTime(args, header)
   renderTime(args, header)
   renderCharacter(args, header)
   renderCharacter(args, header)
  renderKitchenwareHeader(args, header)


   local body = output:tag('div')
   local body = output:tag('div')
     :addClass('recipe_body')
     :addClass('recipe-body')
     :addClass('recipe_body_cooking')
     :addClass('recipe-body-cooking')


  local ingredientIndex = 1
   local foundIngredient = false
   local foundIngredient = false
  local maxIndex = getMaxCookingItemIndex(args)


   while true do
   for index = 1, maxIndex do
     local ingredient = getCookingIngredient(args, ingredientIndex)
     local ingredient = getCookingIngredient(args, index)


     if not ingredient then
     if ingredient then
       break
       foundIngredient = true
      renderCookingIngredient(category, body, ingredient)
     end
     end
    foundIngredient = true
    renderCookingIngredient(category, body, ingredient)
    ingredientIndex = ingredientIndex + 1
   end
   end


Line 460: Line 563:
   end
   end


   renderKitchenware(category, body, args)
   renderCookingYield(args, category, body)
 
  renderYield(args, body)


   body:done()
   body:done()
Line 482: Line 583:
function p._main(args, frame)
function p._main(args, frame)
   local output = mw.html.create('div')
   local output = mw.html.create('div')
     :addClass('recipe_container')
     :addClass('recipe-container')


  -- Important: category must be local to this render.
  -- Do not keep it as a module-level variable.
   local category = mw.html.create()
   local category = mw.html.create()


Line 495: Line 594:
     local sType = args[param.sTypeArg]
     local sType = args[param.sTypeArg]


     if normalizeType(sType) == 'cooking' then
     if normalizeText(sType) == 'cooking' then
       renderCookingRecipe(args, frame, output, category)
       renderCookingRecipe(args, frame, output, category)
     else
     else