Module:Main page

From The Museum of Human Achievement

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

local p = {}

-- ########### IS VALID URL ###########
local function isValidUrl(target)
	return type(target) == "string" and target:match('^https?://[%w%-%._~:/%?#%[%]@!$&\'()*+,;=]+$') ~= nil
end

-- ########### DATE FORMAT ###########

local function formatDate(timestamp, dateformat)
    local formattedDate = os.date(dateformat, timestamp)
    return formattedDate
end

-- ########### GET MONTH NAME ###########

local function getMonthName(monthNumber)
    local months = {
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    }
    return months[monthNumber] or "Invalid month"
end

-- ########### FORMAT HOUR ###########

local function formatAMPM(hour)
    -- Convert 24-hour format to 12-hour format with AM/PM
    local ampm = hour < 12 and "AM" or "PM"
    local hour12 = hour % 12
    if hour12 == 0 then
        hour12 = 12  -- Midnight or noon should be 12
    end
    return tostring(hour12), ampm
end

-- ########### CREATE TIMESTAMP TABLE ###########

local function getDateTime(timestamp)
    -- If no timestamp is provided, use the current time
    if not timestamp then
        timestamp = os.time()
    end
    
    -- Get the date components as a table
    local dateTable = os.date('*t', timestamp)
	return dateTable
end

-- ########### FORMAT TIMESTAMP ###########

local function formatTimestamp(timestampTable, format)

    -- Default format if none is provided
    if not format or format == '' then
        format = "%s %d, %d"  -- Default format: "Full Month Name Day, Year"
    end
    
    -- Extract the month, day, and year
    local month     = timestampTable.month
    local day       = timestampTable.day
    local year      = timestampTable.year
    local hours     = timestampTable.hour
    local minutes   = timestampTable.min
    local seconds   = timestampTable.sec
    local fullmonth = getMonthName(month)
    
    -- Convert hour to 12-hour format with AM/PM
    local hour12, ampm = formatAMPM(hours)
    
        -- Replace placeholders in the format string with actual values
    local formattedDate = format:gsub("%%month", string.format("%02d", month))
    							 :gsub("%%fullmonth", fullmonth)	
                                 :gsub("%%day", string.format("%02d", day))
                                 :gsub("%%year", tostring(year))
                                 :gsub("%%hour", tostring(hours))  -- 24-hour format
                                 :gsub("%%minute", string.format("%02d", minutes))  -- Minutes with leading zero
                                 :gsub("%%h12", hour12)  -- 12-hour format
                                 :gsub("%%ampm", ampm)  -- AM/PM
    
    return formattedDate
end

-- ########### CHECK EMPTY TABLE ###########

local function isTableEmpty(tbl)
    -- Check if the table has any numeric keys
    if next(tbl) == nil then
        return true  -- Table is empty
    end
    
    return false  -- Table is not empty
end

-- ########### BUTTON ###########

local function button(frame, value, action, class )
	local button = frame:callParserFunction('#widget',
    	'submit',
    	'value=' .. value,
    	'class=' .. class,
    	'action=' .. action
	)
	return button
end

-- ########### BUTTON LINK ###########

local function buttonlink(frame, value, action, class, width, buttonclass )
	local btnclass = buttonclass or ''
	local html = mw.html.create()
	local button = frame:preprocess(string.format('[%s <span class="%s">%s</span>]'
		, action
		, class
		, value
	))
    local autowidth
	if width then
		autowidth = width
	else
		autowidth = 'auto'
	end
	html:tag('div')
		:addClass('buttonlink moha-btn ' .. btnclass .. ' mt-4 mb-0 text-center w-' .. autowidth )
		:wikitext(button)
		
	return tostring(html)
end

-- ########### BANNER ###########

function p.banner(frame)

local params  = frame:getParent().args
local id      = params['id'] or 'banner'
local class   = params['class'] or 'banner'
local image   = params['image'] or ''
local height  = params['height'] or 'auto'
local bgcolor = params['background-color'] or '#A8DDBF'
local content = params['content'] or ''

if image ~= '' then 
	image = frame:preprocess('{{filepath:' .. image .. '}}')
end

local html = mw.html.create()

html:tag('div')
    :addClass(class .. ' mx-md-n5')
    :attr({
    	['id'] = id,
    	['data-bg'] = image
    })
    :css({
    	['background-color'] = bgcolor,
    	['height'] = height
    })
    :wikitext(content)
    :tag('div')
	    :addClass('overlap')
    	:attr({
    		['id'] = 'banner-tagline'
    	})
    	:css({
    		['background-color'] = 'var(--primary)'
    	})
        :wikitext(frame:preprocess('{{int:moha-fancy-tagline}}'))

return html

end

-- ########### VISION ###########

function p.vision(frame)

local params  = frame:getParent().args
local id      = params['id'] or 'vision'
local class   = params['class'] or 'vision'
local image   = params['image'] or ''
local bgcolor = params['background-color'] or '#A8DDBF'
local color   = params['color'] or 'var(--primary)'
local button1target = params['button1target'] or 'Main Page'
local button1text   = params['button1text'] or 'LEARN MORE'

local html = mw.html.create()

local vision = html:tag('div')
    :addClass(class .. ' flex-column flex-md-row')
    :attr({
    	['id'] = id
    })
    :css({
    	['background-color'] = bgcolor,
    	['color'] = color
    })
    if image ~= '' then 
    	vision:tag('div')
    		:addClass('image-wrapper')
    		:wikitext('[[File:' .. image .. '|link=]]')
    		:done()
    end
    local message = vision:tag('div')
    	:addClass('py-3 px-4 mx-3 px-md-5')
    	:wikitext(frame:preprocess('{{int:moha-fancy-vision}}'))
    	
    local target1 = frame:preprocess('{{fullurl:' .. button1target .. '}}')
    
    local button1 = buttonlink(frame, button1text, target1, '', 'auto', 'moha-btn-green' )
    
    message:tag('div')
    	:addClass('mt-4 mb-5 mb-lg-0')
    	:wikitext(button1)

return html

end

-- ########### ADVENTURE ###########

function p.adventure(frame)

local params  = frame.args
local id      = params['id'] or 'adventure'
local class   = params['class'] or 'adventure'
local image   = params['image'] or ''
local bgcolor = params['background-color'] or 'var(--primary)'
local color   = params['color'] or '#FFFFFF'

local button1target = params['button1target'] or 'Main Page'
local button1text   = params['button1text'] or 'LEARN ABOUT MoHA'
local button2target = params['button2target'] or 'Main Page'
local button2text   = params['button2text'] or 'ATTEND AN EVENT'
local button3target = params['button3target'] or 'Main Page'
local button3text   = params['button3text'] or 'SUPPORT MoHA WORK'
local button4target = params['button4target'] or 'Main Page'
local button4text   = params['button4text'] or 'GET INVOLVED'

local html = mw.html.create()

local vision = html:tag('div')
    :addClass(class)
    :attr({
    	['id'] = id
    })
    :css({
    	['background-color'] = bgcolor,
    	['color'] = color
    })
    	
    local message = vision:tag('div')
    	:addClass('col py-3 px-4 p-md-5 order-1 order-md-0')
    	:attr('id', 'adventure-cta')
    	:wikitext(frame:preprocess('{{int:moha-fancy-adventure}}'))
    
    local target1 = frame:preprocess('{{fullurl:' .. button1target .. '}}')
    local target2 = frame:preprocess('{{fullurl:' .. button2target .. '}}')
    local target3 = frame:preprocess('{{fullurl:' .. button3target .. '}}')
    local target4 = frame:preprocess('{{fullurl:' .. button4target .. '}}')
    
    local button1 = buttonlink(frame, button1text, target1, 'moha-btn-inverted' )
    
    local button2 = buttonlink(frame, button2text, target2, 'moha-btn-inverted' )
    
    local button3 = buttonlink(frame, button3text, target3, 'moha-btn-inverted' )
    
    local button4 = buttonlink(frame, button4text, target4, 'moha-btn-inverted' )
    
    message:tag('div')
    	:attr('id', 'button-block')
    	:addClass('text-center text-md-left d-flex flex-column')
    	:wikitext(button1)
    	:wikitext(button2)
    	:wikitext(button3)
    	:wikitext(button4)

    vision:tag('div')
    	:addClass('image-wrapper order-0 order-md-1 col')
    	:done()
    	
return html

end

-- ########### PROGRAMS ###########

function p.heading(frame)

local params = frame.args
local heading = params[1] or 'Section Heading'

html = mw.html.create()

html:tag('div')
	:addClass('moha-heading')
	:wikitext(heading)

return html

end

-- ########### SCROLLER CONTROLS ###########

function p.scroller(frame)

local params    = frame.args
local container = params[1] or '#programs-wall'
local goprev    = params[2] or 'left'
local gonext    = params[3] or 'right'

html = mw.html.create()

local scroller = html:tag('div')
	:addClass('moha-scroll ' .. container)
		
	if gonext ~= 'null' then
		scroller:tag('div')
			:addClass('moha-control')
			:attr('data-target', container)
			:wikitext('<i class="icono-arrow1-' .. gonext .. '"></i>')
			:done()
	end
	
	if goprev ~= 'null' then
		scroller:tag('div')
			:addClass('moha-control')
			:attr('data-target', container)
			:wikitext('<i class="icono-arrow1-' .. goprev .. '"></i>')
			:done()
	end
	
return html

end

-- ########### CARD ###########

function p.card(frame)

local params = frame:getParent().args
local image  = params['image'] or 'Moha.wiki logo.png'
local label  = params['label'] or 'MoHA'
local content= params['content'] or ''
local target = params['target'] or 'Main Page'
local buttontext = params['buttontext'] or 'LEARN MORE'
local class  = params['class'] or 'moha-card'

local imagepath = frame:preprocess('{{filepath:' .. image .. '}}')
target = frame:preprocess('{{fullurl:' .. target .. '}}')

html = mw.html.create()

local card = html:tag('div')
    :addClass('card ' .. class)
    
local cardimage = card:tag('div')
		:addClass('card-img-top')

		:tag('div')
			:addClass('image')
			:attr('data-bg', imagepath)
			
			:tag('div')
				:tag('div')
					:addClass('overlap-label')
					:wikitext(label)
		:done()
		
local cardbody = card:tag('div')
		:addClass('card-body')
		
		:tag('div')
			:addClass('card-text')
			:wikitext(content)
			:tag('div')
			:addClass('mt-auto')
			:wikitext(buttonlink(frame, buttontext, target, 'moha-btn-inverted' , '100'))
		:done()
		
return html

end

-- ########### GET PROGRAMS  ###########

function p.getprograms(frame)
	
	local data = mw.smw.ask {
		'[[Category:Current Programs]]',
		'mainlabel=-',
		'?#-=program',
		'?Program image#-=image',
		'?Program short description=description',
		'named args=yes',
		'limit=7',
		'order=random',
		'searchlabel='
	} or {}
	
	html = mw.html.create()
	
	for _, card in ipairs(data) do
		
		local program = card['program'] or 'MoHA'
		local image = card['image'] or 'Moha.wiki logo.png'
		local description = card['description'] or ''
		
		local target =  frame:preprocess('{{fullurl:' .. program .. '}}')
		local imagepath = frame:preprocess('{{filepath:{{PAGENAME:' .. image .. '}}}}')
		
		local card = html:tag('div')
    		:addClass('card moha-card')
    
		local cardimage = card:tag('div')
			:addClass('card-img-top')

			:tag('div')
				:addClass('image')
				:attr('data-bg', imagepath)
			
			:tag('div')
				:tag('div')
					:addClass('overlap-label')
					:wikitext(program)
		:done()
		
		local cardbody = card:tag('div')
			:addClass('card-body')
		
			:tag('div')
				:addClass('card-text')
				
					:tag('div')
						:wikitext(description)
						:done()
					
					:tag('div')
						:addClass('mt-auto')
						:wikitext(buttonlink(frame, 'LEARN MORE', target, 'moha-btn-inverted', '100'))
		:done()
		
	end
	
	return html
	
end

-- ########### MORE PROGRAMS ###########

function p.moreprograms(frame)
	
local params = frame:getParent().args
local id     = params['id'] or 'moreprograms'
local class  = params['class'] or 'border-0'
local title  = params['title'] or ''
local cta    = params['cta'] or ''
local target = params['target'] or 'Main Page'
local page   = frame:preprocess('{{fullurl:' .. target .. '}}')

local html = mw.html.create()

html:tag('div')
	:attr('id', id)
	:addClass(class)
	
		:tag('div')
			:addClass('h2')
			:wikitext(title)
		:done()
		
		:tag('div')
			:addClass('continue')
			:wikitext(string.format('[%s <span class="cta">%s</span><i class="icono-arrow1-left"></i>]'
				, page
				, cta
			))

return html

end

-- ########### COMMUNITY ###########

function p.community(frame)

local params  = frame.args
local id      = params['id'] or 'community'
local class   = params['class'] or 'community'
local image   = params['image'] or 'MoHA-Fancy-community.png'
local bgcolor = params['background-color'] or 'var(--primary)'
local color   = params['color'] or '#FFFFFF'
local title   = params['title'] or 'Card Title'
local content = params['content'] or 'Card content'
local buttontext = params['buttontext'] or 'Click Me'
local target  = params['target'] or 'Main Page'
local page    = frame:preprocess('{{fullurl:' .. target .. '}}')

image = frame:preprocess('{{filepath:' .. image .. '}}')

local html = mw.html.create()

local community = html:tag('div')
    :addClass(class)
    :attr({
    	['id'] = id
    })
    :css({
    	['background-color'] = bgcolor,
    	['color'] = color
    })

    local message = community:tag('div')
    	:addClass('w-100 w-md-50 py-3 order-1 order-md-0 card-body')
    	:attr('id', 'community-cta')
    		
    		:tag('div')
    			:addClass('h2')
    			:wikitext(title)
    			:done()
    		
    		:tag('div')
    			:addClass('my-3 my-md-5 card-text')
    			:wikitext(content)
    			:done()
    			
    	message:tag('div')
    		:addClass('text-center text-md-left d-flex flex-column')
    		:wikitext(buttonlink(frame, buttontext, page, 'moha-btn-secondary'))
            :done()
            
    community:tag('div')
    	:addClass('w-100 w-md-50 image-wrapper order-0 order-md-1')
    	:attr('data-bg', image)
    	:done()
    	
return html

end

-- ########### EVENTS ###########

function p.getevents(frame)

--	local params  = frame.args
    local today = formatTimestamp(getDateTime(), '%year-%month-%day' )
    local conditions = 
    '[[Category:Events]]' ..
    '[[Is public::true]]' ..
    '[[Date Start::>' .. today .. ']]' ..
    '[[Modification date::+]]' ..
    ' OR ' ..
    '[[Category:Events]]' ..
    '[[Is public::true]]' ..
    '[[Date Start::<' .. today .. ']]' ..
    '[[Date End::>>' .. today .. ']]' ..
    '[[Modification date::+]]'
    
	local data = mw.smw.ask {
		conditions,
		'mainlabel=-',
		'?#-=event',
		'?Event image#-=image',
		'?Event image caption=caption',
		'?Date Start#-F[U]=dstart',
		'?Date End#-F[U]=dend',
		'?Event format=format',
		'?Event location=location',
		'?Event admission type=admtype',
		'?Event admission price#-=price',
		'?Event admission price sliding low#=low',
		'?Event admission price sliding high#=high',
		'?Associated Program#-=program',
		'named args=yes',
		'limit=20',
		'order=random',
		'searchlabel='
	} or {}

	local html = mw.html.create()

	for _, card in ipairs(data) do
		
		local event = card['event'] or ''
		local image = card['image'] or 'Moha.wiki logo.png'
		local caption = card['caption'] or ''
		local program = card['program'] or ''
		local dstart = card['dstart'] or ''
		local dend = card['dend'] or ''
		local admtype = card['admtype'] or ''
		local location = card['location'] or ''
		local price = card['price'] or ''
		local low = card['low'] or ''
		local high = card['high'] or ''
		local programs = {}
		
		for item in string.gmatch(program, "([^;]+)") do
        	table.insert(programs, string.format('<div class="overlap-label">%s</div>', mw.text.trim(item))) -- Trim spaces for clean output
    	end
        
        local labels = ''
        if #programs > 0 then
        	labels = table.concat(programs, '')
        end
	
		local target =  frame:preprocess('{{fullurl:' .. event .. '}}')
		local imagepath = frame:preprocess('{{filepath:{{PAGENAME:' .. image .. '}}}}')
		
        local printdate, printdate_long, printdate_short, printtime_start, printtime_end, printtime, printmin_start, printmin_end, datesep, isCurrent, currentevent, pricerange, printprice

		-- Price formatting
		if low ~= '' and high ~= '' then
        	pricerange = low .. '-' .. high
        else
        	pricerange = low .. high
        end
        
        if admtype == "Free" or admtype == '' or price == '' or price == '0 USD' then
        	printprice = 'Free'
        else
        	printprice = (admtype == "Set Price") and '' or admtype .. ' '
        	printprice = printprice .. price .. pricerange
        end
        
        -- Timestamps formatting        
        local startdate = getDateTime(dstart) or ''
        local enddate   = getDateTime(dend) or ''
        
        if formatTimestamp(startdate, '%minute') == '00' then
        	printmin_start = ''
        else
            printmin_start = ':' .. formatTimestamp(startdate, '%minute')
        end
        
        if formatTimestamp(enddate, '%minute') == '00' then
        	printmin_end = ''
        else
            printmin_end = ':' .. formatTimestamp(enddate, '%minute')
        end
        
        printtime_start = formatTimestamp(startdate, '%h12' .. printmin_start .. '%ampm')
        printtime_end   = formatTimestamp(enddate, '%h12' .. printmin_end .. '%ampm')
        
        if printtime_end == printtime_start then
        	printtime = ''
        else
        	printtime = ' | ' .. printtime_start .. '-' .. printtime_end
        end

        if isTableEmpty(startdate) or isTableEmpty(enddate) then
        	datesep = ''
        else
        	datesep = '-'
        end
        
		printdate_long  = formatTimestamp(startdate, '%fullmonth %day, %year') .. printtime
        printdate_short = formatTimestamp(startdate, '%month.%day.%year') .. datesep .. formatTimestamp(enddate, '%month.%day.%year')
        printdate = (formatTimestamp(startdate,'%year%month%day') == formatTimestamp(enddate,'%year%month%day')) and printdate_long or printdate_short

		-- Formatted location (if present)
		if location ~= '' then
			location = string.format(' | %s', location)
		end
	
		-- Special class for the current event cards
		isCurrent = frame:callParserFunction('#ask', 
			'[[' .. event .. ']]' ..
			'[[Category:Events]]' ..
            '[[Is public::true]]' ..
            '[[Date Start::<' .. today .. ']]' ..
            '[[Date End::>>' .. today .. ']]' ..
            '[[Modification date::+]]'
		) or ''
        currentevent = (isCurrent ~= '')  and 'current' or ''
		local card = html:tag('div')
    		:addClass('card moha-card ' .. currentevent )
    
		local cardimage = card:tag('div')
			:addClass('card-img-top')

			:tag('div')
				:addClass('image')
				:attr('data-bg', imagepath)
			
			:tag('div')
				:wikitext(labels)

		local cardbody = card:tag('div')
			:addClass('card-body')

			:tag('div')
				:addClass('card-title')
				:wikitext(string.format('[[%s]]', event))
				:done()		
			
			:tag('div')
				:addClass('card-text')
				
				:tag('div')
					:addClass('datetime')
					:wikitext(printdate)
				:done()	
				
				:tag('div')
					:addClass('admission')
					:wikitext(printprice .. location)
				:done()	

			:tag('div')
				:addClass('mt-auto')
				:wikitext(buttonlink(frame, 'LEARN MORE', target, 'moha-btn-inverted', '100'))
				:done()
	end
return html

end

-- ########### BANNER ###########

function p.footerbanner(frame)

local params  = frame:getParent().args
local id      = params['id'] or 'footerbanner'
local class   = params['class'] or 'footerbanner'
local image   = params['image'] or ''
local height  = params['height'] or 'auto'
local bgcolor = params['background-color'] or '#A8DDBF'
local content = params['content'] or ''

if image ~= '' then 
	image = frame:preprocess('{{filepath:' .. image .. '}}')
end

local html = mw.html.create()

html:tag('div')
    :addClass(class .. ' mx-md-n5')
    :attr({
    	['id'] = id
    })
    :css({
    	['background-color'] = bgcolor,
    	['height'] = height
    })
    :wikitext(content)
    :tag('div')
	    :addClass('footeroverlap d-flex flex-column flex-lg-row ml-auto justify-content-between')
    	:attr({
    		['id'] = 'banner-bottom-tagline'
    	})
    	:css({
    		['background-color'] = 'var(--primary)'
    	})
    
    	:tag('div')
    		:addClass('bottom-tagline')
        	:wikitext(frame:preprocess('{{int:moha-fancy-bottom-tagline}}'))
			:done()
			
		:tag('div')
			:addClass('bottom-details')
        	:wikitext(frame:preprocess('{{int:moha-fancy-bottom-details}}'))
        	
return html

end

function p.horizontal(frame)
  
  local params = frame:getParent().args
  
  local title       = params['title'] or ''
  local content     = params['content'] or ''
  
  local image       = params['image'] or ''
  local bgcolor     = params['bgcolor'] or 'var(--moha-green)'
  local fgcolor     = params['fgcolor'] or 'var(--white)'
  local linkcolor   = params['linkcolor'] or 'var(--white)'
  
  local highlight   = params['highlight-text'] or ''
  local hlbgcolor   = params['highlight-bgcolor'] or 'var(--primary)'
  local hlfgcolor   = params['highlight-fgcolor'] or 'var(--white)'
  
  local target      = params['button-target'] or ''
  local label       = params['button-text'] or ''
  local btnfgcolor  = params['button-fgcolor'] or ''
  local button      = ''
  
  if target and target ~='' and isValidUrl(target) then
	button = string.format('[%s <span style="color:%s">%s</span>]'
		, target
		, btnfgcolor
		, label
    )
  end
  
  if target and target ~='' and not isValidUrl(target) then
	button = string.format('[[%s|<span style="color:%s">%s</span>]]'
		, target
		, btnfgcolor
		, label
    )
  end
  
  if target == '' and label ~= '' then
  	button = string.format('<span style="color:%s">%s</span>'
  		, btnfgcolor
  		, label
  	)
  end
  
  local filename, fileurl
  if image ~= '' then
  	filename = frame:preprocess('{{PAGENAME:' .. image .. '}}')
  	filenameDecoded = mw.text.decode(frame:preprocess('{{PAGENAME:' .. filename .. '}}'))
  	fileurl  = frame:preprocess('{{filepath:' .. filenameDecoded .. '}}')
  end
  
  -- Assemble
  local html = mw.html.create()
  
  local card = html:tag('div')
		:addClass('d-flex flex-column flex-lg-row moha-card-horizontal mb-4 mb-lg-0')
		:css({
			['background-color'] = bgcolor,
			['color'] = fgcolor,
		})
	
  local cardbody = card:tag('div')
		:addClass('d-flex flex-column p-5 moha-card-body order-1 order-lg-0 mt-lg-5')
		:css({
			['gap'] = '1rem 0'
		})
	
  local cardtitle = cardbody:tag('div')
		:addClass('h4 moha-card-title')
		:wikitext(title)
		
  local cardtext =  cardbody:tag('div')
		:addClass('moha-card-text')
		:wikitext(content)
		
  local cardfooter = cardbody:tag('div')
  		:addClass('d-flex flex-column flex-md-row moha-card-footer')
  
  if highlight ~= '' then		
		local cardhighlight = cardfooter:tag('div')
			:addClass('mb-3')
			:css('max-width', 'fit-content')
  
		local message = cardhighlight:tag('div')
			:addClass('moha-highlight')	
			:wikitext(highlight)
  end
  
  if button ~= '' then
		local cardbutton = cardfooter:tag('div')
			:addClass('moha-btn transparent border-white m-0 ml-auto my-lg-5')	
			:wikitext(button)
  end
  
  local cardimage = card:tag('div')
		:addClass('d-block moha-card-img order-0 order-lg-1')
		:attr('data-bg', fileurl)
		
  return html
  
end

return p