Ir para o conteúdo

Módulo:Lista de faixas

De Wiki TokuDrive
Revisão de 06h52min de 13 de setembro de 2026 por Tavoraadmin (discussão | contribs) (Importando predefinição/módulo da Wikipédia em português para manter layout)
(dif) ← Edição anterior | Revisão atual (dif) | Versão posterior → (dif)

local yesno = require('Module:Yesno') local checkType = require('libraryUtil').checkType local cfg = mw.loadData('Módulo:Lista de faixas/configuração')

local fieldMapping = { -- Título titulo = 'title', ['título'] = 'title', -- Nota nota = 'note', -- Créditos por faixa escritor = 'writer', letra = 'lyrics', musica = 'music', ['música'] = 'music', diretor = 'director', extra = 'extra', -- Duração duracao = 'length', ['duração'] = 'length', duracao_total = 'total_length', ['duração_total'] = 'total_length', -- Créditos de coluna (booleanos de nível de listagem) credito_letra = 'lyrics_credit', ['crédito_letra'] = 'lyrics_credit', credito_musica = 'music_credit', ['crédito_música'] = 'music_credit', credito_escrita = 'writing_credit', ['crédito_escrita'] = 'writing_credit', credito_direcao = 'direction_credit', ['crédito_direção'] = 'direction_credit', -- Coluna extra e linha vazia coluna_extra = 'extra_column', linha_vazia = 'blank_row', -- Larguras largura = 'width', largura_titulo = 'title_width', ['largura_título'] = 'title_width', largura_escrita = 'writing_width', largura_letra = 'lyrics_width', largura_musica = 'music_width', ['largura_música'] = 'music_width', largura_extra = 'extra_width', -- Cabeçalho e estado colapsável topo = 'headline', fechado = 'collapsed', -- Créditos globais ['toda_composição'] = 'all_writing', toda_composicao = 'all_writing', toda_letra = 'all_lyrics', ['toda_música'] = 'all_music', toda_musica = 'all_music', }


-- Funções auxiliares


local function addMixin(classe, mixin) for k, v in pairs(mixin) do if k ~= 'init' then classe[k] = v end end end


-- Mixin de validação


local Validacao = {}

function Validacao.init(self) self.avisos = {} self.categorias = {} end

function Validacao:adicionarAviso(msg, categoria) table.insert(self.avisos, msg) if categoria then table.insert(self.categorias, categoria) end end

function Validacao:adicionarCategoria(categoria) table.insert(self.categorias, categoria) end

function Validacao:obterAvisos() return self.avisos end

function Validacao:obterCategorias() return self.categorias end

function Validacao:validarDuracao(duracao) checkType('validarDuracao', 1, duracao, 'string', true) if duracao == nil then return nil end

local horas, minutos, segundos

-- Tenta "1:23:45" horas, minutos, segundos = duracao:match('^(%d+):(%d%d):(%d%d)$') if horas and horas:sub(1, 1) == '0' then self:adicionarAviso( string.format(cfg.leading_0_in_hours, mw.text.nowiki(duracao)), cfg.input_error_category ) return nil end

if not segundos then -- Tenta "1:23" minutos, segundos = duracao:match('^(%d?%d):(%d%d)$') if minutos and minutos:find('^0%d$') then self:adicionarAviso( string.format(cfg.leading_0_in_minutes, mw.text.nowiki(duracao)), cfg.input_error_category ) return nil end end

if not segundos then self:adicionarAviso( string.format(cfg.not_a_time, mw.text.nowiki(duracao)), cfg.input_error_category ) return nil end

if horas and tonumber(minutos) >= 60 then self:adicionarAviso( string.format(cfg.more_than_60_minutes, mw.text.nowiki(duracao)), cfg.input_error_category ) return nil end

if tonumber(segundos) >= 60 then self:adicionarAviso( string.format(cfg.more_than_60_seconds, mw.text.nowiki(duracao)), cfg.input_error_category ) end

return nil end


-- Classe Faixa


local Faixa = {} Faixa.__index = Faixa addMixin(Faixa, Validacao)

Faixa.campos = { number = true, title = true, note = true, writer = true, lyrics = true, music = true, director = true, extra = true, length = true, }

Faixa.metodosCelula = { number = 'makeCelulaNumero', title = 'makeCelulaTitulo', writer = 'makeCelulaEscritor', lyrics = 'makeCelulaLetra', music = 'makeCelulaMusica', director = 'makeCelulaDiretor', extra = 'makeCelulaExtra', length = 'makeCelulaDuracao', }

function Faixa.new(dados) local self = setmetatable({}, Faixa) Validacao.init(self) for campo in pairs(Faixa.campos) do self[campo] = dados[campo] end self.number = assert(tonumber(self.number)) self:validarDuracao(self.length) return self end

function Faixa:getCreditoLetra() return self.lyrics end

function Faixa:getCreditoMusica() return self.music end

function Faixa:getCreditoEscritor() return self.writer end

function Faixa:getCreditoDiretor() return self.director end

function Faixa:getCampoExtra() return self.extra end

function Faixa.makeCelulaSimples(wikitexto) return mw.html.create('td') :wikitext(wikitexto or cfg.blank_cell) end

function Faixa:makeCelulaNumero() return mw.html.create('th') :attr('id', string.format(cfg.track_id, self.number)) :attr('scope', 'row') :wikitext(string.format(cfg.number_terminated, self.number)) end

function Faixa:makeCelulaTitulo() local celula = mw.html.create('td') celula:wikitext( self.title and string.format(cfg.track_title, self.title) or cfg.untitled ) if self.note then celula:wikitext(string.format(cfg.note, self.note)) end return celula end

function Faixa:makeCelulaEscritor() return Faixa.makeCelulaSimples(self.writer) end

function Faixa:makeCelulaLetra() return Faixa.makeCelulaSimples(self.lyrics) end

function Faixa:makeCelulaMusica() return Faixa.makeCelulaSimples(self.music) end

function Faixa:makeCelulaDiretor() return Faixa.makeCelulaSimples(self.director) end

function Faixa:makeCelulaExtra() return Faixa.makeCelulaSimples(self.extra) end

function Faixa:makeCelulaDuracao() return mw.html.create('td') :addClass('tracklist-length') :wikitext(self.length or cfg.blank_cell) end

function Faixa:exportarLinha(colunas, ehPar) local linha = mw.html.create('tr') if ehPar then linha:addClass('tracklist-even') end for _, coluna in ipairs(colunas) do local metodo = Faixa.metodosCelula[coluna] if metodo then linha:node(self[metodo](self)) end end return linha end


-- Classe ListaDeFaixas


local ListaDeFaixas = {} ListaDeFaixas.__index = ListaDeFaixas addMixin(ListaDeFaixas, Validacao)

ListaDeFaixas.campos = { headline = true, collapsed = true, width = true, title_width = true, writing_width = true, lyrics_width = true, music_width = true, extra_width = true, extra_column = true, blank_row = true, total_length = true, all_writing = true, all_lyrics = true, all_music = true, lyrics_credit = true, music_credit = true, writing_credit = true, direction_credit = true, category = true, }

ListaDeFaixas.camposObsoletos = cfg.deprecated_track_listing_field_names or {}

function ListaDeFaixas.new(dados) local self = setmetatable({}, ListaDeFaixas) Validacao.init(self)

for campoObsoleto in pairs(ListaDeFaixas.camposObsoletos) do if dados[campoObsoleto] then self:adicionarCategoria(cfg.deprecated_parameter_category) break end end

if dados.total_length then self:validarDuracao(dados.total_length) end

for campo in pairs(ListaDeFaixas.campos) do self[campo] = dados[campo] end

self.exibirCategorias = yesno(self.category) ~= false self.category = nil

self.collapsed = ( self.collapsed == 'sim' or self.collapsed == 'yes' or yesno(self.collapsed) == true )

self.forcaEscritor = yesno(self.writing_credit) == true self.forcaLetra = yesno(self.lyrics_credit) == true self.forcaMusica = yesno(self.music_credit) == true self.forcaDiretor = yesno(self.direction_credit) == true

self.faixas = {} for _, dadosFaixa in ipairs(dados.faixas or {}) do table.insert(self.faixas, Faixa.new(dadosFaixa)) end

do local opt = {}

if self.forcaEscritor then opt.writer = true else if self.forcaLetra then opt.lyrics = true end if self.forcaMusica then opt.music = true end end if self.forcaDiretor then opt.director = true end

local verificar = { writer = not opt.writer, lyrics = not opt.lyrics, music = not opt.music, director = not opt.director, extra = true, }

for _, faixa in ipairs(self.faixas) do if verificar.writer and faixa:getCreditoEscritor() then opt.writer = true verificar.writer = false opt.lyrics = nil opt.music = nil verificar.lyrics = false verificar.music = false end if verificar.lyrics and faixa:getCreditoLetra() then opt.lyrics = true; verificar.lyrics = false end if verificar.music and faixa:getCreditoMusica() then opt.music = true; verificar.music = false end if verificar.director and faixa:getCreditoDiretor() then opt.director = true; verificar.director = false end if verificar.extra and faixa:getCampoExtra() then opt.extra = true; verificar.extra = false end

local continua = false for _, v in pairs(verificar) do if v then continua = true; break end end if not continua then break end end

self.colunasOpcionais = opt end

return self end

function ListaDeFaixas:fazerIntro() if self.all_writing then return string.format(cfg.tracks_written, self.all_writing) elseif self.all_lyrics and self.all_music then return mw.message.newRawMessage( cfg.lyrics_written_music_composed, self.all_lyrics, self.all_music ):plain() elseif self.all_lyrics then return string.format(cfg.lyrics_written, self.all_lyrics) elseif self.all_music then return string.format(cfg.music_composed, self.all_music) else return nil end end

function ListaDeFaixas:renderizarCategorias() if not self.exibirCategorias or mw.title.getCurrentTitle().namespace ~= 0 then return end

local ret = {} local function addCat(cat) table.insert(ret, string.format(, cat)) end

for _, cat in ipairs(self:obterCategorias()) do addCat(cat) end for _, faixa in ipairs(self.faixas) do for _, cat in ipairs(faixa:obterCategorias()) do addCat(cat) end end

return table.concat(ret) end

function ListaDeFaixas:renderizarAvisos() if not cfg.show_warnings then return end

local ret = {} local function addAviso(msg) table.insert(ret, string.format(cfg.track_listing_error, msg)) end

for _, aviso in ipairs(self:obterAvisos()) do addAviso(aviso) end for _, faixa in ipairs(self.faixas) do for _, aviso in ipairs(faixa:obterAvisos()) do addAviso(aviso) end end

return table.concat(ret, '
') end

function ListaDeFaixas:__tostring() local raiz = mw.html.create('div') :addClass('track-listing') if self.collapsed then raiz:css('border', 'var(--border-color-base, #aaa) 1px solid') raiz:css('padding', '3px') else raiz:css('padding', '4px') end

local intro = self:fazerIntro() if intro then raiz:tag('p'):wikitext(intro):done() end

local tabela = mw.html.create('table') tabela:addClass('tracklist') if self.collapsed then tabela:addClass('collapsible'):addClass('collapsed') tabela:css('width', self.width or '100%') else if self.width then tabela:css('width', self.width) end end

local colunas = {'number', 'title'}

if self.colunasOpcionais.writer then colunas[#colunas + 1] = 'writer' else if self.colunasOpcionais.lyrics then colunas[#colunas + 1] = 'lyrics' end if self.colunasOpcionais.music then colunas[#colunas + 1] = 'music' end end if self.colunasOpcionais.director then colunas[#colunas + 1] = 'director' end if self.colunasOpcionais.extra then colunas[#colunas + 1] = 'extra' end colunas[#colunas + 1] = 'length'

local nColunas = #colunas local nColunasOpcionais = nColunas - 3 local larguraTitulo

if nColunas >= 5 then larguraTitulo = 40 elseif nColunas >= 4 then larguraTitulo = 60 else larguraTitulo = 100 end

local larguraOpcional = nColunasOpcionais > 0 and (math.floor((100 - larguraTitulo) / nColunasOpcionais) .. '%') or '0%' larguraTitulo = larguraTitulo .. '%'

-- Linha de título/colapso (equivalente ao original)

-- Exibida quando há |topo= ou |fechado=sim; o mw-collapsible ancora o toggle

-- no desta linha, deixando o cabeçalho de colunas intacto.

if self.headline or self.collapsed then local linhaTopo = tabela:tag('tr') linhaTopo:tag('th') :addClass('tlheader mbox-text') :attr('colspan', nColunas) :attr('scope', 'col') :css('text-align', 'left') :css('background-color', 'var(--background-color-base, #fff)') :css ('color', 'var(--color-base, black)') :wikitext(self.headline or cfg.track_listing) linhaTopo:tag('td') :addClass('mbox-empty-cell') end

-- Linha de cabeçalho das colunas local linhaCabecalho = tabela:tag('tr')

linhaCabecalho:tag('th') :addClass('tracklist-number-header') :attr('scope', 'col') :tag('abbr') :attr('title', cfg.number) :wikitext(cfg.number_abbr)

linhaCabecalho:tag('th') :attr('scope', 'col') :css('width', self.title_width or larguraTitulo) :css('text-align', 'left') :wikitext(cfg.title)

local function adicionarCabecalhoOpcional(campo, textoCabecalho, largura) if self.colunasOpcionais[campo] then linhaCabecalho:tag('th') :attr('scope', 'col') :css('width', largura or larguraOpcional) :css('text-align', 'left') :wikitext(textoCabecalho) end end

adicionarCabecalhoOpcional('writer', cfg.writer, self.writing_width) adicionarCabecalhoOpcional('lyrics', cfg.lyrics, self.lyrics_width) adicionarCabecalhoOpcional('music', cfg.music, self.music_width) adicionarCabecalhoOpcional('director', cfg.director or 'Diretor(es)', nil) adicionarCabecalhoOpcional('extra', self.extra_column or cfg.extra, self.extra_width)

linhaCabecalho:tag('th') :addClass('tracklist-length-header') :attr('scope', 'col') :wikitext(cfg.length)

-- Célula vazia no cabeçalho de colunas para alinhar com o mbox-empty-cell do topo if self.headline or self.collapsed then linhaCabecalho:tag('td') :addClass('mbox-empty-cell') end

local separadorInserido = false for i, faixa in ipairs(self.faixas) do if not faixa.number then self:adicionarAviso( string.format(cfg.no_param_number), cfg.input_error_category ) end local blankRowNum = tonumber(self.blank_row)

if self.blank_row and not blankRowNum then self:adicionarAviso( string.format(cfg.no_param_number), cfg.input_error_category ) end

if blankRowNum and not separadorInserido and faixa.number >= blankRowNum then local linhaSep = tabela:tag('tr'):addClass('tracklist-separator') linhaSep:tag('td') :attr('colspan', nColunas) :css('height', '4px') :css('padding', '0') separadorInserido = true end

tabela:node(faixa:exportarLinha(colunas, i % 2 == 0)) end

if self.total_length then tabela:tag('tr') :addClass('tracklist-total-length') :tag('th') :attr('colspan', nColunas - 1) :attr('scope', 'row') :tag('span') :wikitext(cfg.total_length) :done() :done() :tag('td') :wikitext(self.total_length) end

raiz:node(tabela) raiz:wikitext(self:renderizarAvisos()) raiz:wikitext(self:renderizarCategorias())

return mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = { src = 'Módulo:Lista de faixas/styles.css' } } .. tostring(raiz) end


-- Exportações


local p = {}

function p._main(args) local dados = {} local faixas = {}

for k, v in pairs(args) do if type(k) == 'string' then

local prefixo, num = k:match('^(%D.-)(%d+)$')

local prefixoTraduzido = prefixo and (fieldMapping[prefixo] or prefixo)

if prefixoTraduzido and Faixa.campos[prefixoTraduzido] and (num == '0' or num:sub(1, 1) ~= '0') then num = tonumber(num) faixas[num] = faixas[num] or {} faixas[num][prefixoTraduzido] = v else dados[fieldMapping[k] or k] = v end end end

dados.faixas = (function(t) local ret = {} for num, dadosFaixa in pairs(t) do dadosFaixa.number = num table.insert(ret, dadosFaixa) end table.sort(ret, function(a, b) return a.number < b.number end) return ret end)(faixas)

return tostring(ListaDeFaixas.new(dados)) end

function p.main(frame) local args = require('Module:Arguments').getArgs(frame, { parentOnly = true, }) return p._main(args) end

return p