Перейти к содержанию

Модуль:Навигация учебника

Материал из Викиучебника — открытых книг для открытого мира

Для документации этого модуля может быть создана страница Модуль:Навигация учебника/doc

local p = {}

local TRIM_PATTERN = "^%s*(.-)%s*$"
local LINE_PATTERN = "[^\n]+"
local FILTER_PATTERN = "^[%*#:]"
local LINK_PATTERN = "%[%[([^%]]-)%]%]"

local function trim(s)
    return (s:gsub(TRIM_PATTERN, "%1"))
end

function p.nav(frame)
    local args = frame.args
    local parentTitle = args.main or args[1]
    local currentSubPage = args.page or args[2]
    if not parentTitle or not currentSubPage then
        error("Ошибка: не переданы обязательные аргументы 'main' и 'page'.")
    end
    -- Оставляем только имя подстраницы после последнего слеша
    local slashPos = currentSubPage:find("/", 1, true)
    if slashPos then
        currentSubPage = trim(currentSubPage:match(".*/(.*)"))
    else
        currentSubPage = trim(currentSubPage)
    end

    local parentPageObj = mw.title.new(parentTitle)
    if not parentPageObj or not parentPageObj.exists then
        error("Страница '" .. parentTitle .. "' не найдена.")
    end

    local parentContent = parentPageObj:getContent() or ""
    if parentContent == "" then
        error("Не удалось получить содержимое страницы '" .. parentTitle .. "'.")
    end
    -- Извлекаем весь блок {{Содержание}} вплоть до его закрытия
    local contentBlock = parentContent:match("{{Содержание([%s%S]+)}}")
    if not contentBlock then
        error("Шаблон {{Содержание}} не найден на '" .. parentTitle .. "'.")
    end

    local pages = {}
    for line in contentBlock:gmatch(LINE_PATTERN) do
        local tline = trim(line)
        if tline:match(FILTER_PATTERN) then
            for link in tline:gmatch(LINK_PATTERN) do
                local target, display = link:match("([^|]+)|(.+)")
                local name = nil
                if display then
                    name = trim(display)
                else
                    local after = link:match(".*/(.*)")
                    name = trim(after or link)
                end
                table.insert(pages, name)
            end
        end
    end

    if #pages == 0 then
        error("Не удалось извлечь список из {{Содержание}} на '" .. parentTitle .. "'.")
    end

    -- Поиск индекса текущей подстраницы
    local currentIndex
    for i, name in ipairs(pages) do
        if name == currentSubPage then currentIndex = i; break end
    end
    if not currentIndex then
        return ""
    end

    -- Формируем ссылки на соседние подстраницы
    local prevLink, nextLink = "", ""
    if currentIndex > 1 then
        prevLink = string.format("[[%s/%s|%s]]", parentTitle, pages[currentIndex-1], pages[currentIndex-1])
    end
    if currentIndex < #pages then
        nextLink = string.format("[[%s/%s|%s]]", parentTitle, pages[currentIndex+1], pages[currentIndex+1])
    end

    local navType = (args.type and args.type:lower()) or ""
    if navType == "prev" then
        return prevLink ~= "" and ("[[Файл:Left Arrow - The Noun Project.svg|25px|link=]] " .. prevLink) or ""
    elseif navType == "next" then
        return nextLink ~= "" and (nextLink .. " [[Файл:Right Arrow - The Noun Project.svg|25px|link=]]") or ""
    else
        local parts = {}
        if prevLink ~= "" then table.insert(parts, prevLink) end
        if nextLink ~= "" then table.insert(parts, nextLink) end
        return table.concat(parts, " | ")
    end
end

function p.getContent(frame)
    local args = frame.args
    local textbookName = args[1] or args.page or ""
    if textbookName == "" then error("Ошибка: не указано название учебника.") end

    local titleObj = mw.title.new(textbookName)
    if not titleObj or not titleObj.exists then
        error("Страница '" .. textbookName .. "' не найдена.")
    end

    local content = titleObj:getContent() or ""
    if content == "" then
        error("Страница '" .. textbookName .. "' пуста или недоступна.")
    end

    -- Извлечение всего блока {{Содержание}}
    local contentBlock = content:match("{{Содержание([%s%S]+)}}")
    if not contentBlock then
        error("Шаблон {{Содержание}} не найден на '" .. textbookName .. "'.")
    end

    -- Парсинг названий подстраниц и стадий готовности
    local pages, stages = {}, {}
    for line in contentBlock:gmatch(LINE_PATTERN) do
        local tline = trim(line)
        if tline:match(FILTER_PATTERN) then
            local link = tline:match(LINK_PATTERN)
            local stageVal = tline:match("{{Стадия кор|([^}]-)}}") or "0%"
            if link then
                local target, display = link:match("([^|]+)|(.+)")
                local name = display or (link:match(".*/(.*)") or link)
                table.insert(pages, trim(name))
                table.insert(stages, trim(stageVal))
            end
        end
    end

    -- Определяем текущую подстраницу через mw.title
    local currentTitle = mw.title.getCurrentTitle()
    local currentSubPage = currentTitle and currentTitle.prefixedText:match(textbookName .. "/(.+)")
    if currentSubPage then currentSubPage = trim(currentSubPage) end

    -- Ищем индекс и соответствующую стадию
    local stage
    if currentSubPage then
        for i, name in ipairs(pages) do
            if name == currentSubPage then
                stage = stages[i]
                break
            end
        end
    end

    -- Генерация итогового вывода
    local full = "{{Содержание" .. contentBlock .. "}}"
    local modified = full:gsub("(|%s*width%s*=%s*)100%%", "%130%%")
    local output = mw.getCurrentFrame():preprocess(modified)

    -- Исправление относительных ссылок
    output = output:gsub("%[%[/(.-)|(.+)%]\]\]", string.format("[[%s/%%1|%%2]]", textbookName))
    output = output:gsub("%[%[/(.-)%]\]\]", string.format("[[%s/%%1]]", textbookName))

    if stage then
        output = string.format("{{Готовность|%s}}\n%s", stage, output)
    end

    return output
end

return p