Модуль:Topic monitoring — Википедия

Документация

Реализация шаблона {{Мониторинг тем}}. Основан на модуле Get page content.

См. также

[править код]
local p = {}  local function conceal(text, class) 	 class = class or '' 	 return '<span style="display:none; speak:none;" class="topicWatch-' .. class .. '">' .. text .. '</span>' -- содержимое шаблона {{~}} end  local function findInTable(table, value) 	for k, v in pairs(table) do 		if v == value then 			return true 		end 	end 	return false end  local function cleanSectionHeading(heading) 	-- The following patterns reproduce [[Участник:Jack who built the house/transferHeadingToSummary.js]] 	heading = mw.ustring.gsub(heading, '%[%[:?[^|%]]*|([^%]]*)%]%]', '%1') 	heading = mw.ustring.gsub(heading, '%[%[:?([^%]]*)%]%]', '%1') 	heading = mw.ustring.gsub(heading, "'''(.-)'''", '%1') 	heading = mw.ustring.gsub(heading, "''(.-)''", '%1') 	heading = mw.ustring.gsub(heading, '</?%w+ ?/?>', '') 	heading = mw.ustring.gsub(heading, '<%w+ [%w ]-=[^<>]->', '') 	heading = mw.ustring.gsub(heading, '  +', ' ') 	heading = mw.text.trim(heading) 	return heading end  local function sectionHeadingToLink(sectionHeading) 	local sectionHeadingLink = sectionHeading 	-- The following reproduces processURI function of [[Участник:Jack who built the house/copyWikilinks.js]] 	--[[sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '<', '%%3C') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '>', '%%3E') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '%[', '%%5B') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '%]', '%%5D') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '{', '%%7B') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '|', '%%7C') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, '}', '%%7D') 	sectionHeadingLink = mw.ustring.gsub(sectionHeadingLink, ' ', '.C2.A0')]] 	return '#' .. sectionHeadingLink end  local function killHeadingMarkers(content) 	content = mw.ustring.gsub( 		content, 		string.char(127) .. '\'"`UNIQ%-%-h%-%d+%-%-QINU`"\'' .. string.char(127), 		'' 	) 	return content end  function p.main(frame) 	if not getArgs then 		getArgs = require('Модуль:Arguments').getArgs 	end 	local yesno = require('Module:Yesno') 	local args = getArgs(frame, {removeBlanks = false}) 	local ru = mw.getLanguage('ru') 	local errorInfo = {} 	 	-- обрабатываем параметры 	local talkpageMode = args['режим'] == 'страницы' 	local afdMode = args['режим'] == 'КУ' 	local pageListMode = args['режим'] == 'список страниц' 	local standardMode 	if not talkpageMode and not afdMode and not pageListMode then 		standardMode = true 	end 	 	local separateTopicListPage, separateTopicList 	if standardMode and not args[1] then 		separateTopicListPage = mw.title.new(args['список'] or mw.title.getCurrentTitle().prefixedText .. '/список') 		if separateTopicListPage.exists then 			local content = separateTopicListPage:getContent() 			if content:find('{{') then 				content = frame:preprocess(content) 			end 			separateTopicList = mw.text.split(content, '\n') 		end 		if not separateTopicList or (separateTopicList[1] == '' and not separateTopicList[2]) then 			separateTopicList = {} 		end 	end 	 	local afdItems, nominationsNum 	if afdMode then 		local afdListPage = mw.title.new('Википедия:К удалению') -- Участник:Jack who built the house/песочница2 		nominationsNum = tonumber(args['номинаций']) or 50 		 		afdItems = {} 		local afdListContent = afdListPage:getContent() 		afdListContent = mw.ustring.gsub(afdListContent, '<small>.-<\/small>', '') 		afdListContent = mw.ustring.gsub(afdListContent, '<s>.-<\/s>', '') 		local iterator = mw.ustring.gmatch(afdListContent, '{{Удаление статей|([^|]+)|([^\n]+)}}') 		reversedIteratorTable = {} 		for day, topics in iterator do 			table.insert(reversedIteratorTable, 1, {day = day, topics = topics}) 		end 		for k, v in pairs(reversedIteratorTable) do 			day = v.day 			topics = v.topics 			local afdPage = 'Википедия:К удалению/' .. ru:formatDate('j xg Y', day) 			 			local iterator = mw.text.gsplit(topics, ' • ', true) 			for v2 in iterator do 				if v2 ~= '' then 					v2 = cleanSectionHeading(v2) 					table.insert(afdItems, afdPage .. '#' .. v2) 				end 				if #afdItems >= nominationsNum + 50 then break end 			end 			if #afdItems >= nominationsNum + 50 then break end 		end 	end 	 	local topicsToShow = tonumber(args['тем']) or 10000 	 	-- число тем, содержимое которых выводить непосредственно на странице, а не ссылаться на другую 	local fullTopicsToShow = not pageListMode and ( 			   tonumber(args['полных тем']) 			or ( 				afdMode and 1000 or 10 			   ) 		) 	 	if topicsToShow == 0 and fullTopicsToShow == 0 then return '' end 	 	local style = args['стиль'] == 'форумный' and args['стиль'] or 'wikitable';  	local showTopicCount 	if args['показывать количество тем'] then 		showTopicCount = yesno(args['показывать количество тем'], true) 	else 		showTopicCount = true 	end 	 	local reloadLink 	if args['обновить'] then 		reloadLink = yesno(args['обновить'], false) 	else 		reloadLink = false 	end 	 	local sortFullTopics, sortTopics 	if afdMode then 		sortFullTopics = false 		sortTopics = false 	elseif args['сортировка полных тем'] then 		sortFullTopics = yesno(args['сортировка полных тем'], false) 		sortTopics = true 	else 		sortFullTopics = true 		sortTopics = true 	end 	 	-- проверяем и обрабатываем части названий, чистим от дубликатов 	-- wrongItems — элементы, с которыми что-то не так уже на этапе распознавания адреса 	-- itemsToRemove — элементы, которые предлагается удалить из списка отслеживания 	-- errorInfo (объявлено выше) — сообщения об ошибке 	local items, wrongItems, itemsToRemove = {}, {}, {} 	for k, v in pairs(separateTopicList or afdItems or args) do 		if type(k) == 'number' then 			if v ~= '' then 				local newItem = { 					originalFullTitle = v, 					titleObject = mw.title.new(v), 				} 				if newItem.titleObject and newItem.titleObject.prefixedText ~= '' then 					if newItem.titleObject.fragment ~= '' then 						newItem.sectionHeading = mw.text.encode(mw.uri.decode(newItem.titleObject.fragment), '<>%[%]{|}') 						if not talkpageMode and not pageListMode then 							newItem.resultMode = false 							newItem.sectionHeading = mw.ustring.gsub(newItem.sectionHeading, '\\итог$', function (s) 								newItem.resultMode = true 								return '' 							end) 							newItem.sectionHeading = mw.ustring.gsub(newItem.sectionHeading, '\\\\\\.*$', function (s) -- \\\Итог 								newItem.subsectionHeading = s 								return '' 							end) 						end 					end 					 					if not newItem.sectionHeading and not talkpageMode and not pageListMode then 						table.insert(wrongItems, v) 						table.insert(itemsToRemove, v) 					else 						local found = false 						for k2, v2 in pairs(items) do 							if v2.titleObject.prefixedText == newItem.titleObject.prefixedText and (not newItem.sectionHeading or v2.sectionHeading == newItem.sectionHeading) and (not newItem.subsectionHeading or v2.subsectionHeading == newItem.subsectionHeading) then 								found = true 								break 							end 						end 						if not found then 							table.insert(items, newItem) 						else 							table.insert(itemsToRemove, v) 						end 					end 				else 					table.insert(wrongItems, v) 					table.insert(itemsToRemove, v) 				end 			else 				table.insert(itemsToRemove, v) 			end 		end 	end 	 	if #wrongItems > 0 then 		local wrongItemsString = 			    #wrongItems == 1 			and 'Что-то не так с элементом' 			 or 'Что-то не так со следующими элементами:' 		for k, v in pairs(wrongItems) do 			if k ~= 1 then 				wrongItemsString = wrongItemsString .. ', ' 			end 			wrongItemsString = wrongItemsString .. ' «' .. v .. '»' 		end 		wrongItemsString = wrongItemsString .. '.' 		table.insert(errorInfo, wrongItemsString) 	end 	 	-- запрашиваем данные 	local allData = {} 	local talkpageCount 	local onlyPageTitle 	local pageListData 	if talkpageMode then 		talkpageCount = 0 		local parse_talkpage_content = require('Модуль:Get page content')._parse_talkpage_content 		 		for k, v in pairs(items) do 			local argsToPass = {} 			argsToPass[1] = v.titleObject 			argsToPass['короткие заголовки'] = not items[2] and true or false 			if fullTopicsToShow == 0 then 				argsToPass['только статистика'] = true 			end 			 			local allDataFromPage = parse_talkpage_content(argsToPass) 			if type(allDataFromPage) == 'table' then 				if allDataFromPage[1] then 					local pageTitle = v.titleObject.prefixedText 					talkpageCount = talkpageCount + 1 					for k2, v2 in pairs(allDataFromPage) do 						v2.pageTitle = pageTitle 						v2.canonicalPath = v2.pageTitle .. '#' .. v2.sectionHeading .. (v2.subsectionHeading and '\\\\\\' .. v2.subsectionHeading or '') 						table.insert(allData, v2) 					end 					if talkpageCount == 1 then 						onlyPageTitle = pageTitle 					end 				elseif allDataFromPage.pageNotExistMessage then 					table.insert(itemsToRemove, v.originalFullTitle) 					table.insert(errorInfo, allDataFromPage.pageNotExistMessage) 				end 			else 				table.insert(errorInfo, 'Не удалось получить данные о странице «' .. v.titleObject.prefixedText .. '» от модуля получения содержимого страницы.') 			end 		end 	elseif pageListMode then 		pageListData = {} 		local parse_talkpage_content = require('Модуль:Get page content')._parse_talkpage_content 		 		for k, v in pairs(items) do 			local argsToPass = {} 			argsToPass[1] = v.titleObject 			argsToPass['только статистика'] = true 			argsToPass['режим списков страниц'] = true 			 			local allDataFromPage = parse_talkpage_content(argsToPass) 			if type(allDataFromPage) == 'table' then 				if allDataFromPage[1] then 					local pageTitle = v.titleObject.prefixedText 					local pageData = { 						num = k, 						pageTitle = pageTitle, 						msgCount = 0, 						topicCount = 0, 						lastMsgDateTimestamp = 0 					} 					for k2, v2 in pairs(allDataFromPage) do 						if v2.msgCount then 							pageData.msgCount = pageData.msgCount + v2.msgCount 							if v2.lastMsgDateTimestamp > pageData.lastMsgDateTimestamp then 								--pageData.lastMsgDate = v2.lastMsgDate 								pageData.lastMsgDateString = v2.lastMsgDateString 								pageData.lastMsgDateTimestamp = v2.lastMsgDateTimestamp 								pageData.lastMsgAuthor = v2.lastMsgAuthor 							end 						end 						pageData.topicCount = pageData.topicCount + 1 						if v2.warningHeading and not pageData.warningHeading then 							pageData.warningHeading = v2.warningHeading 						end 					end 					table.insert(pageListData, pageData) 				elseif allDataFromPage.pageNotExistMessage then 					table.insert(itemsToRemove, v.originalFullTitle) 					table.insert(errorInfo, allDataFromPage.pageNotExistMessage) 				end 			else 				table.insert(errorInfo, 'Не удалось получить данные о странице «' .. v.titleObject.prefixedText .. '» от модуля получения содержимого страницы.') 			end 		end 	else 		local parse_section_content = require('Модуль:Get page content')._parse_section_content 		 		for k, v in pairs(items) do 			local argsToPass = {} 			table.insert(argsToPass, v.titleObject) 			table.insert(argsToPass, v.sectionHeading) 			argsToPass['итог'] = v.resultMode 			argsToPass['подраздел'] = v.subsectionHeading 			argsToPass['как данные'] = true 			if fullTopicsToShow == 0 then 				argsToPass['только статистика'] = true 			else 				argsToPass['стандартный заголовок'] = true 			end 			if afdMode then 				argsToPass['режим КУ'] = true 			end 			 			local data = parse_section_content(argsToPass) 			if type(data) == 'table' then 				if data.pageNotExistMessage then 					table.insert(itemsToRemove, v.originalFullTitle) 					table.insert(errorInfo, data.pageNotExistMessage) 				elseif data.sectionNotExistMessage then 					table.insert(itemsToRemove, v.originalFullTitle) 					table.insert(errorInfo, data.sectionNotExistMessage) 				else 					if topicsToShow ~= 0 and data.msgCount then 						data.pageTitle = v.titleObject.prefixedText 						data.sectionHeading = v.sectionHeading 						if argsToPass['подраздел'] then 							data.subsectionHeading = argsToPass['подраздел'] 						end 						data.canonicalPath = data.pageTitle .. '#' .. data.sectionHeading .. (data.subsectionHeading and '\\\\\\' .. data.subsectionHeading or '') 					end 					if afdMode then 						if not data.closureHeading then 							table.insert(allData, data) 							if #allData >= nominationsNum then break end 						end 					else 						table.insert(allData, data) 					end 				end 			else 				table.insert(errorInfo, 'Не удалось получить данные о странице «' .. v.titleObject.prefixedText .. '» от модуля получения содержимого страницы.') 			end 		end 	end 	 	for k, v in pairs(allData) do 		allData[k].num = k 	end 	 	-- если включена сортировка и полных тем, и тем в таблице, мы можем сразу отсортировать общий массив 	if sortTopics and (fullTopicsToShow == 0 or sortFullTopics) then 		table.sort(allData, function (data1, data2) 			-- благодаря 10000 - dataN.num темы частично сохраняют изначальный порядок 			local lastMsgDate1Timestamp = data1.lastMsgDateTimestamp or 10000 - data1.num 			local lastMsgDate2Timestamp = data2.lastMsgDateTimestamp or 10000 - data2.num 			 			return lastMsgDate1Timestamp > lastMsgDate2Timestamp 		end) 	end 	 	-- считаем число тем и страниц, откладываем отсутствующие 	local pages, topics, fullTopics = {}, {}, {} 	local randomNum 	if allData[1] then 		if fullTopicsToShow ~= 0 then 			randomNum = tostring(os.clock()):sub(-7) 		end 		 		for k, v in pairs(allData) do 			table.insert(fullTopics, v) 			if v.msgCount then 				table.insert(topics, v) 				if not findInTable(pages, v.pageTitle) then 					table.insert(pages, v.pageTitle) 				end 			end 		end 	end 	 	-- если отключена сортировка полных тем, сортируем здесь только темы в таблице 	if sortTopics and (fullTopicsToShow ~= 0 and not sortFullTopics) then 		table.sort(topics, function (data1, data2) 			-- благодаря 10000 - dataN.num темы частично сохраняют изначальный порядок 			local lastMsgDate1Timestamp = data1.lastMsgDateTimestamp or 10000 - data1.num 			local lastMsgDate2Timestamp = data2.lastMsgDateTimestamp or 10000 - data2.num 			 			return lastMsgDate1Timestamp > lastMsgDate2Timestamp 		end) 	end 	 	if #topics > topicsToShow then 		pages = {} 		for k, v in pairs(topics) do 			if not findInTable(pages, v.pageTitle) then 				table.insert(pages, v.pageTitle) 			end 			if k == topicsToShow then 				for i = k + 1, #topics do 					table.remove(topics, k + 1) 				end 				break 			end 		end 	end 	 	-- формируем шапку 	local headerContent = '' 	if talkpageMode and talkpageCount == 1 then 		headerContent = headerContent .. '<div style="font-size:1.5em;">[[' .. onlyPageTitle .. ']]</div>\n' 	elseif afdMode then 		headerContent = headerContent .. '<div style="font-size:1.5em;">' .. #topics .. ' ' .. ru:plural(#topics, 'самая старая незакрытая номинация', 'самые старые незакрытые номинации', 'самых старых незакрытых номинаций') .. ' «[[ВП:К удалению|К удалению]]»</div>\n' 	end 	 	if showTopicCount then 		if standardMode or talkpageMode then 			headerContent = headerContent .. '<p><b>' .. #topics .. '</b> ' .. ru:plural(#topics, 'тема', 'темы', 'тем') .. ' на <b>' .. #pages .. '</b> ' .. ru:plural(#pages, 'странице', 'страницах') 		elseif afdMode then 			if #pages ~= 0 then 				headerContent = headerContent .. '<p>За <b>' .. #pages .. '</b> ' .. ru:plural(#pages, 'день', 'дня', 'дней') 			end 		elseif pageListMode then 			headerContent = headerContent .. '<p><b>' .. #pageListData .. '</b> ' .. ru:plural(#pageListData, 'страница', 'страницы', 'страниц') 		end 		if separateTopicListPage then 			headerContent = headerContent .. '&nbsp;<b>·</b> <span class="plainlinks">[' .. separateTopicListPage:fullUrl('action=edit') .. ' Редактировать список тем]</span>\n' 		end 		headerContent = headerContent .. '</p>\n' 	end 	 	if reloadLink then 		headerContent = headerContent .. '<p ' .. ( 			    not false  -- пока оставим 			and 'style="margin-top:1.5em;"' 			 or '' 		) .. '><span style="font-size:1.5em;" class="plainlinks purgelink">[' .. mw.title.getCurrentTitle():fullUrl('action=purge') .. ' Обновить]</span>&nbsp;<b>·</b> обновлялось в ' .. ru:formatDate('H:i j xg Y') .. ' (UTC)' 	end 	if reloadLink or separateTopicPage then 		headerContent = headerContent .. '</p>\n' 	end 	 	if not afdMode and not pageListMode and fullTopicsToShow > 50 and #topics > 50 then 		headerContent = headerContent .. '<p style="font-size:85%;">Для более быстрой загрузки страницы и экономии ресурсов сервера сократите число тем, содержимое которых выводится на странице, уменьшив значение параметра <code>полных тем</code> в шаблоне.</p>\n' 	end 	 	-- формируем таблицу 	local outFragmentLink_title = 'Ссылка прямо на реплику в настоящий момент работает только при включенном гаджете «Удобные обсуждения»' 	if pageListData and pageListData[1] then 		local tableContent = 			   '{| class="wikitable wide sortable" style="line-height:1.4;"\n' 			.. '! width="2%" | №' 			.. '!! Страница ' 			.. '!! width="25%" class="nowrap" | Последнее сообщение ' 			.. '!! width="10%" | Тем ' 			.. '!! width="10%" | Сообщений\n' 		for k, v in pairs(pageListData) do 			if v.msgCount then 				tableContent = tableContent 					.. '|-\n' 					.. '| style="text-align:center;" | ' .. v.num .. '\n' 					.. '| ' .. conceal(v.pageTitle, 'wikilink') .. '[[' .. v.pageTitle .. '|<span style="display:block; font-size:110%;">' .. v.pageTitle .. ( 							    v.warningHeading 							and '<span style="display:inline-block; margin-left:1em; padding:0 8px; border-radius:5px; font-size:83.33%; line-height:1.5; letter-spacing:1px; background-color:#a07; color:#f9f9f9; white-space:nowrap;">ПРЕДУПР.</span>' 							 or '' 						) .. '</span>]]\n' 					.. '| style="/* Chrome */ word-break:break-word; /* ? */ word-wrap:break-word;" | ' .. ( 						    v.msgCount > 0 						and conceal(v.lastMsgDateTimestamp, 'lastMsgDate') .. '[[' .. v.pageTitle .. '#' .. v.lastMsgDateString .. '_' .. v.lastMsgAuthor .. '|<span style="display:block; text-decoration:inherit;" class="topicWatch-outFragmentLink" title="' .. outFragmentLink_title .. '">' .. v.lastMsgDateString .. '<br>от ' .. v.lastMsgAuthor .. '</span>]]\n' 						 or '—\n' 					) 					.. '| style="text-align:center;" | ' .. conceal(v.msgCount, 'topicCount') .. v.topicCount .. '\n' 					.. '| style="text-align:center;" | ' .. conceal(v.msgCount, 'msgCount') .. v.msgCount .. '\n' 			end 		end 		tableContent = tableContent .. '|}\n' 		headerContent = headerContent .. tableContent 	elseif topicsToShow ~= 0 and #topics ~= 0 then 		local needHighlightOutLinks = fullTopicsToShow ~= 0 and fullTopicsToShow < math.min(#topics, topicsToShow) 		local labelColors = 			    afdMode 			and { 					closure           = '#800', 					preclosure        = '#25820e', 					challengedClosure = '#a07', 					partialClosure    = '#00a', 				} 			 or { 					closure           = '#999', 					preclosure        = '#999', 					challengedClosure = '#999', 					partialClosure    = '#999', 				} 		 		local tableContent 		if style == 'wikitable' then 			tableContent = 				   '{| class="wikitable wide sortable topicWatch-topicTable"' .. (fullTopicsToShow ~= 0 and ' id="topicTable' .. randomNum .. '"' or '') .. ' style="line-height:1.4;"\n' 				.. (talkpageMode and talkpageCount > 1 and '' or '! width="2%" | № !') 				.. '! Тема ' 				.. '!! width="25%" class="nowrap" | Последнее сообщение ' 				.. '!! width="10%" | Сообщений ' 				.. '!! width="10%" | Авторов\n' 		elseif style == 'форумный' then 			tableContent = 				   '{| cellspacing="0" cellpadding="0" style="margin:1em 0; padding:8px 13px; border:1px solid #afd2e9; background:#f1f7fb; line-height:1.4;"\n' 				.. '|\n' 				.. '{| cellspacing="0" cellpadding="0" class="sortable topicWatch-topicTable"' .. (fullTopicsToShow ~= 0 and ' id="topicTable' .. randomNum .. '"' or '') .. '\n' 				.. '|-\n' 				.. (talkpageMode and talkpageCount > 1 and '' or '! style="border-bottom:1px solid #afd2e9; padding:2px 1.5em 4px 0; text-align:right; font-weight:normal;" | № !') 				.. '! style="border-bottom:1px solid #afd2e9; padding:2px 2em 4px 0; text-align:left; font-weight:normal;" | ' .. (talkpageMode and talkpageCount == 1 and '' or '<span style="visibility:hidden;">§ </span>') .. 'Тема ' 				.. '!! style="border-bottom:1px solid #afd2e9; padding:2px 2em 4px 0; text-align:left; font-weight:normal;" class="nowrap" | Последнее сообщение ' 				.. '!! style="border-bottom:1px solid #afd2e9; padding:2px 1.5em 4px 0; text-align:left; font-weight:normal;" | Сообщений\n' 				.. '|-\n' 				.. '| colspan="3" style="padding-top:6px;" |\n' 		end 		for k, v in pairs(topics) do 			local labels = (  -- может быть предварительный после оспоренного, поэтому ярлык оспаривания раньше 					    v.challengedClosureHeading 					and '<span style="display:inline-block; margin-left:1em; padding:0 8px; border-radius:5px; font-size:83.33%; line-height:1.5; letter-spacing:1px; background-color:' .. labelColors.challengedClosure .. '; color:#f9f9f9; white-space:nowrap;" title="В теме есть подраздел с названием «' .. v.challengedClosureHeading .. '»"' .. (afdMode and '' or ' class="topicWatch-label-challengedClosure"') .. '>ОСПОРЕНО</span>' 					 or '' 				) .. ( 					    v.preclosureHeading 					and '<span style="display:inline-block; margin-left:1em; padding:0 8px; border-radius:5px; font-size:83.33%; line-height:1.5; letter-spacing:1px; background-color:' .. labelColors.preclosure .. '; color:#f9f9f9; white-space:nowrap;" title="В теме есть подраздел с названием «' .. v.preclosureHeading .. '»"' .. (afdMode and '' or ' class="topicWatch-label-preclosure"') .. '>ПРЕДЫТОГ</span>' 					 or '' 				) .. ( 					    v.partialClosureHeading 					and '<span style="display:inline-block; margin-left:1em; padding:0 8px; border-radius:5px; font-size:83.33%; line-height:1.5; letter-spacing:1px; background-color:' .. labelColors.partialClosure .. '; color:#f9f9f9; white-space:nowrap;" title="В теме есть подподраздел с названием «' .. v.partialClosureHeading .. '»"' .. (afdMode and '' or ' class="topicWatch-label-partialClosure"') .. '>ЧАСТ. ИТОГ</span>' 					 or '' 				) .. ( 					    v.closureHeading 					and '<span style="display:inline-block; margin-left:1em; padding:0 8px; border-radius:5px; font-size:83.33%; line-height:1.5; letter-spacing:1px; background-color:' .. labelColors.closure .. '; color:#f9f9f9; white-space:nowrap;" title="В теме есть подраздел с названием «' .. v.closureHeading .. '»"' .. (afdMode and '' or ' class="topicWatch-label-closure"') .. '>ИТОГ</span>' 					 or '' 				) 			 			local topicString, lastMsgString, msgCountString, authorCountString, numString 			local topicAttrs, lastMsgAttrs, msgCountAttrs, authorCountAttrs, numAttrs 			if style == 'wikitable' then 				numString = v.num 				topicString = conceal(v.canonicalPath, 'wikilink') .. '[[' .. ( 						    k <= fullTopicsToShow 						and ( 							    talkpageMode and talkpageCount == 1 							and v.sectionHeadingLink 							 or ( 								    v.subsectionHeading 								and v.subsectionHeading .. ' (' .. v.sectionHeadingLink .. ')' 								 or v.sectionHeadingLink 								) .. ' ← ' .. v.pageTitle 							) 						 or v.pageTitle .. ( 							    v.subsectionHeading 							and v.subsectionHeading .. ' (' .. v.sectionHeadingLink .. ')' 							 or v.sectionHeadingLink 							) 					) .. '|<span ' .. (k <= fullTopicsToShow and fullTopicsToShow ~= 0 and '' or 'title="Откроется на отдельной странице" ') .. ( 						    talkpageMode and talkpageCount == 1 						and 'style="display:block; font-size:110%;">' 						 or 'style="display:block;"><span style="visibility:hidden;">§ </span><small>' .. v.pageTitle .. '</small><br><span style="display:inline-block;">§ </span>' 					) .. v.sectionHeading .. labels .. '</span>]]'  -- TODO: учёт подраздела 				lastMsgString = conceal(v.lastMsgDateTimestamp, 'lastMsgDate') .. ( 						    k <= fullTopicsToShow 						and '[[#' .. v.lastMsgAnchor .. '|<span style="display:block; text-decoration:inherit;">' 						 or '[[' .. v.pageTitle .. '#' .. v.lastMsgAnchor .. '|<span style="display:block; text-decoration:inherit;" class="topicWatch-outFragmentLink" title="' .. outFragmentLink_title .. '">' 					) .. v.lastMsgDateString .. '<br>от ' .. v.lastMsgAuthor .. '</span>]]' 				msgCountString = conceal(v.msgCount, 'msgCount') .. v.msgCount 				authorCountString = conceal(#v.authors, 'authorCount') .. '<span style="border-bottom:1px dotted; cursor:help;" title="' .. table.concat(v.authors, ', ') .. '">' .. #v.authors .. '</span>' 				 				numAttrs = 'style="text-align:center;"' 				topicAttrs = '' 				lastMsgAttrs = 'style="/* Chrome */ word-break:break-word; /* ? */ word-wrap:break-word;"' 				msgCountAttrs = 'style="text-align:center;"' 				authorCountAttrs = 'style="text-align:center;"' 			elseif style == 'форумный' then 				numString =     talkpageMode and talkpageCount == 1 							and '<span style="font-size:110%; visibility:hidden;">l</span>' .. v.num .. '<span style="font-size:110%; visibility:hidden;">l</span>'  -- способ, рекомендуемый Горбуновым, чтобы строки были на одной линии 							 or v.num 				topicString = conceal(v.canonicalPath, 'wikilink')  .. '[[' .. v.pageTitle .. ( 						    v.subsectionHeading 						and v.subsectionHeading .. ' (' .. v.sectionHeadingLink .. ')' 						 or v.sectionHeadingLink 					) .. '|' .. ( 						    talkpageMode and talkpageCount == 1 						and '<span style="display:block; font-size:110%;">' 						 or '<span style="display:block;"><span style="display:inline-block;"><span style="visibility:hidden;">§ </span><small>' .. mw.ustring.gsub(v.pageTitle, 'Википедия:Форум/', '') .. '</small></span><br><span style="display:inline-block;">§ </span>' 					) .. v.sectionHeading .. labels .. '</span>]]' 				lastMsgString = conceal(v.lastMsgDateTimestamp, 'lastMsgDate') .. ( 						    k <= fullTopicsToShow 						and '[[#' .. v.lastMsgAnchor .. '|<span style="display:block; text-decoration:inherit;">' 						 or '[[' .. v.pageTitle .. '#' .. v.lastMsgAnchor .. '|<span style="display:block; text-decoration:inherit;" class="topicWatch-outFragmentLink" title="' .. outFragmentLink_title .. '">' 					) .. v.lastMsgDateString .. '<br>от ' .. v.lastMsgAuthor .. '</span>]]' 				msgCountString = conceal(v.msgCount, 'msgCount') .. '<span style="border-bottom:1px dotted; cursor:help;" title="От ' .. #v.authors .. ' ' .. ru:plural(#v.authors , 'автора', 'авторов') .. ':&#10;' .. table.concat(v.authors, ', ') .. '">' .. v.msgCount .. '</span>' 				 				numAttrs = 'style="padding:3px 0; vertical-align:top; text-align:right; padding-right:8px;"' 				topicAttrs = 'style="padding:3px 2em 3px 0; vertical-align:top;"' 				lastMsgAttrs = 'style="padding:3px 0.5em 3px 0; vertical-align:top; /* Chrome */ word-break:break-word; /* ? */ word-wrap:break-word;"' 				msgCountAttrs = 'style="padding:3px 0; vertical-align:top; text-align:center; vertical-align:top;"' 			end 			 			tableContent = tableContent 				.. '|-' .. (style ~= 'форумный' and needHighlightOutLinks and k > fullTopicsToShow and ' style="background-color:#f2f2f2;"' or '') .. '\n' 				.. (talkpageMode and talkpageCount > 1 and '' or '| ' .. numAttrs .. ' | ' .. numString .. '\n') 				.. '| ' .. topicAttrs .. ' | ' .. topicString .. '\n' 				.. '| ' .. lastMsgAttrs .. ' | ' .. lastMsgString .. '\n' 				.. '| ' .. msgCountAttrs .. ' | ' .. msgCountString .. '\n' 				.. (style == 'форумный' and '' or '| ' .. authorCountAttrs .. ' | ' .. authorCountString .. '\n') 		end 		tableContent = tableContent .. '|}\n' 		if style == 'форумный' then 			tableContent = tableContent .. '|}\n' 		end 		headerContent = headerContent .. tableContent 	end 	 	-- формируем сообщение(-я) об ошибке 	if not items[1] then 		if standardMode then 			table.insert(errorInfo, 'В списке тем для отслеживания пока пусто.') 		elseif talkpageMode or pageListMode then 			table.insert(errorInfo, 'В списке страниц пусто.') 		elseif afdMode then 			table.insert(errorInfo, 'Не удалось найти темы.') 		end 	end 	if errorInfo[1] then 		headerContent = headerContent 			.. '<div style="font-style:italic; margin:1em 0;">\n' 			.. table.concat(errorInfo, '<br>') 			.. '</div>\n' 	end 	local itemsToRemoveString 	if itemsToRemove[1] then 		itemsToRemoveString = '<ul style="display:none;" class="topicWatch-itemsToRemove">\n' 		for k, v in pairs(itemsToRemove) do 			itemsToRemoveString = itemsToRemoveString .. '<li>' .. v .. '</li>\n' 		end 		itemsToRemoveString = itemsToRemoveString .. '</ul>\n' 		headerContent = headerContent .. itemsToRemoveString 	end 	 	-- формируем сами темы 	local content = '' 	if fullTopicsToShow ~= 0 then 		headerContent = headerContent .. '__TOC__\n' 		for k, v in pairs(fullTopics) do 			if v.msgCount then 				v.sectionContent = mw.ustring.gsub(v.sectionContent, v.lastMsgDateString .. ' %(UTC%)', '<cite id="' .. v.lastMsgAnchor:gsub('"', '&quot;') .. '" style="font-style:normal;">%0</cite>') 			end 			content = content 				.. killHeadingMarkers(frame:preprocess(v.sectionContent)) .. '\n' 				.. '<div style="margin-top:1em;">' .. (topicsToShow ~= 0 and '[[#topicTable' .. randomNum .. '|↑ К списку тем]]' or '[[#toc|↑ К содержанию]]') .. '</div>\n' 			if k == fullTopicsToShow then break end 		end 	end 	 	content = headerContent .. content 	 	return content end  return p