Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add glossary/endnote functionality #651

Merged
merged 10 commits into from
Jan 23, 2017
157 changes: 133 additions & 24 deletions lib/showoff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -492,12 +492,6 @@ def process_content_for_section_tags(content, name = nil, opts = {})
# Turn this into a document for munging
doc = Nokogiri::HTML::DocumentFragment.parse(result)

if opts[:section]
doc.css('div.notes-section').each do |section|
section.remove unless section.attr('class').split.include? opts[:section]
end
end

filename = File.join(settings.pres_dir, '_notes', "#{name}.md")
@logger.debug "personal notes filename: #{filename}"
if [nil, 'notes'].include? opts[:section] and File.file? filename
Expand All @@ -519,9 +513,68 @@ def process_content_for_section_tags(content, name = nil, opts = {})
end
end

# Now add a target so we open all external links from notes in a new window
doc.css('.callout.glossary').each do |item|
next unless item.content =~ /^([^|]+)\|([^:]+):(.*)$/
item['data-term'] = $1
item['data-target'] = $2
item['data-text'] = $3
item.content = $3

glossary = (item.attr('class').split - ['callout', 'glossary']).first
address = glossary ? "#{glossary}/#{$2}" : $2
frag = "<a class=\"processed label\" href=\"glossary://#{address}\">#{$1}</a>"

item.children.before(Nokogiri::HTML::DocumentFragment.parse(frag))
end

# Process links
doc.css('a').each do |link|
link.set_attribute('target', '_blank') unless link['href'].start_with? '#'
next if link['href'].start_with? '#'
next if link['class'].split.include? 'processed' rescue nil

# If these are glossary links, populate the notes/handouts sections
if link['href'].start_with? 'glossary://'
doc.add_child '<div class="notes-section notes"></div>' if doc.css('div.notes-section.notes').empty?
doc.add_child '<div class="notes-section handouts"></div>' if doc.css('div.notes-section.handouts').empty?

term = link.content
text = link['title']
href = link['href']
href.slice!('glossary://')

parts = href.split('/')
target = parts.pop
name = parts.pop # either the glossary name or nil

link['class'] = 'term'

label = link.clone
label['class'] = 'label processed'

frag = Nokogiri::HTML::DocumentFragment.parse('<p></p>')
definition = frag.children.first
definition['class'] = "callout glossary #{name}"
definition['data-term'] = term
definition['data-target'] = target
definition['data-text'] = text
definition.content = text
definition.children.before(label)

[doc.css('div.notes-section.notes'), doc.css('div.notes-section.handouts')].each do |section|
section.first.add_child(definition.clone)
end

else
# Add a target so we open all external links from notes in a new window
link.set_attribute('target', '_blank')
end
end

# finally, remove any sections we don't want to print
if opts[:section]
doc.css('div.notes-section').each do |section|
section.remove unless section.attr('class').split.include? opts[:section]
end
end

doc.to_html
Expand Down Expand Up @@ -552,31 +605,87 @@ def final_slide_fixup(text)
end

def process_content_for_all_slides(content, num_slides, opts={})
# this has to be text replacement for now, since the string can appear in any context
content.gsub!("~~~NUM_SLIDES~~~", num_slides.to_s)
doc = Nokogiri::HTML::DocumentFragment.parse(content)

# Should we build a table of contents?
if opts[:toc]
frag = Nokogiri::HTML::DocumentFragment.parse ""
toc = Nokogiri::XML::Node.new('div', frag)
toc['id'] = 'toc'
frag.add_child(toc)

Nokogiri::HTML(content).css('div.subsection > h1:not(.section_title)').each do |section|
entry = Nokogiri::XML::Node.new('div', frag)
entry['class'] = 'tocentry'
toc.add_child(entry)

link = Nokogiri::XML::Node.new('a', frag)
link['href'] = "##{section.parent.parent['id']}"
link.content = section.content
entry.add_child(link)
toc = Nokogiri::HTML::DocumentFragment.parse("<p id=\"toc\"></p>")

doc.css('div.subsection > h1:not(.section_title)').each do |section|
href = section.parent.parent['id']
frag = "<div class=\"tocentry\"><a href=\"##{href}\">#{section.content}</a></div>"
link = Nokogiri::HTML::DocumentFragment.parse(frag)

toc.children.first.add_child(link)
end

# swap out the tag, if found, with the table of contents
content.gsub!("~~~TOC~~~", frag.to_html)
doc.at('p:contains("~~~TOC~~~")').replace(toc)
end

content
doc.css('.slide.glossary .content').each do |glossary|
name = (glossary.attr('class').split - ['content', 'glossary']).first
list = Nokogiri::HTML::DocumentFragment.parse('<ul class="glossary terms"></ul>')
seen = []

doc.css('.callout.glossary').each do |item|
target = (item.attr('class').split - ['callout', 'glossary']).first

# if the name matches or if we didn't name it to begin with.
next unless target == name

# the definition can exist in multiple places, so de-dup it here
term = item.attr('data-term')
next if seen.include? term
seen << term

# excrutiatingly find the parent slide content and grab the ref
# in a library less shitty, this would be something like
# $(this).parent().siblings('.content').attr('ref')
href = nil
item.ancestors('.slide').first.traverse do |element|
next if element['class'].nil?
next unless element['class'].split.include? 'content'

href = element.attr('ref')
end

text = item.attr('data-text')
link = item.attr('data-target')
page = glossary.attr('ref')
anchor = "#{page}+#{link}"
next if href.nil? or text.nil? or link.nil?

frag = "<li><a id=\"#{anchor}\" class=\"label\">#{term}</a>#{text}<a href=\"##{href}\" class=\"return\">↩</a></li>"
item = Nokogiri::HTML::DocumentFragment.parse(frag)

list.children.first.add_child(item)
end

glossary.add_child(list)
end

# now fix all the links to point to the glossary page
doc.css('a').each do |link|
next if link['href'].nil?
next unless link['href'].start_with? 'glossary://'

href = link['href']
href.slice!('glossary://')

parts = href.split('/')
target = parts.pop
name = parts.pop # either the glossary name or nil

classes = name.nil? ? ".slide.glossary" : ".slide.glossary.#{name}"
href = doc.at("#{classes} .content").attr('ref') rescue nil

link['href'] = "##{href}+#{target}"
end

doc.to_html
end

# Find any lines that start with a <p>.(something), remove the ones tagged with
Expand Down
30 changes: 29 additions & 1 deletion public/css/showoff.css
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,8 @@ form .element {
.callout.exercise,
.callout.stop,
.callout.thumbsup,
.callout.conversation {
.callout.conversation,
.callout.glossary {
padding-left: 3em;
}

Expand All @@ -955,6 +956,7 @@ form .element {
.callout.stop:before { content: "\f05e"; } /* fa-ban */
.callout.thumbsup:before { content: "\f164"; } /* fa-thumbs-up */
.callout.conversation:before { content: "\f0e5"; } /* fa-comment */
.callout.glossary:before { content: "\f02d"; } /* fa-book */

/* callouts used on error pages */
.error .callout {
Expand All @@ -970,6 +972,32 @@ form .element {
/**********************
*** end callouts ***
**********************/
.callout.glossary a.label,
ul.glossary.terms a.label {
display: block;
font-weight: 600;
text-decoration: none;
}
ul.glossary.terms a.return {
text-decoration: none;
padding-right: 3em;
}
a.term {
text-decoration: none;
}
a.term:after {
font-family: FontAwesome;
font-size: 0.8em;
vertical-align: super;
content: "\f27b";
text-decoration: none;/* fa-commenting-o */
}


/**********************
*** glossary ***
**********************/


/* Render hidden headlines so that when we print with wkhtmltopdf, we can use section
titles for page headers. it would make sense to put this in the print section, but
Expand Down
11 changes: 6 additions & 5 deletions public/js/showoff.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,13 @@ function currentSlideFromName(name) {

function currentSlideFromParams() {
var result;
if (result = window.location.hash.match(/#([0-9]+)/)) {
return result[result.length - 1] - 1;
// Match numeric slide hashes: #241
if (result = window.location.hash.match(/^#([0-9]+)$/)) {
return result[1] - 1;
}
else {
var hash = window.location.hash
return currentSlideFromName(hash.substr(1, hash.length))
// Match slide, with optional internal mark: #slideName(+internal)
else if (result = window.location.hash.match(/^#([^+]+)\+?(.*)?$/)) {
return currentSlideFromName(result[1]);
}
}

Expand Down