Shko te përmbajtja

Moduli:WindowSections

Page protected
Nga Wikipedia, enciklopedia e lirë

local p = {}
local mw_html = mw.html
local mw_text = mw.text
local mw_uri = mw.uri

-- The reading of the lists (their labeled section «dritare», the requests they transclude, the
-- closed threads) and the votes of the requests (tally, deadline, rules) are in Module:Votes, which
-- the hubs use too ({{Vote summary}}).
local Votes = require('Module:Votes')
local heading_of = Votes.heading_of
local items_of = Votes.items_of

-- [[ CONFIGURATION ]]
-- Maps each named window to its display title, wiki page sources, fallback empty message,
-- and content type ("standard" for discussion/voting panels, "news" for bulletin-style panels).
-- A source with a «rule» (one of the RULES of Module:Votes) is a list of votes: each of its items
-- gets a line with the tally of its votes and its state (the time left, whether it can be closed);
-- with the rule «diskutim» (Pyetje e Përgjigje, Zyra e Ankesave), each thread gets a line under its
-- title with the date of its last comment and when MajavahBot archives it.
-- A window with a «count_class» (the article of the week, whose items are cards, not headings) takes the
-- number for its badge from the elements of its content with that class, and the tooltip of its header from
-- «count_note»; with none, the window is grey and says «Gjithçka në rregull!» above its content, with
-- «done_note» as its tooltip.
-- Every window has a tooltip on its header, by one standard (tooltip_of): what is open, by part in a
-- window with several sources («Kandidatura: 4 · Rivlerësime: 10»; the «part» of a source names its part,
-- else its «label» does), else «open_note» with the number of its items («Pyetje të hapura: 2»), and the
-- requests closed before the conditions of their vote were met, if any («Mbyllur para kohe: 1», «Mbyllur
-- pa votat e nevojshme: 2»); then,
-- after a dash, what the header does not show, the states of the items: of the votes, how many are ready
-- to be closed, wait for votes or are before their minimum time; of the threads, how many are about to be
-- archived. A window with nothing to do has «idle_note» («Nuk ka pyetje të hapura»).
local WINDOW_CONFIG = {
	['Pyetje e Përgjigje'] = {
		title = "Pyetje e Përgjigje",
		sources = { { page = "Ndihmë:Pyetje e Përgjigje", section = "dritare", rule = "diskutim" } },
		empty_msg = "Gjithçka në rregull!",
		open_note = "Pyetje të hapura: %d",
		idle_note = "Nuk ka pyetje të hapura",
		type = "standard"
	},
	['Zyra e Ankesave'] = {
		title = "Zyra e Ankesave",
		sources = { { page = "Wikipedia:Zyra e Ankesave", section = "dritare", rule = "diskutim" } },
		empty_msg = "Gjithçka në rregull!",
		open_note = "Ankesa të hapura: %d",
		idle_note = "Nuk ka ankesa të hapura",
		type = "standard"
	},
	['Administrata'] = {
		title = "Administrata",
		sources = {
			{ page = "Wikipedia:Administrata/Votimet/Kërkesa për kontrollues/Kërkesa", section = "dritare", label = "Kërkesa për kontrollues", part = "Kontrollues" },
			{ page = "Wikipedia:Administrata/Votimet/Kandidatura për administratorë/Kandidatura të propozuara", section = "dritare", label = "Kandidatura për administratorë (Të propozuara)", part = "Administratorë", rule = "zgjedhje" },
			{ page = "Wikipedia:Administrata/Votimet/Kandidatura për administratorë/Kandidatura vetjake", section = "dritare", label = "Kandidatura për administratorë (Vetjake)", part = "Administratorë", rule = "zgjedhje" },
			{ page = "Wikipedia:Administrata/Votimet/Kandidatura për burokratë/Kandidatura të propozuara", section = "dritare", label = "Kandidatura për burokratë (Të propozuara)", part = "Burokratë", rule = "zgjedhje" },
			{ page = "Wikipedia:Administrata/Votimet/Kandidatura për burokratë/Kandidatura vetjake", section = "dritare", label = "Kandidatura për burokratë (Vetjake)", part = "Burokratë", rule = "zgjedhje" },
			{ page = "Wikipedia:Administrata/Votimet/Dorëheqje/Dorëheqje të propozuara", section = "dritare", label = "Dorëheqje të propozuara", part = "Dorëheqje", rule = "zgjedhje" },
			{ page = "Wikipedia:Administrata/Votimet/Dorëheqje/Dorëheqje vullnetare", section = "dritare", label = "Dorëheqje vullnetare", part = "Dorëheqje" },
		},
		empty_msg = "Gjithçka në rregull!",
		idle_note = "Nuk ka votime ose kërkesa të hapura",
		type = "standard"
	},
	['Ofiçina'] = {
		title = "Ofiçina",
		sources = { { page = "Wikipedia:Robotët/Kërkesë për miratim", section = "dritare", rule = "votim" } },
		empty_msg = "Gjithçka në rregull!",
		open_note = "Kërkesa për miratim të hapura: %d",
		idle_note = "Nuk ka kërkesa për miratim të hapura",
		type = "standard"
	},
	['Forumi i Grisjes'] = {
		title = "Forumi i Grisjes",
		sources = { { page = "Wikipedia:Forumi i Grisjes/Kërkesë për grisje", section = "dritare", rule = "grisje" } },
		empty_msg = "Gjithçka në rregull!",
		open_note = "Kërkesa për grisje të hapura: %d",
		idle_note = "Nuk ka kërkesa për grisje të hapura",
		type = "standard"
	},
	['Artikuj të mirë'] = {
		title = "Artikuj të mirë",
		sources = {
			{ page = "Wikipedia:Artikuj të mirë/Kandidatura/Votimet", section = "dritare", label = "Kandidatura", rule = "kandidature" },
			{ page = "Wikipedia:Artikuj të mirë/Rivlerësime/Votimet", section = "dritare", label = "Rivlerësime", rule = "rivleresim" }
		},
		empty_msg = "Gjithçka në rregull!",
		idle_note = "Nuk ka kandidatura ose rivlerësime të hapura",
		type = "standard"
	},
	['Artikuj të përkryer'] = {
		title = "Artikuj të përkryer",
		sources = {
			{ page = "Wikipedia:Artikuj të përkryer/Kandidatura/Votimet", section = "dritare", label = "Kandidatura", rule = "kandidature" },
			{ page = "Wikipedia:Artikuj të përkryer/Rivlerësime/Votimet", section = "dritare", label = "Rivlerësime", rule = "rivleresim" }
		},
		empty_msg = "Gjithçka në rregull!",
		idle_note = "Nuk ka kandidatura ose rivlerësime të hapura",
		type = "standard"
	},
	['Artikulli i javës'] = {
		title = "Artikulli i javës",
		sources = { { page = "Wikipedia:Artikulli i javës", section = "dritare" } },
		empty_msg = "Gjithçka në rregull!",
		type = "standard",
		-- the badge: the weeks without a chosen article (the amber cards)
		count_class = "window-aj-auto",
		count_note = "Javë pa artikull të zgjedhur: %d nga 4",
		done_note = "Artikulli është zgjedhur për të katër javët"
	},
	['Buletini Teknik'] = {
		title = "Buletini Teknik",
		sources = { { page = "Wikipedia:Buletini Teknik", section = "dritare" } },
		empty_msg = "Lajme të reja së shpejti!",
		open_note = "Numra të rinj të buletinit: %d",
		idle_note = "Nuk ka numra të rinj të buletinit",
		type = "news"
	}
}

-- Threads closed with one of the closing templates of Module:Votes ({{Resolved}}, {{Refused}},
-- {{dmb}} and their other names) are left out of the windows, which show only what still needs
-- attention; the threads stay on their page until they are archived. A request closed before the
-- conditions of its vote were met still needs attention: it stays in its window until it is archived,
-- with its closing and its status in amber («⚠ Mbyllur para kohe»), and the tooltip of the window
-- counts it apart from the open ones (Module:Votes, needing_attention).

-- [[ HELPER FUNCTIONS ]]

-- Collapses multiple blank lines immediately before or after a heading div
-- into a single newline, preventing the MediaWiki parser from emitting
-- phantom paragraph tags around empty subsections.
local function strip_blank_lines_between_headings(text)
	text = mw.ustring.gsub(text, '(
)%s*\n%s*\n+', '%1\n') text = mw.ustring.gsub(text, '\n%s*\n+%s*(
, '\n%1') return text end -- Fetches and merges wikitext content from all sources defined for a window. -- Transforms wikitext section headings (==...==) into styled HTML div elements, -- each carrying a permalink icon linking back to the originating page and anchor. -- Spaces immediately before wikilink brackets in heading text are encoded as -- so the MediaWiki parser preserves them as entity spans rather than -- collapsing them during HTML normalisation. -- Anchor deduplication is tracked at the card level so that identically-titled -- headings across different sources within the same window receive unique IDs. -- The heading regex validates that opening and closing marker lengths match, -- rejecting malformed headings such as ==Title===. -- Threads closed with one of the closing templates are left out, except -- the requests closed before the conditions of their vote were met. -- Returns the assembled wikitext string, the total count of top-level -- (H2) headings found across all sources, for windows with several -- sources the name and the count of the open items of each part, in their -- order (also of the empty ones), and the states of the items, for the -- tooltip of the window: whether it has votes with conditions (votes) or -- threads that a robot archives (threads), and how many items are in each -- state (ready, wait, time; early, for the requests closed before their -- conditions were met, and of those early_time, before their minimum time, -- and early_votes, without the votes they need; of the threads, due). local function get_content(frame, sources) local full_content = "" local h2_count = 0 local parts, part_index = {}, {} local states = {} local anchor_counts = {} local now = os.time() for _, src in ipairs(sources) do local rule = src.rule and Votes.RULES[src.rule] local text, early = Votes.needing_attention(frame, Votes.inline_transclusions(Votes.section_text(src.page, src.section), 1), rule) local count_before = h2_count local early_count = 0 if rule and (rule.min or rule.min_per) then states.votes = true elseif rule and rule.archive then states.threads = true end if text and mw_text.trim(text) ~= "" then local src_page = src.page -- The line of votes and deadline of each item of a vote local metas = {} if rule then for i, item in ipairs(items_of(mw_text.trim(text))) do local html, _, kind = Votes.meta(item, rule, now, early[item[1]]) metas[i] = html if early[item[1]] then -- a request closed before the conditions of its vote were met: before its minimum time -- («time») or, after it, without the votes it needs («votes») early_count = early_count + 1 local how = 'early_' .. early[item[1]].kind states[how] = (states[how] or 0) + 1 elseif kind and (not rule.archive or kind == 'due') then -- of the threads, the tooltip counts only those about to be archived states[kind] = (states[kind] or 0) + 1 end end -- the time left changes by the hour: the page is parsed again every hour frame:preprocess('{{#timel:H}}') end local lines = {} local item, placed = 0, false for line in mw_text.gsplit(mw_text.trim(text), '\n', true) do local level, clean_title = heading_of(line) if level then if level == 2 then h2_count = h2_count + 1 item = item + 1 placed = false end local base_anchor = mw_uri.anchorEncode(clean_title) local final_anchor = base_anchor if anchor_counts[base_anchor] then anchor_counts[base_anchor] = anchor_counts[base_anchor] + 1 final_anchor = base_anchor .. "_" .. anchor_counts[base_anchor] else anchor_counts[base_anchor] = 1 end local display_title = mw.ustring.gsub(clean_title, "%s+(%[%[)", " %1") local link_html = string.format('[[%s#%s|🔗]]', src_page, final_anchor) line = string.format('
%s%s
'
, level, display_title, link_html)
-- the line of votes goes at the top of the item's «Votimi», under its title, as on the hubs if level >= 3 and not placed and metas[item] and mw.ustring.find(mw.ustring.lower(clean_title), '^votim') then line = line .. '\n' .. metas[item] placed = true end -- the line of a thread of a discussion page (its last comment, when it is archived) goes -- under its title if level == 2 and rule and rule.archive and metas[item] then line = line .. '\n' .. metas[item] placed = true end end table.insert(lines, line) end text = table.concat(lines, '\n') text = strip_blank_lines_between_headings(text) -- expand the templates of the section (votes, signatures…) now that its headings are divs text = frame:preprocess(text) -- When a window aggregates multiple sources, prepend a labeled sub-header -- above each source's content block so readers can tell the parts apart: -- the name of the part, the number of its items and a link to its page. local source_header = "" if #sources > 1 then local display_label = src.label or src_page local count = h2_count - count_before source_header = string.format( '
%s%s[[%s|🔗]]
'
,
display_label, count > 0 and string.format('%d', count) or '', src_page ) end full_content = full_content .. source_header .. text end -- the parts of a window with several sources: sources with the same «part» count together if #sources > 1 then local name = src.part or src.label or src.page if not part_index[name] then table.insert(parts, { name = name, count = 0 }) part_index[name] = #parts end parts[part_index[name]].count = parts[part_index[name]].count + (h2_count - count_before - early_count) end if early_count > 0 then states.early = (states.early or 0) + early_count end end return full_content, h2_count, parts, states end -- The tooltip of the header of a window with something open, by one standard for all the windows: what is -- open, by part in a window with several («Kandidatura: 2 · Rivlerësime: 10»), else the words of the window -- with the number of its items («Kërkesa për grisje të hapura: 29»), and the requests closed before the -- conditions of their vote were met, which the window still shows, by what was not met («Mbyllur para -- kohe: 1», «Mbyllur pa votat e nevojshme: 2»): together, the number of the badge; then, after a dash, -- the states of the open items, which the header does not show: of the votes, how many are ready to be -- closed, wait for votes or are before their minimum time; of the threads, how many are about to be -- archived. local VOTE_STATES = { { 'ready', 'Gati për mbyllje' }, { 'wait', 'Në pritje të votave' }, { 'time', 'Para kohës minimale' }, } local EARLY_NOTES = { { 'early_time', 'Mbyllur para kohe' }, { 'early_votes', 'Mbyllur pa votat e nevojshme' }, } local function tooltip_of(config_data, h2_count, parts, states) local what states = states or {} local early = states.early or 0 if parts and #parts > 0 then local names = {} for _, part in ipairs(parts) do table.insert(names, part.name .. ': ' .. part.count) end what = table.concat(names, ' · ') elseif config_data.open_note then what = config_data.open_note:format(h2_count - early) end if not what then return nil end for _, note in ipairs(EARLY_NOTES) do if (states[note[1]] or 0) > 0 then what = what .. ' · ' .. note[2] .. ': ' .. states[note[1]] end end local notes = {} -- the states of the votes, when at least one of the open items is a vote if states.votes and (states.ready or states.wait or states.time) then for _, state in ipairs(VOTE_STATES) do table.insert(notes, state[2] .. ': ' .. (states[state[1]] or 0)) end end if states.threads then table.insert(notes, 'Arkivohen së shpejti: ' .. (states.due or 0)) end return what .. (#notes > 0 and (' — ' .. table.concat(notes, ' · ')) or '') end -- Constructs a collapsible card element for a single window panel. -- Applies a CSS status class based on whether the panel has active content -- and whether it is a news-type or standard-type window. -- Displays a badge showing the number of H2-level discussion topics (or "i" -- for news panels), using a unified pill shape for both types. -- Renders the fetched content inside the card body, or a localised -- empty-state message with a decorative check icon when no content is present. -- Panels with more than five H2 headings receive a scroll-to-top wikilink at -- the bottom of their content area for easy navigation. -- The message of a window with nothing to do («Gjithçka në rregull!»), with its check mark local function empty_message(msg) return tostring(mw_html.create('div'):addClass('no-content-message') :tag('span'):wikitext(msg):done() :tag('span'):addClass('no-content-icon'):wikitext('✓'):done()) end -- «own», for a window whose badge is not the number of its headings (the article of the week): its badge -- (none when there is nothing to do), its status class and the tooltip of its header. -- The header of every window has a tooltip: «own.tooltip», else that of tooltip_of (what is open and the -- states of its items); «idle_note» when the window has nothing to do. local function build_card(config_data, content, is_active, h2_count, parts, own, states) local card_id = 'window-card-' .. mw_uri.anchorEncode(config_data.title) local status_class = own and own.status or (is_active and (config_data.type == 'news' and "status-news" or "status-alert") or "status-idle") local card = mw_html.create('div') :addClass('window-card mw-collapsible mw-collapsed') :addClass(status_class) :attr('id', card_id) local header = card:tag('div'):addClass('window-header mw-collapsible-toggle') local title_div = header:tag('div'):addClass('title-text'):wikitext(config_data.title) local tooltip if own then if own.badge then title_div:tag('span'):addClass('issue-count-badge'):wikitext(own.badge) end tooltip = own.tooltip elseif h2_count and h2_count > 0 then local badge_text = (config_data.type == 'news') and "i" or tostring(h2_count) title_div:tag('span'):addClass('issue-count-badge'):wikitext(badge_text) tooltip = tooltip_of(config_data, h2_count, parts, states) end if not tooltip and not is_active then tooltip = config_data.idle_note end if tooltip then header:attr('title', tooltip) end header:tag('div'):addClass('caret-icon') local body = card:tag('div'):addClass('window-content mw-collapsible-content') if is_active then local full_content = content -- Append a scroll-to-top link for panels with more than five H2 headings -- so readers can return to the card header without scrolling back up manually. if h2_count > 5 then full_content = full_content .. string.format( '
[[#%s|↑]]
'
, card_id
) end body:wikitext(full_content) else body:wikitext(empty_message(config_data.empty_msg)) end return tostring(card) end -- Entry point invoked by the template. Reads the ordered list of window names -- passed as positional arguments, looks each one up in WINDOW_CONFIG, fetches -- its content, and emits the sequence of rendered cards preceded by the module -- stylesheet tag. Unrecognised window names are silently skipped. function p.render(frame) local parent = frame:getParent() local args = (parent and parent.args[1]) and parent.args or frame.args local output_buffer = { frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:WindowSections/styles.css' } } } local sorted_keys = {} for k, _ in pairs(args) do if type(k) == 'number' then table.insert(sorted_keys, k) end end table.sort(sorted_keys) for _, k in ipairs(sorted_keys) do local conf = WINDOW_CONFIG[mw_text.trim(args[k])] if conf then local content, h2_count, parts, states = get_content(frame, conf.sources) local own if conf.count_class and mw_text.trim(content) ~= "" then -- a window whose items are not headings (the article of the week): the badge counts the elements -- of its content with this class; with none, the window is grey like the other windows with nothing -- to do, with «Gjithçka në rregull!» above its content local _, count = content:gsub((conf.count_class:gsub('%p', '%%%0')), '') if count > 0 then own = { badge = tostring(count), status = 'status-alert', tooltip = conf.count_note:format(count) } else own = { status = 'status-idle', tooltip = conf.done_note } content = empty_message(conf.empty_msg) .. content end end table.insert(output_buffer, build_card(conf, content, mw_text.trim(content) ~= "", h2_count, parts, own, states)) end end return table.concat(output_buffer) end return p