Module:LD

From Roovet Articles
Jump to navigation Jump to search

Documentation for this module may be created at Module:LD/doc

-- Module:LD — JSON-LD helpers (safe: no autolinking inside <script>)
-- Use via templates like {{LD-Album}} or {{LD-Article}}

local p = {}

local function argsOf(frame)
  local parent = frame:getParent()
  return parent and parent.args or frame.args
end

local function nz(s) return (s and s ~= "") and s or nil end
local function num(s) s = tonumber(s); return s end

local function listFrom(args, prefix, maxn)
  local t = {}
  for i = 1, maxn do
    local v = nz(args[prefix .. i])
    if v then t[#t+1] = v end
  end
  return (#t > 0) and t or nil
end

local function scriptTagFrom(obj)
  local json = mw.text.jsonEncode(obj)
  local safe = mw.text.nowiki(json)  -- prevent auto-linking/formatting
  return mw.getCurrentFrame():callParserFunction{
    name = "#tag",
    args = { "script", safe, type = "application/ld+json" }
  }
end

-- ===== MusicAlbum =====
function p.album(frame)
  local a = argsOf(frame)
  local title = mw.title.getCurrentTitle()

  local obj = {
    ["@context"]  = "https://schema.org",
    ["@type"]     = "MusicAlbum",
    name          = a.name or title.text,
    datePublished = nz(a.datePublished),
    genre         = nz(a.genre) or listFrom(a, "genre", 10),
    numTracks     = tonumber(a.numTracks),
    image         = nz(a.image),
    inLanguage    = nz(a.inLanguage),
    url           = nz(a.url) or title:fullUrl{ protocol = "https" }
  }

  -- byArtist
  local artistName = nz(a.artistName)
  local artistType = nz(a.artistType) or "MusicGroup"
  if artistName then
    obj.byArtist = { ["@type"] = artistType, name = artistName }
  end

  -- recordLabel
  local label = nz(a.recordLabel)
  if label then
    obj.recordLabel = { ["@type"] = "Organization", name = label }
  end

  -- sameAs (up to 10)
  local sameAs = listFrom(a, "sameAs", 10)
  if sameAs then obj.sameAs = sameAs end

  -- identifier
  if nz(a.identifier) then obj.identifier = a.identifier end

  return scriptTagFrom(obj)
end

-- ===== Article / WebPage =====
function p.article(frame)
  local a = argsOf(frame)
  local title = mw.title.getCurrentTitle()

  local url = nz(a.url) or title:fullUrl{ protocol = "https" }

  local function logoObj(urlStr, w, h)
    if not nz(urlStr) then return nil end
    local o = { ["@type"] = "ImageObject", url = urlStr }
    local W, H = num(w), num(h)
    o.width, o.height = W, H
    return o
  end

  -- Build images array without ever emitting []
  local images = {}
  if nz(a.image) then table.insert(images, a.image) end
  for i=1,5 do
    local v = nz(a["image"..i])
    if v then table.insert(images, v) end
  end
  if #images == 0 and nz(a.coverImage) then
    images = { a.coverImage }
  end
  -- If you want a sitewide fallback, uncomment next line:
  -- if #images == 0 then images = { "https://roovet.com/static/og/roovet-default.jpg" } end

  local obj = {
    ["@context"]       = "https://schema.org",
    ["@type"]          = nz(a.type) or "Article",
    name               = nz(a.name) or (title.text .. " - Roovet Articles"),
    headline           = nz(a.headline) or (title.text .. " - Roovet Articles"),
    mainEntityOfPage   = { ["@type"] = "WebPage", ["@id"] = url },
    identifier         = url,
    url                = url,
    datePublished      = nz(a.datePublished),
    dateModified       = nz(a.dateModified),
    author             = {
      ["@type"] = nz(a.authorType) or "Organization",
      name      = nz(a.authorName) or "Roovet Articles",
      url       = nz(a.authorUrl) or "https://roovet.com"
    },
    publisher          = {
      ["@type"] = "Organization",
      name       = nz(a.publisherName) or "Roovet Articles",
      url        = nz(a.publisherUrl) or "https://roovet.com",
      logo       = logoObj(nz(a.publisherLogo), a.publisherLogoWidth, a.publisherLogoHeight)
    },
    potentialAction    = nz(a.searchTarget) and {
      ["@type"]      = "SearchAction",
      target         = a.searchTarget,
      ["query-input"]= "required name=search_term"
    } or nil
  }

  if #images > 0 then obj.image = images end

  return scriptTagFrom(obj)
end

return p