Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Join the Playtest on Steam Now: SpiritVale

Module:Definitions: Difference between revisions

From SpiritVale Wiki
No edit summary
Tags: Mobile edit Mobile web edit
No edit summary
Tags: Mobile edit Mobile web edit
 
Line 9: Line 9:
-- - CSS namespace: ONLY "sv-def" (plus sv-def--* modifiers).
-- - CSS namespace: ONLY "sv-def" (plus sv-def--* modifiers).
-- - Output classes: sv-def, sv-def-icon, sv-def-icon-img, sv-def-text.
-- - Output classes: sv-def, sv-def-icon, sv-def-icon-img, sv-def-text.
-- - Tooltip/link behavior is handled by Universal Popups (Common.js).


local p = {}
local p = {}
Line 117: Line 118:
-- =============================================================================
-- =============================================================================
-- Icon rendering
-- Icon rendering
-- - Blank icon: render nothing (no generic dot/circle)
-- - Blank icon: render nothing
-- - Missing file: render "?" badge (no redlink image)
-- - Missing file: render "?" badge (no redlink image)
-- =============================================================================
-- =============================================================================
Line 175: Line 176:
local d_lc = lc(domain)
local d_lc = lc(domain)


-- Missing record: show a visible hint but NO tooltip/link data.
-- Missing record: visible hint, no tooltip/link attributes.
if rec == nil then
if rec == nil then
local miss_icon = noicon and "" or '<span class="sv-def-icon sv-def-icon--missing" aria-hidden="true">?</span>'
local miss_icon = noicon and "" or '<span class="sv-def-icon sv-def-icon--missing" aria-hidden="true">?</span>'
Line 194: Line 195:


local classes = 'sv-def sv-def--' .. enc_attr(d_lc)
local classes = 'sv-def sv-def--' .. enc_attr(d_lc)
if noicon then
if noicon then classes = classes .. ' sv-def--noicon' end
classes = classes .. ' sv-def--noicon'
if pill then classes = classes .. ' sv-def--pill' end
end
if fill then classes = classes .. ' sv-def--fill' end
if pill then
classes = classes .. ' sv-def--pill'
end
if fill then
classes = classes .. ' sv-def--fill'
end


local attrs = {
local attrs = {
Line 209: Line 204:
'data-sv-def-key="' .. enc_attr(key) .. '"',
'data-sv-def-key="' .. enc_attr(key) .. '"',
}
}
if pill then
 
attrs[#attrs + 1] = 'data-sv-def-pill="1"'
if pill then attrs[#attrs + 1] = 'data-sv-def-pill="1"' end
end
if fill then attrs[#attrs + 1] = 'data-sv-def-fill="1"' end
if fill then
attrs[#attrs + 1] = 'data-sv-def-fill="1"'
end


-- Only include tooltip/link attributes when populated.
-- Only include tooltip/link attributes when populated.
if defn ~= "" then
if defn ~= "" then
attrs[#attrs + 1] = 'data-sv-def-tip="' .. enc_attr(defn) .. '"'
attrs[#attrs + 1] = 'data-sv-def-tip="' .. enc_attr(defn) .. '"'
-- Interactive definitions are focusable for consistent UX.
attrs[#attrs + 1] = 'tabindex="0"'
end
end
if link ~= "" then
if link ~= "" then
attrs[#attrs + 1] = 'data-sv-def-link="' .. enc_attr(link) .. '"'
attrs[#attrs + 1] = 'data-sv-def-link="' .. enc_attr(link) .. '"'
end
end
local title_attr = (defn ~= "") and (' title="' .. enc_attr(defn) .. '"') or ""


local ico = noicon and "" or icon_html(icon)
local ico = noicon and "" or icon_html(icon)


return
return
'<span ' .. table.concat(attrs, " ") .. title_attr .. '>' ..
'<span ' .. table.concat(attrs, " ") .. '>' ..
ico ..
ico ..
'<span class="sv-def-text">' .. mw.text.nowiki(name) .. '</span>' ..
'<span class="sv-def-text">' .. mw.text.nowiki(name) .. '</span>' ..

Latest revision as of 06:04, 27 February 2026

Module:Definitions

This module implements the Definitions v1 system used by Template:Def. It resolves a (Domain, Key) pair against a JSON database and outputs a small inline term with optional tooltip text.

Purpose

  • Provide consistent, reusable definitions across the wiki.
  • Keep usage explicit to avoid collisions (no guessing).
  • Allow pages to show definitions without requiring navigation.

Usage

The public interface is the template Template:Def:

{{def|Domain|Key}}

Examples:

{{def|Stat|Vit}}
{{def|Element|Fire}}
{{def|Cast|Ground}}
{{def|Target|Summon}}

System components

1) Template:def

Page: Template:def

This template calls Module:Definitions and passes the provided parameters.

2) Module:Definitions

Page: Module:Definitions

Responsibilities:

  • Load the JSON database.
  • Resolve Domain and Key.
  • Output HTML with sv-def classes and data-sv-def-* attributes.

Tooltip rule:

  • If Definition is empty, no tooltip is shown.

Link rule:

  • If Link is non-empty, the term text is rendered as a real <a> (clickable).
  • If Link is empty, the term text is rendered as plain text (tooltip-only).

3) Definitions database (JSON)

Page: Module:Definitions/Definitions.json

This page is the Definitions v1 database.

Schema:

{
  "Schema": 1,
  "UpdatedAt": "YYYY-MM-DD",
  "Stat": {
    "Vit": { "Name": "Vitality", "Definition": "...", "Icon": "", "Link": "" }
  },
  "Element": {
    "Fire": { "Name": "Fire", "Definition": "", "Icon": "", "Link": "" }
  }
}

Rules:

  • Top-level keys are Domains (example: Stat, Element).
  • Each Domain contains Keys (example: Vit, Fire).
  • Keys are CamelCase and should match the intended enum key names.
  • Name and Definition are user-facing fields.
  • Icon and Link may be blank.

Supported v1 domains:

  • Cast
  • Damage
  • Element
  • Aura
  • Event
  • Stance
  • Stat
  • Target

Styling

CSS is provided by TemplateStyles:

  • Page: Template:def/styles.css
  • Namespace: sv-def only

JavaScript

Tooltip behavior is implemented in site JavaScript:

  • Page: MediaWiki:Common.js

The script reads:

  • data-sv-def-tip (tooltip text)
  • data-sv-def-link (reserved / optional)

Fallback behavior

  • Invalid arguments render an error placeholder.
  • Missing records render a missing placeholder.
  • If Name is empty, the module displays a humanized version of the Key.
  • If Definition is empty, tooltip behavior is disabled.
  • If an icon file is missing, the module displays ? instead of a redlink image.

-- Module:Definitions
-- Core resolver + renderer for {{def|Domain|Key}} (Definitions v1).
--
-- Data source (static):
--   Module:Definitions/Definitions.json
--
-- Notes:
-- - Domains + keys are discovered from JSON (no hardcoded domain lists).
-- - CSS namespace: ONLY "sv-def" (plus sv-def--* modifiers).
-- - Output classes: sv-def, sv-def-icon, sv-def-icon-img, sv-def-text.
-- - Tooltip/link behavior is handled by Universal Popups (Common.js).

local p = {}

local DATA_TITLE = "Module:Definitions/Definitions.json"

-- =============================================================================
-- Helpers
-- =============================================================================

local function trim(s)
	if type(s) ~= "string" then return "" end
	return mw.text.trim(s)
end

local function lc(s)
	return string.lower(tostring(s or ""))
end

local function truthy(v)
	v = lc(mw.text.trim(tostring(v or "")))
	return v == "1" or v == "true" or v == "yes" or v == "y"
end

local function enc_attr(s)
	return mw.text.encode(tostring(s or ""), '"<>&')
end

local function humanize(key)
	key = tostring(key or "")
	key = key:gsub("([a-z0-9])([A-Z])", "%1 %2")
	key = key:gsub("([A-Z]+)([A-Z][a-z])", "%1 %2")
	key = key:gsub("%s+", " ")
	return mw.text.trim(key)
end

local function find_ci(t, key)
	if type(t) ~= "table" then return nil end
	if t[key] ~= nil then return t[key] end
	local want = lc(key)
	for k, v in pairs(t) do
		if lc(k) == want then return v end
	end
	return nil
end

-- =============================================================================
-- DB load (cached)
-- =============================================================================

local DB = nil

local function load_db()
	if DB ~= nil then return DB end

	local title = mw.title.new(DATA_TITLE)
	if not title then
		DB = {}
		return DB
	end

	local raw = title:getContent()
	if type(raw) ~= "string" or raw == "" then
		DB = {}
		return DB
	end

	local ok, obj = pcall(mw.text.jsonDecode, raw)
	if not ok or type(obj) ~= "table" then
		DB = {}
		return DB
	end

	DB = obj
	return DB
end

-- =============================================================================
-- Domain + record resolution (dynamic)
-- =============================================================================

local function norm_domain(db, domain)
	domain = trim(domain)
	if domain == "" then return nil end

	-- Exact match first
	if type(db[domain]) == "table" then
		return domain
	end

	-- Case-insensitive match across top-level keys that are tables
	local want = lc(domain)
	for k, v in pairs(db) do
		if type(v) == "table" and lc(k) == want then
			return k
		end
	end

	return nil
end

local function get_record(db, domain, key)
	local dom = db[domain]
	if type(dom) ~= "table" then return nil end
	return find_ci(dom, key)
end

-- =============================================================================
-- Icon rendering
-- - Blank icon: render nothing
-- - Missing file: render "?" badge (no redlink image)
-- =============================================================================

local function icon_html(icon)
	icon = trim(icon)

	if icon == "" then
		return ""
	end

	local fileTitle = icon
	if not fileTitle:match("^[Ff]ile:") then
		fileTitle = "File:" .. fileTitle
	end

	local t = mw.title.new(fileTitle)
	if not t or not t.exists then
		return '<span class="sv-def-icon sv-def-icon--missing" aria-hidden="true">?</span>'
	end

	return '<span class="sv-def-icon-img">[[' .. fileTitle .. '|14px|link=]]</span>'
end

-- =============================================================================
-- Render
-- =============================================================================

local function render(domain, key, opts)
	opts = opts or {}
	local noicon = truthy(opts.noicon)
	local pill   = truthy(opts.pill)
	local fill   = truthy(opts.fill)

	local db = load_db()
	domain = norm_domain(db, domain)
	key = trim(key)

	if not domain or key == "" then
		return '<span class="sv-def sv-def--error">?</span>'
	end

	local rec = get_record(db, domain, key)

	local name, defn, icon, link = "", "", "", ""
	if type(rec) == "table" then
		name = trim(rec.Name)
		defn = trim(rec.Definition)
		icon = trim(rec.Icon)
		link = trim(rec.Link)
	end

	if name == "" then
		name = humanize(key)
	end

	local d_lc = lc(domain)

	-- Missing record: visible hint, no tooltip/link attributes.
	if rec == nil then
		local miss_icon = noicon and "" or '<span class="sv-def-icon sv-def-icon--missing" aria-hidden="true">?</span>'
		return
			'<span class="sv-def sv-def--missing' ..
				(pill and ' sv-def--pill' or '') ..
				(fill and ' sv-def--fill' or '') ..
				' sv-def--' .. enc_attr(d_lc) .. '"' ..
				' data-sv-def-domain="' .. enc_attr(domain) .. '"' ..
				' data-sv-def-key="' .. enc_attr(key) .. '"' ..
				(pill and ' data-sv-def-pill="1"' or '') ..
				(fill and ' data-sv-def-fill="1"' or '') ..
			'>' ..
				miss_icon ..
				'<span class="sv-def-text">' .. mw.text.nowiki(name) .. '</span>' ..
			'</span>'
	end

	local classes = 'sv-def sv-def--' .. enc_attr(d_lc)
	if noicon then classes = classes .. ' sv-def--noicon' end
	if pill then classes = classes .. ' sv-def--pill' end
	if fill then classes = classes .. ' sv-def--fill' end

	local attrs = {
		'class="' .. classes .. '"',
		'data-sv-def-domain="' .. enc_attr(domain) .. '"',
		'data-sv-def-key="' .. enc_attr(key) .. '"',
	}

	if pill then attrs[#attrs + 1] = 'data-sv-def-pill="1"' end
	if fill then attrs[#attrs + 1] = 'data-sv-def-fill="1"' end

	-- Only include tooltip/link attributes when populated.
	if defn ~= "" then
		attrs[#attrs + 1] = 'data-sv-def-tip="' .. enc_attr(defn) .. '"'
		-- Interactive definitions are focusable for consistent UX.
		attrs[#attrs + 1] = 'tabindex="0"'
	end
	if link ~= "" then
		attrs[#attrs + 1] = 'data-sv-def-link="' .. enc_attr(link) .. '"'
	end

	local ico = noicon and "" or icon_html(icon)

	return
		'<span ' .. table.concat(attrs, " ") .. '>' ..
			ico ..
			'<span class="sv-def-text">' .. mw.text.nowiki(name) .. '</span>' ..
		'</span>'
end

-- =============================================================================
-- Public API
-- =============================================================================

function p.def(frame)
	local a = frame.args or {}
	local domain = a[1] or a.Domain or a.domain
	local key    = a[2] or a.Key or a.key
	local noicon = a.noicon or a.NoIcon or a[3]
	local pill   = a.pill or a.Pill
	local fill   = a.fill or a.Fill
	return render(domain, key, { noicon = noicon, pill = pill, fill = fill })
end

function p.render(domain, key, opts)
	return render(domain, key, opts)
end

return p