Module:SocialMedia
Appearance
Documentation for this module may be created at Module:SocialMedia/doc
-- Module:SocialMedia
-- Reusable social media component meant to be called from other modules
-- ###############################################################
-- Currently supported platforms are:
-- (UPDATE THE LIST BETWEEN THE HOOK EMOJIS AS PLATFORMS GET ADDED OR REMOVED)
-- đȘFacebook, Github, Instagram, LinkedIn, Telegram, Threads, TikTok, X/Twitter, YoutubeđȘ
-- ###############################################################
local sf = {}
local TemplateHelpers = require('Module:TemplateHelpers')
-- This section defines the social platforms; can be added or deleted without consequence
-- param = handle for the platform; for "x", we also accept "twitter" for legacy purposes
-- icon = wiki file for the icon
-- prefix = URL prefix
-- label = used for alt text/screen readers
local socialPlatforms = {
{
param = "facebook",
icon = "File:SocialFacebookIcon.svg",
prefix = "https://www.facebook.com/",
label = "Facebook"
},
{
param = "instagram",
icon = "File:SocialInstagramIcon.svg",
prefix = "https://www.instagram.com/",
label = "Instagram"
},
{
param = "linkedin",
icon = "File:SocialLinkedInIcon.svg",
prefix = "https://www.linkedin.com/in/",
label = "LinkedIn"
},
{
param = "telegram",
icon = "File:SocialTelegramIcon.svg",
prefix = "https://t.me/",
label = "Telegram"
},
{
param = "threads",
icon = "File:SocialThreadsIcon.svg",
prefix = "https://www.threads.net/@",
label = "Threads"
},
{
param = "tiktok",
icon = "File:SocialTikTokIcon.svg",
prefix = "https://www.tiktok.com/@",
label = "TikTok"
},
{
param = { "x", "twitter" },
icon = "File:SocialXIcon.svg",
prefix = "https://x.com/",
label = "X (Twitter)"
},
{
param = "youtube",
icon = "File:SocialYouTubeIcon.svg",
prefix = "https://www.youtube.com/@",
label = "YouTube"
},
{
param = "github",
icon = "File:SocialGithub.svg",
prefix = "https://github.com/",
label = "Github"
},
}
-- Helper function to check if text is a full URL
local function isFullUrl(text)
return text and text:match("^https?://") ~= nil
end
-- Platform-specific normalizers table
local normalizers = {
linkedin = function(handle)
-- If it's not a URL, return as is
if not isFullUrl(handle) then return handle end
-- Extract the path from the URL (case insensitive for linkedin.com)
local path = handle:lower():match("linkedin%.com/(.+)")
if not path then return handle end
-- Handle /pub/ format
if path:match("^pub/") then
-- Extract the name part (first segment after /pub/)
local name = path:match("^pub/([^/]+)")
if name then return name end
end
-- Handle /in/ format - just extract the username
if path:match("^in/") then
return path:match("^in/([^/]+)")
end
-- If we can't parse it, return the original
return handle
end
-- Add other platform normalizers as needed
}
-- Helper function to normalize handles based on platform
local function normalizeHandle(platform, handle)
-- Skip normalization if handle is nil or empty
if not handle or handle == "" then return handle end
-- Get the platform param (use first one if it's a table)
local platformParam = platform.param
if type(platformParam) == "table" then
platformParam = platformParam[1]
end
-- If platform has a specific normalizer, use it
if normalizers[platformParam] then
return normalizers[platformParam](handle)
end
-- Default behavior: if it's a full URL, try to extract the handle
if isFullUrl(handle) then
-- Try to extract the handle from the URL based on the platform's prefix
local prefix = platform.prefix:gsub("([%%%-%.])", "%%%1") -- Escape special pattern chars
return handle:match(prefix .. "(.+)") or handle
end
-- If not a URL, return as is
return handle
end
-- (Non-generalizable) Helper to find the first non-empty user handle from a param or table of params
local function getUserHandle(args, param)
local key, handle
if type(param) == "string" then
key, handle = TemplateHelpers.getFieldValue(args, { key = param })
else
key, handle = TemplateHelpers.getFieldValue(args, { keys = param })
end
if handle and handle ~= "" then
return mw.text.trim(handle)
end
return nil
end
-- Build a clickable icon link (24px) with alt text for accessibility: [[File:Name.svg|24px|alt=ALT_TEXT|link=URL]]
local function buildIconLink(iconFile, altText, url)
return string.format("[[%s|24px|alt=%s|link=%s]]", iconFile, altText, url)
end
-- Build a single table row with colspan=2, containing a flex container for icons
local function buildSocialRow(icons)
local iconMarkup = table.concat({
'<div class="social-icons external-social">',
table.concat(icons, ""),
'</div>'
}, "\n")
return table.concat({
"|-",
'| colspan="2" |',
iconMarkup
}, "\n")
end
-- Main render function, takes a table of arguments (handles), returns wiki markup with social icons
function sf.render(args)
-- Early return if args is empty or nil
if not args or next(args) == nil then
return ""
end
-- Pre-allocate icons table based on maximum possible size (number of platforms)
local icons = {}
icons[#socialPlatforms] = nil -- Pre-allocate table to avoid reallocation
local iconCount = 0
for _, platform in ipairs(socialPlatforms) do
local handle = getUserHandle(args, platform.param)
if handle and handle ~= "" then
-- Normalize the handle based on platform
local normalizedHandle = normalizeHandle(platform, handle)
-- Check for special FULLURL: prefix from Python transformation
local url
if handle:match("^FULLURL:") then
-- Extract the URL part after the FULLURL: prefix
url = handle:gsub("^FULLURL:", "")
print("Found special FULLURL prefix, using direct URL: " .. url)
-- If the handle is already a full URL, use it directly
elseif isFullUrl(handle) and not isFullUrl(normalizedHandle) then
-- We have a full URL that was successfully normalized to a handle
url = platform.prefix .. normalizedHandle
elseif isFullUrl(handle) then
-- We have a full URL that couldn't be normalized, use it directly
url = handle
else
-- We have a regular handle, use the standard approach
url = platform.prefix .. handle
end
local iconLink = buildIconLink(platform.icon, platform.label, url)
iconCount = iconCount + 1
icons[iconCount] = iconLink -- Direct index assignment is faster than table.insert
end
end
-- If no handles were provided, return nothing
if #icons == 0 then
return ""
end
-- Return a row with a flex container for icons
return buildSocialRow(icons)
end
return sf