Модуль:Topic monitoring
Поделись знанием:
Во избежание поломок страниц, использующих данный модуль, желательно экспериментировать в Песочнице для модулей.
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, '</%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() 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 reloadLink if args['обновить'] then reloadLink = yesno(args['обновить'], false) else reloadLink = true 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 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 -- формируем шапку 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 standardMode or talkpageMode then headerContent = headerContent .. '<p><b>' .. #topics .. '</b> ' .. ru:plural(#topics, 'тема', 'темы', 'тем') .. ' на <b>' .. #pages .. '</b> ' .. ru:plural(#pages, 'странице', 'страницах') .. '</p>\n' elseif afdMode then if #pages ~= 0 then headerContent = headerContent .. '<p>За <b>' .. #pages .. '</b> ' .. ru:plural(#pages, 'день', 'дня', 'дней') .. '</p>\n' end elseif pageListMode then headerContent = headerContent .. '<p><b>' .. #pageListData .. '</b> ' .. ru:plural(#pageListData, 'страница', 'страницы', 'страниц') .. '</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>(обновлялось в ' .. ru:formatDate('H:i j xg Y') .. ' (UTC))\n' end if separateTopicListPage then headerContent = headerContent .. (reloadLink and ' <b>·</b> ' or '') .. '<span class="plainlinks">[' .. separateTopicListPage:fullUrl('action=edit') .. ' Редактировать список тем]</span>\n' end if reloadLink or separateTopicPage then headerContent = headerContent .. '</p>\n' end if not afdMode and fullTopicsToShow > 50 and #topics > 50 then headerContent = headerContent .. '<p style="font-size:85%;">Для более быстрой загрузки страницы и экономии ресурсов сервера сократите число тем, содержимое которых выводится на странице, уменьшив значение параметра <code>полных тем</code> в шаблоне.</p>\n' end -- формируем таблицу if pageListData and pageListData[1] then local tableContent = '{| class="wikitable wide sortable"\n' .. '! width="2%" | №' .. '!! Страница ' .. '!! width="25%" | Последнее сообщение ' .. '!! 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="line-height:1.4; 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="line-height:1.4; display:block; text-decoration:inherit;" class="topicWatch-outFragmentLink" title="Ссылка прямо на реплику работает только при установленном скрипте topicWatch.js">' .. 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 = '{| class="wikitable wide sortable topicWatch-topicTable"' .. (fullTopicsToShow ~= 0 and ' id="topicTable' .. randomNum .. '"' or '') .. '\n' .. (talkpageMode and talkpageCount > 1 and '' or '! width="2%" | № !') .. '! Тема ' .. '!! width="25%" | Последнее сообщение ' .. '!! width="10%" | Сообщений ' .. '!! width="10%" | Авторов\n' for k, v in pairs(topics) do tableContent = tableContent .. '|-' .. (needHighlightOutLinks and k > fullTopicsToShow and ' style="background-color:#f2f2f2;"' or '') .. '\n' .. (talkpageMode and talkpageCount > 1 and '' or '| style="text-align:center;" | ' .. v.num .. '\n') .. '| ' .. 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 ) .. ' ←.C2.A0' .. v.pageTitle ) or v.pageTitle .. ( v.subsectionHeading and v.subsectionHeading .. ' (' .. v.sectionHeadingLink .. ')' or v.sectionHeadingLink ) ) .. '|<span ' .. (k <= fullTopicsToShow and '' or 'title="Откроется на отдельной странице" ') .. ( talkpageMode and talkpageCount == 1 and 'style="line-height:1.4; display:block; font-size:110%;">' or 'style="line-height:1.4; display:block;"><span style="visibility:hidden;">§ </span><small>' .. v.pageTitle .. '</small><br>§ ' ) .. v.sectionHeading .. ( -- может быть предварительный после оспоренного, поэтому ярлык оспаривания раньше 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 '' ) .. '</span>]]\n' .. '| style="/* Chrome */ word-break:break-word; /* ? */ word-wrap:break-word;" | ' .. conceal(v.lastMsgDateTimestamp, 'lastMsgDate') .. ( k <= fullTopicsToShow and '[[#' .. v.lastMsgAnchor .. '|<span style="line-height:1.4; display:block; text-decoration:inherit;">' or '[[' .. v.pageTitle .. '#' .. v.lastMsgAnchor .. '|<span style="line-height:1.4; display:block; text-decoration:inherit;" class="topicWatch-outFragmentLink" title="Ссылка прямо на реплику работает только при установленном скрипте topicWatch.js">' ) .. v.lastMsgDateString .. '<br>от ' .. v.lastMsgAuthor .. '</span>]]\n' .. '| style="text-align:center;" | ' .. conceal(v.msgCount, 'msgCount') .. v.msgCount .. '\n' .. '| style="text-align:center;" | ' .. conceal(#v.authors, 'authorCount') .. '<span style="border-bottom:1px dotted; cursor:help;" title="' .. table.concat(v.authors, ', ') .. '">' .. #v.authors .. '</span>\n' if k == topicsToShow then break end end tableContent = tableContent .. '|}\n' 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('"', '"') .. '" style="font-style:normal;">%0</cite>') end content = content .. killHeadingMarkers(frame:preprocess(v.sectionContent)) .. '\n' .. '<div style="margin-top:1em; margin-left:0.0em;">' .. (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