Skip to content

Commit

Permalink
Merge pull request #571 from astroDimitrios/feature/tab-admonition
Browse files Browse the repository at this point in the history
Add support for tabs / tabset panels
  • Loading branch information
froggleston committed Apr 5, 2024
2 parents 915ad12 + 7b3c741 commit 3b41671
Showing 1 changed file with 236 additions and 2 deletions.
238 changes: 236 additions & 2 deletions inst/rmarkdown/lua/lesson.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ local blocks = {
["discussion"] = "message-circle",
["testimonial"] = "heart",
["keypoints"] = "key",
["instructor"] = "edit-2"
["instructor"] = "edit-2",
["tab"] = "none",
["group-tab"] = "none"
}

local block_counts = {
Expand All @@ -36,7 +38,9 @@ local block_counts = {
["discussion"] = 0,
["testimonial"] = 0,
["keypoints"] = 0,
["instructor"] = 0
["instructor"] = 0,
["tab"] = 0,
["group-tab"] = 0
}

-- get the timing elements from the metadata and store them in a global var
Expand Down Expand Up @@ -192,6 +196,10 @@ local button_headings = {
<div class="note-square"><i aria-hidden="true" class="callout-icon" data-feather="eye"></i></div>
{{title}}
</h3>]],
["tab"] = [[
<h3 class="tab-header" id="nav-tab-heading-{{id}}">
{{title}}
</h3>]],
}

local accordion_titles = {
Expand Down Expand Up @@ -268,6 +276,214 @@ accordion = function(el, class)
return(main_div)
end

-- For a single tab block:
-- Store the current tab button number
local tab_button_num = 0
-- Store the tab button number a tabpanel
-- element thinks it is on
local tabpanel_tab_button_num = 1

-- Stores the tab nav buttons
local tab_buttons = {}
-- Stores the elements to form the tabpanel
-- content for the tab currently being processed
local this_tab_tabpanel = {}
-- Stores a tab blocks, tabpanel content
local tabpanels = {}

-- Are we processing a group-tab?
local group_tab = false
local group_tab_titles = {}

local tab_button = [[
<button class="nav-link" id="nav-tab-{{id}}" {{name}} data-bs-toggle="tab" data-bs-target="#nav-tabpanel-{{id}}" type="button" role="tab" aria-controls="nav-tabpanel-{{id}}" aria-selected="false">
{{heading}}
</button>]]

-- The first tab button is active
local tab_button_active = [[
<button class="nav-link active" id="nav-tab-{{id}}" {{name}} data-bs-toggle="tab" data-bs-target="#nav-tabpanel-{{id}}" type="button" role="tab" aria-controls="nav-tabpanel-{{id}}" aria-selected="true">
{{heading}}
</button>]]

add_to_tabpanel = function(el)
-- If the tabpanel_button_number is the same
-- as the nav tab_button_number this element
-- belongs with the current nav button so store
-- it for later
if tabpanel_tab_button_num == tab_button_num then
table.insert(this_tab_tabpanel, el)
-- Else we have hit the next tab button and should
-- wrap the tabpanel content we stored in the
-- this_tab_tabpanel table for the previous button
else
local id
if group_tab then
local tab_id = block_counts["group-tab"]
id = tab_id.."-"..group_tab_titles[tabpanel_tab_button_num]
else
local tab_id = block_counts["tab"]
id = tab_id.."-"..tabpanel_tab_button_num
end

-- Wrap the tabpanel contents in a div
local tabpanel_div = pandoc.Div(this_tab_tabpanel)
-- n.b. in pandoc 2.17, the attributes must be set after the classes
if tabpanel_tab_button_num == 1 then
tabpanel_div.classes = {"tab-pane show active"}
else
tabpanel_div.classes = {"tab-pane"}
end
tabpanel_div.identifier = "nav-tabpanel-"..id
tabpanel_div.attributes = {
['role'] = 'tabpanel',
['aria-labelledby'] = "nav-tab-"..id,
}

-- Store the div for the tab_block function
table.insert(tabpanels, tabpanel_div)

-- We move onto the next button having processed
-- the previous buttons tabpanel content
tabpanel_tab_button_num = tabpanel_tab_button_num + 1

-- The current element belongs to the new button
-- so empty out the this_tab_tabpanel table and store the el
this_tab_tabpanel = {}
table.insert(this_tab_tabpanel, el)
end
end

tab_filter = {
Header = function(el)
-- Level 3 headers mark the tab titles
-- all other headers in a tab block are ignored
if el.level == 3 then

-- Insert the title for the add_to_tabpanel to access
local title = pandoc.utils.stringify(el)

local id
local name
if group_tab then
local tab_id = block_counts["group-tab"]
local title_no_spaces = title:gsub("%s+", "")
id = tab_id.."-"..title_no_spaces
-- The JS for the group tabs selects buttons
-- to show based on the name attribute.
-- Here we set it to the button title.
name = 'name="'..title_no_spaces..'"'
-- Store the title so it can be used in the tabpanel id
table.insert(group_tab_titles, title_no_spaces)
else
local tab_id = block_counts["tab"]
id = tab_id.."-"..tab_button_num+1
-- Non group tabs don't need a name attribute.
name = ""
end

-- Found another button so increment the
-- current tab_button_num
tab_button_num = tab_button_num + 1

-- Create the button, if this is the first
-- button it needs to be active
local this_button
if tab_button_num == 1 then
this_button = tab_button_active
else
this_button = tab_button
end

-- Substitute in the button information
this_button = this_button:gsub("{{heading}}", button_headings["tab"])
this_button = this_button:gsub("{{title}}", title)
this_button = this_button:gsub("{{id}}", id)
this_button = this_button:gsub("{{name}}", name)

-- Convert the tab button to a raw block and store
local button = pandoc.RawBlock("html", this_button)
table.insert(tab_buttons, button)
end
end,
-- for all other elements process them using
-- the add_to_tabpanel function
Para = function(el)
_ = add_to_tabpanel(el)
end,
Div = function(el)
_ = add_to_tabpanel(el)
end,
Figure = function(el)
_ = add_to_tabpanel(el)
end,
CodeBlock = function(el)
_ = add_to_tabpanel(el)
end,
OrderedList = function(el)
_ = add_to_tabpanel(el)
end,
BulletList = function(el)
_ = add_to_tabpanel(el)
end
}

tab_block = function(el)

-- Increment the tab count
local count
if group_tab then
block_counts["group-tab"] = block_counts["group-tab"] + 1
count = block_counts["group-tab"]
else
block_counts["tab"] = block_counts["tab"] + 1
count = block_counts["tab"]
end

-- Walk the tab elements and process them
_ = pandoc.walk_block(el,tab_filter)

-- Wraps the tab buttons to create the tablist div
local button_div_id = "nav-tab-"..count
local button_div = pandoc.Div(tab_buttons)
button_div.identifier = button_div_id
button_div.classes = {"nav", "nav-tabs"}
button_div.attributes = {
['role'] = 'tablist',
}

-- The tab_filter uses the current tab number
-- to determine whether we have reached the next tab
-- This tricks the add_to_tabpanel function into thinking
-- it has hit the number of tabs + 1 so it wraps
-- the last tabpanel in a div
tab_button_num = tab_button_num + 1
_ = add_to_tabpanel(tabpanels)

-- Wraps the tabpanels
local tab_content_div = pandoc.Div(tabpanels)
local tab_content_div_id = "nav-tabContent-"..count
tab_content_div.identifier = tab_content_div_id
tab_content_div.classes = {"tab-content"}

-- Create the nav html tags
local nav_start = pandoc.RawBlock("html", "<nav>")
local nav_end = pandoc.RawBlock("html", "</nav>")

-- Put everything in a tabs div
local tabs = pandoc.Div({nav_start, button_div, nav_end, tab_content_div})
tabs.classes = {"tabs"}

-- Reset counters for the next tab block
tab_button_num = 0
tabpanel_tab_button_num = 1
tab_buttons = {}
this_tab_tabpanel = {}
tabpanels = {}

return tabs
end

callout_block = function(el)
local classes = el.classes:map(pandoc.utils.stringify)
local this_icon = blocks[classes[1]]
Expand Down Expand Up @@ -402,6 +618,24 @@ handle_our_divs = function(el)
return(challenge_block(el))
end

-- Tab blocks:
--
-- Toggleable Tab blocks.
v,i = el.classes:find("tab")
if i ~= nil then
group_tab = false
return(tab_block(el))
end

-- Group Tab blocks:
--
-- Toggleable Group Tab blocks.
v,i = el.classes:find("group-tab")
if i ~= nil then
group_tab = true
return(tab_block(el))
end

-- All other Div tags should have at most level 3 headers
level_head(el, 3)
return(callout_block(el))
Expand Down

0 comments on commit 3b41671

Please sign in to comment.