Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Templating + Fixes for Static and PDF Generation #149

Merged
merged 16 commits into from

3 participants

@grundprinzip

Hi,

basically this group of commits does a few things.

First, it implements a way to provide the user with layout templates if he wants and make the pretty configurable . Templates can be applied either per default or per slide. Please have a look at the updated readme for a usage instruction.

Second, I fixed a a few bugs that relate to static HTML and PDF generation. This includes, rewriting URLs used in CSS to point to static file:// URIs, forcing wkthmltopdf to use the print media styles instead of the screen media styles. In addition sytnax highlighting now works both in PDF and static mode.

It would be great if you could review my changes.

You can find a simple presentation of how to use the templates, made with showoff here - http://grundprinzip.github.com/Showoff-Templates

Thanks
Martin

grundprinzip added some commits
@grundprinzip grundprinzip Fixing print CSS
When printing the CSS file paths need to be rewritten
to match the absolute URIs used in PDFKit. In addition, it
is required to configure the print media style or 
the output will be buggy. 

In print mode, the position of the slide needs to be 
set explicitly to "relavtive" or more explict styling 
in the slide container will fail in print mode.
96d510d
@grundprinzip grundprinzip Templates
* allow templates to be configurable
* allow default template to be not present
* parsing of slide options
04dd4eb
@grundprinzip grundprinzip Templates for Statics and PDFs
* fixing positioning of a slide when using the onepage.css
ce08379
@grundprinzip grundprinzip Documentation for Templates a838d8c
@grundprinzip grundprinzip Proper formating for documentation bad2a58
@grundprinzip grundprinzip Documentation and Replacements
Replaced the ###CONTENT### string with something 
that does not break in Markdown :). Furthermore now
multiple replacements can be used in the slides, the 
documentation is in the README.
ea6046a
@grundprinzip grundprinzip Fixing syntax highlighting in static mode and PDF output 00f52a9
@grundprinzip grundprinzip Removing old init stuff f6a82a4
@grundprinzip grundprinzip Avoiding error if no @languages specified e900701
@grundprinzip grundprinzip Fixing bad logger variable in static mode 2b76bc2
@grundprinzip

This would close #143 and #144 as well.

@grundprinzip grundprinzip referenced this pull request
Closed

Giant Merge #150

@grundprinzip

I merged the recent changes from schacon/master to keep the changes compatible with the current development.

@bowsersenior

Hey, this is just what I was looking for! I hope this gets merged in soon.

@goncalossilva
Collaborator

I love this change. I'm not so sure about the ~~~ SYNTAX ~~~, but I don't have a better suggestion either.

I'll merge this and tweak the README a little bit. This is an advanced feature and I believe it's introduced too soon.

@goncalossilva goncalossilva merged commit 580c025 into schacon:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 12, 2011
  1. @grundprinzip

    Fixing print CSS

    grundprinzip authored
    When printing the CSS file paths need to be rewritten
    to match the absolute URIs used in PDFKit. In addition, it
    is required to configure the print media style or 
    the output will be buggy. 
    
    In print mode, the position of the slide needs to be 
    set explicitly to "relavtive" or more explict styling 
    in the slide container will fail in print mode.
Commits on Nov 13, 2011
  1. @grundprinzip

    Templates

    grundprinzip authored
    * allow templates to be configurable
    * allow default template to be not present
    * parsing of slide options
  2. @grundprinzip

    Templates for Statics and PDFs

    grundprinzip authored
    * fixing positioning of a slide when using the onepage.css
  3. @grundprinzip
  4. @grundprinzip
  5. @grundprinzip

    Documentation and Replacements

    grundprinzip authored
    Replaced the ###CONTENT### string with something 
    that does not break in Markdown :). Furthermore now
    multiple replacements can be used in the slides, the 
    documentation is in the README.
  6. @grundprinzip
  7. @grundprinzip

    Removing old init stuff

    grundprinzip authored
  8. @grundprinzip
Commits on Nov 14, 2011
  1. @grundprinzip
  2. @grundprinzip

    Fixing failing tests

    grundprinzip authored
  3. @grundprinzip
Commits on Nov 18, 2011
  1. @grundprinzip
Commits on Nov 19, 2011
  1. @grundprinzip

    Merging schacon/master

    grundprinzip authored
Commits on Nov 21, 2011
  1. @grundprinzip

    Merge branch 'master' into templating

    grundprinzip authored
    Conflicts:
    	lib/showoff.rb
  2. @grundprinzip
This page is out of date. Refresh to see the latest.
View
97 README.rdoc
@@ -84,7 +84,23 @@ the following contents:
That represents two slides, the first contains just a large title, and the
second is faded into view showing the title and three bullets that are then
-incrementally shown. In order for ShowOff to see those slides, your
+incrementally shown. In addition you can configure a certain template for
+this slide overriding the default one:
+
+ !SLIDE[tpl=title]
+
+ # My Presentation #
+
+ !SLIDE bullets incremental
+
+ # Bullet Points #
+
+ * first point
+ * second point
+ * third point
+
+
+In order for ShowOff to see those slides, your
<tt>showoff.json</tt> file needs to look something like this:
{
@@ -95,19 +111,20 @@ incrementally shown. In order for ShowOff to see those slides, your
]
}
-If you have multiple sections in your talk, you can make this json array
-include all the sections you want to show in which order you want to show
-them.
+If you have multiple sections in your talk, you can make this json
+array include all the sections you want to show in which order you
+want to show them. Template configuration is done in
+<tt>showoff.json</tt> as well.
-Instead of a hash, you can use a plain string as an entry in the `sections`
-section of `showoff.json`.
-And if that plain string starts with '#' then it is interpreted not as a
-filename, but as markdown. This is used for inserting interstitial slides
-or notes -- for instance, Alex Chaffee's
-[Ruby Notes](http://github.com/alexch/ruby_notes)
-uses it to insert lab instructions between lecture slide sections, which may
-vary from venue to venue.
+Instead of a hash, you can use a plain string as an entry in the
+`sections` section of `showoff.json`. And if that plain string starts
+with '#' then it is interpreted not as a filename, but as
+markdown. This is used for inserting interstitial slides or notes --
+for instance, Alex Chaffee's [Ruby
+Notes](http://github.com/alexch/ruby_notes) uses it to insert lab
+instructions between lecture slide sections, which may vary from venue
+to venue.
If you want to keep the ability to emit an HTML document from your
Markdown source file -- say, for a TextMate preview or a GitHub rendering
@@ -447,6 +464,62 @@ You'll then need to install a version of wkhtmltopdf available at the {wkhtmltop
Then restart showoff, and navigate to <tt>/pdf</tt> (e.g. http://localhost/pdf) of your presentation and a PDF will be generated with the browser.
+= ShowOff Templates
+
+Templates can come handy if you need more than what you can achieve
+via CSS. To configure templates you'll have to specify them in the
+<tt>showoff.json</tt> by adding an entry called "templates". This
+entry is an object where you can specify as many templates as you
+want. The default template is marked with the "default" key.
+
+ {
+ "name": "Something",
+ "description": "Example Presentation",
+ "templates" : {
+ "default" : "tpl1.tpl",
+ "special" : "tpl2.tpl"
+ },
+ "sections": [
+ {"section":"one"}
+ ]
+ }
+
+If the "default" key is not given, no template will be used for the
+default slide. If you want to apply a certain layout to a slide you
+have to specify it in the slide header:
+
+ !SLIDE[tpl=special]
+ # Header
+
+== Template Commands
+
+You can place content anywhere in your template, but you have to
+explicitly mark the location using a special command:
+
+[~~~CONTENT~~~] is replaced by the slide content
+
+[~~~CURRENT_SLIDE~~~] is replaced by the current slide number
+
+[~~~NUM_SLIDES~~~] is replaced by the total number of slides
+
+[~~~CONFIG:*~~~] is replaced by any value (*) from the
+ <tt>showoff.json</tt> configuration. This can be used to
+ specify an author, venue etc. A simple example would be
+ <tt>~~~CONFIG:author~~~</tt>
+
+
+The usage of these replacements is not limited to templates, but
+anywhere in your slides.
+
+== Template Hints
+
+You can basically put everything you want into templates, but you
+should make sure that the CSS is applied fine. The best way to apply a
+custom layout is to create a container that uses absolute positioning
+and has width and height set to 100% which are then derived from the
+parent slide element.
+
+
= Completion
== ZSH completion
View
5 example/four/01slide.md
@@ -0,0 +1,5 @@
+!SLIDE[tpl=special]
+
+# A Template #
+
+Really? How many slides? -- ~~~NUM_SLIDES~~~
View
7 example/showoff.json
@@ -1,9 +1,14 @@
{
"name": "Something",
"description": "Example Presentation",
+ "author": "Foo Bar John",
+ "templates" : {
+ "special" : "simple.tpl"
+ },
"sections": [
{"section":"one"},
{"section":"two"},
- {"section":"three"}
+ {"section":"three"},
+ {"section":"four"}
]
}
View
2  example/simple.tpl
@@ -0,0 +1,2 @@
+<div class="border">~~~CONFIG:author~~~@~~~CURRENT_SLIDE~~~</div>
+<div class="main">~~~CONTENT~~~<div>
View
117 lib/showoff.rb
@@ -33,6 +33,9 @@ class ShowOff < Sinatra::Application
set :verbose, false
set :pres_dir, '.'
set :pres_file, 'showoff.json'
+ set :page_size, "Letter"
+ set :pres_template, nil
+ set :showoff_config, nil
def initialize(app=nil)
super(app)
@@ -51,6 +54,21 @@ def initialize(app=nil)
if (settings.pres_file)
ShowOffUtils.presentation_config_file = settings.pres_file
end
+
+ # Load configuration for page size and template from the
+ # configuration JSON file
+ if File.exists?(ShowOffUtils.presentation_config_file)
+ showoff_json = JSON.parse(File.read(ShowOffUtils.presentation_config_file))
+ settings.showoff_config = showoff_json
+
+ # Set options for template and page size
+ settings.page_size = showoff_json["page-size"] || "Letter"
+ settings.pres_template = showoff_json["templates"]
+ end
+
+
+ @logger.debug settings.pres_template
+
@cached_image_size = {}
@logger.debug settings.pres_dir
@pres_name = settings.pres_dir.split('/').pop
@@ -96,9 +114,21 @@ def preshow_files
# todo: move more behavior into this class
class Slide
- attr_reader :classes, :text
- def initialize classes = ""
- @classes = ["content"] + classes.strip.chomp('>').split
+ attr_reader :classes, :text, :tpl
+ def initialize( context = "")
+
+ @tpl = "default"
+ @classes = ["content"]
+
+ # Parse the context string for options and content classes
+ if context and context.match(/(\[(.*?)\])?(.*)/)
+
+ options = ShowOffUtils.parse_options($2)
+ @tpl = options["tpl"] if options["tpl"]
+ @classes += $3.strip.chomp('>').split if $3
+
+ end
+
@text = ""
end
def <<(s)
@@ -126,7 +156,8 @@ def process_markdown(name, content, static=false, pdf=false)
until lines.empty?
line = lines.shift
if line =~ /^<?!SLIDE(.*)>?/
- slides << (slide = Slide.new($1))
+ ctx = $1 ? $1.strip : $1
+ slides << (slide = Slide.new(ctx))
else
slide << line
end
@@ -139,6 +170,7 @@ def process_markdown(name, content, static=false, pdf=false)
seq = 1
end
slides.each do |slide|
+ @slide_count += 1
md = ''
content_classes = slide.classes
@@ -151,27 +183,70 @@ def process_markdown(name, content, static=false, pdf=false)
@logger.debug "id: #{id}" if id
@logger.debug "classes: #{content_classes.inspect}"
@logger.debug "transition: #{transition}"
+ @logger.debug "tpl: #{slide.tpl} " if slide.tpl
# create html
md += "<div"
md += " id=\"#{id}\"" if id
md += " class=\"slide\" data-transition=\"#{transition}\">"
+
+
+ template = "~~~CONTENT~~~"
+ # Template handling
+ if settings.pres_template
+ # We allow specifying a new template even when default is
+ # not given.
+ if settings.pres_template.include?(slide.tpl) and
+ File.exists?(settings.pres_template[slide.tpl])
+ template = File.open(settings.pres_template[slide.tpl], "r").read()
+ end
+ end
+
+ # Extract the content of the slide
+ content = ""
if seq
- md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
- seq += 1
+ content += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}/#{seq.to_s}\">\n"
else
- md += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
+ content += "<div class=\"#{content_classes.join(' ')}\" ref=\"#{name}\">\n"
end
sl = Tilt[:markdown].new { slide.text }.render
sl = update_image_paths(name, sl, static, pdf)
- md += sl
- md += "</div>\n"
+ content += sl
+ content += "</div>\n"
+
+ # Apply the template to the slide and replace the key with
+ # content of the slide
+ md += process_content_for_replacements(template.gsub(/~~~CONTENT~~~/, content), @slide_count)
+
+ # Apply other configuration
+
md += "</div>\n"
final += update_commandline_code(md)
final = update_p_classes(final)
+
+ if seq
+ seq += 1
+ end
end
final
end
+ # This method processes the content of the slide and replaces
+ # content markers with their actual value information
+ def process_content_for_replacements(content, seq)
+ result = content.gsub("~~~CURRENT_SLIDE~~~", seq.to_s)
+ # Now check for any kind of options
+ content.scan(/(~~~CONFIG:(.*?)~~~)/).each do |match|
+ result.gsub!(match[0], settings.showoff_config[match[1]]) if settings.showoff_config.key?(match[1])
+ end
+
+ result
+ end
+
+ def process_content_for_all_slides(content, num_slides)
+ content.gsub("~~~NUM_SLIDES~~~", num_slides.to_s)
+ end
+
+
# find any lines that start with a <p>.(something) and turn them into <p class="something">
def update_p_classes(markdown)
markdown.gsub(/<p>\.(.*?) /, '<p class="\1">')
@@ -260,6 +335,7 @@ def update_commandline_code(slide)
end
def get_slides_html(static=false, pdf=false)
+ @slide_count = 0
sections = ShowOffUtils.showoff_sections(settings.pres_dir, @logger)
files = []
if sections
@@ -280,7 +356,7 @@ def get_slides_html(static=false, pdf=false)
end
end
end
- data
+ process_content_for_all_slides(data, @slide_count)
end
def inline_css(csses, pre = nil)
@@ -319,6 +395,11 @@ def index(static=false)
if static
@title = ShowOffUtils.showoff_title
@slides = get_slides_html(static)
+
+ # Identify which languages to bundle for highlighting
+ @languages = []
+ @languages += @slides.scan(/<pre class="(sh_.*?\w)"/).uniq.map{ |w| "sh_lang/#{w[0]}.min.js"}
+
@asset_path = "./"
end
erb :index
@@ -377,9 +458,22 @@ def onepage(static=false)
def pdf(static=true)
@slides = get_slides_html(static, true)
@no_js = false
+
+ # Identify which languages to bundle for highlighting
+ @languages = []
+ @languages += @slides.scan(/<pre class="(sh_.*?\w)"/).uniq.map{ |w| "/sh_lang/#{w[0]}.min.js"}
+
html = erb :onepage
# TODO make a random filename
+ # Process inline css and js for included images
+ # The css uses relative paths for images and we prepend the file url
+ html.gsub!(/url\(([^\/].*?)\)/) do |s|
+ "url(file://#{settings.pres_dir}/#{$1})"
+ end
+
+ # Todo fix javascript path
+
# PDFKit.new takes the HTML and any options for wkhtmltopdf
# run `wkhtmltopdf --extended-help` for a full list of options
kit = PDFKit.new(html, ShowOffUtils.showoff_pdf_options)
@@ -403,6 +497,7 @@ def self.do_static(what)
path = showoff.instance_variable_get(:@root_path)
logger = showoff.instance_variable_get(:@logger)
data = showoff.send(what, true)
+
if data.is_a?(File)
FileUtils.cp(data.path, "#{name}.pdf")
else
@@ -479,7 +574,9 @@ def eval_ruby code
@title = ShowOffUtils.showoff_title
what = params[:captures].first
what = 'index' if "" == what
+
@asset_path = (env['SCRIPT_NAME'] || '').gsub(/\/?$/, '/').gsub(/^\//, '')
+
if (what != "favicon.ico")
data = send(what)
if data.is_a?(File)
View
24 lib/showoff_utils.rb
@@ -1,4 +1,28 @@
class ShowOffUtils
+
+ # Helper method to parse a comma separated options string and stores
+ # the result in a dictionrary
+ #
+ # Example:
+ #
+ # "tpl=hpi,title=Over the rainbow"
+ #
+ # will be stored as
+ #
+ # { "tpl" => "hpi", "title" => "Over the rainbow" }
+ def self.parse_options(option_string="")
+ result = {}
+
+ if option_string
+ option_string.split(",").each do |element|
+ pair = element.split("=")
+ result[pair[0]] = pair.size > 1 ? pair[1] : nil
+ end
+ end
+
+ result
+ end
+
def self.presentation_config_file
@presentation_config_file ||= 'showoff.json'
end
View
2  public/css/onepage.css
@@ -9,6 +9,7 @@
margin-left:auto;
margin-right:auto;
overflow:hidden;
+ position: relative;
border: 1px solid #333;
page-break-after: always
}
@@ -23,6 +24,7 @@
height: 600px;
overflow:hidden;
border: none;
+ position: relative;
page-break-after: always
}
}
View
4 public/css/showoff.css
@@ -372,7 +372,7 @@ a.fg-button { float:left; }
}
.slide .center {
- width: 600px;
+ width: 800px;
height: 600px;
display: table-cell;
text-align: center;
@@ -381,7 +381,7 @@ a.fg-button { float:left; }
#preso, .slide {
background: #fff;
- width: 600px;
+ width: 800px;
height: 600px;
margin-left:auto;
margin-right:auto;
View
5 public/js/onepage.js
@@ -1,5 +1,4 @@
function setupOnePage() {
- sh_highlightDocument('/js/sh_lang/', '.min.js')
-
+ sh_highlightDocument();
centerSlides($("#slides > .slide"))
-}
+}
View
14 public/js/showoff.js
@@ -28,6 +28,8 @@ function setupPreso(load_slides, prefix) {
}
preso_started = true
+
+ // Load slides fetches images
loadSlidesBool = load_slides
loadSlidesPrefix = prefix
loadSlides(loadSlidesBool, loadSlidesPrefix)
@@ -64,7 +66,7 @@ function loadSlides(load_slides, prefix) {
function initializePresentation(prefix) {
// unhide for height to work in static mode
- $("#slides").show();
+ $("#slides").show();
//center slides offscreen
centerSlides($('#slides > .slide'))
@@ -90,7 +92,11 @@ function initializePresentation(prefix) {
slidesLoaded = true
}
setupSlideParamsCheck();
- sh_highlightDocument(prefix+'/js/sh_lang/', '.min.js')
+ try {
+ sh_highlightDocument(prefix+'/js/sh_lang/', '.min.js')
+ } catch(e) {
+ sh_highlightDocument();
+ }
$("#preso").trigger("showoff:loaded");
}
@@ -101,7 +107,7 @@ function centerSlides(slides) {
}
function centerSlide(slide) {
- var slide_content = $(slide).children(".content").first()
+ var slide_content = $(slide).find(".content").first()
var height = slide_content.height()
var mar_top = (0.5 * parseFloat($(slide).height())) - (0.5 * parseFloat(height))
if (mar_top < 0) {
@@ -117,7 +123,7 @@ function setupMenu() {
var menu = new ListMenu()
slides.each(function(s, elem) {
- content = $(elem).children(".content")
+ content = $(elem).find(".content")
shortTxt = $(content).text().substr(0, 20)
path = $(content).attr('ref').split('/')
currSlide += 1
View
6 views/header.erb
@@ -25,6 +25,12 @@
<link type="text/css" href="<%= @asset_path %>css/theme/ui.all.css" media="screen" rel="stylesheet" />
<link type="text/css" href="<%= @asset_path %>css/sh_style.css" rel="stylesheet" >
+<% if @languages %>
+<% @languages.each do |l| %>
+ <script type="text/javascript" src="<%= @asset_path %>js/<%= l %>"></script>
+<% end %>
+<% end %>
+
<% css_files.each do |css_file| %>
<link rel="stylesheet" href="<%= @asset_path %>file/<%= css_file %>" type="text/css"/>
<% end %>
View
4 views/onepage.erb
@@ -15,7 +15,7 @@
<%= inline_css(css_files) %>
<% if !@no_js %>
- <%= inline_js(['jquery-1.4.2.min.js', 'jquery-print.js', 'showoff.js', 'onepage.js', 'sh_main.min.js', 'core.js', 'showoffcore.js'], 'public/js') %>
+ <%= inline_js(['jquery-1.4.2.min.js', 'jquery-print.js', 'showoff.js', 'onepage.js', 'sh_main.min.js', 'core.js', 'showoffcore.js'] + @languages, 'public/js') %>
<script type="text/javascript">
$(document).ready(function() {
setupOnePage()
@@ -25,10 +25,8 @@
</head>
<body>
-
<div id="slides">
<%= @slides %>
</div>
-
</body>
</html>
Something went wrong with that request. Please try again.