Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Things basically work now.

  • Loading branch information...
commit cc4dd4904bb1e7b8176028baabd57935895f4b39 1 parent 3619f40
Benson Kalahar authored

Showing 4 changed files with 661 additions and 16 deletions. Show diff stats Hide diff stats

  1. +570 0 linen.js
  2. +27 6 liveslides.css
  3. +11 3 liveslides.html
  4. +53 7 liveslides.js
570 linen.js
... ... @@ -0,0 +1,570 @@
  1 +// The following line is a function to encapsulate all our helper functions and
  2 +// keep us from polluting the namespace.
  3 +var linen = (function() {
  4 + function lex(doc) {
  5 + function handle_list(b) {
  6 + function lex_list(list_text) {
  7 + var items = list_text.split("\n");
  8 + var list = [];
  9 + for(var i in items) {
  10 + var parts = /^(\*+|#+)(.*)/.exec(items[i]);
  11 + if(parts) {
  12 + types = { "*": "ul", "#": "ol" };
  13 + var lexed = lex_attrs(parts[2]);
  14 + list.push({
  15 + type: types[parts[1][0]],
  16 + indent: parts[1].length,
  17 + content: lexed.content,
  18 + attrs: lexed.attrs
  19 + });
  20 + }
  21 + }
  22 + return list;
  23 + }
  24 +
  25 + var list = lex_list(b);
  26 +
  27 + return {
  28 + type: list[0].type,
  29 + extended: false,
  30 + attrs: [],
  31 + content: list
  32 + };
  33 + }
  34 +
  35 + function lex_table(block) {
  36 + var lines = block.split("\n");
  37 + var ret = [];
  38 + var attrs;
  39 + for(var i in lines) {
  40 + var line = lines[i].replace(/^\|/, "").replace(/\|$/, "");
  41 + var lexed_attrs = lex_attrs(line[0]);
  42 + line[0] = lexed_attrs.content;
  43 + // Hacky, but this is how the grammar seems to be designed
  44 + // TODO: Figure out why this is causing the parser to hang
  45 + if(i == 0) attrs = lexed_attrs.attrs;
  46 + ret.push(line.split("|"));
  47 + }
  48 +
  49 + return {
  50 + type: 'table',
  51 + extended: false,
  52 + attrs: attrs,
  53 + content: ret
  54 + };
  55 + }
  56 +
  57 + function lex_block(block) {
  58 + var res = [];
  59 + var blockType = "";
  60 +
  61 + // First, we look for a block descriptor.
  62 + var i = 0, c = block[0];
  63 + if(c == 'f' && block[i+1] == 'n') {
  64 + // TODO: Implement footnotes properly
  65 + // i.e. grab the footnote id number that follows.
  66 + blockType = "fn";
  67 + i++;
  68 + }
  69 +
  70 + // Lists are pretty different, so we'll treat them completely differently here.
  71 + if(c == '*' || c == '#') {
  72 + var attrs = lex_attrs(block.slice(1));
  73 + if(/^\s+/.exec(attrs.content))
  74 + return handle_list(block)
  75 + }
  76 +
  77 + // Tables are also different, so they get their own function too.
  78 + if(c == '|') { return lex_table(block) }
  79 +
  80 + // Blockquotes and Blocks of code
  81 + if(c == 'b') { blockType = 'b' + block[++i] }
  82 +
  83 + // Headings
  84 + if(c == "h") { blockType = 'h' + block[++i] }
  85 +
  86 + // ps and pres
  87 + if(c == "p") {
  88 + if(block.slice(i, i+3) == 'pre') {
  89 + i += 2;
  90 + blockType = "pre";
  91 + }
  92 + else {
  93 + blockType = "p";
  94 + }
  95 + }
  96 +
  97 + i++;
  98 +
  99 + var obj = lex_attrs(block.slice(i, block.length));
  100 + var match = /^(\.+ )/.exec(obj.content);
  101 +
  102 + function is_valid_blocktype(bt) {
  103 + var blockTypes = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'bq', 'fn', 'p', 'bc', 'pre', 'table', 'ul', 'ol' ];
  104 + for(var i in blockTypes) {
  105 + if(blockTypes[i] == bt)
  106 + return true;
  107 + }
  108 + return false;
  109 + }
  110 +
  111 + if(match && is_valid_blocktype(blockType) ) {
  112 + obj.type = blockType;
  113 + obj.content = obj.content.replace(/^(\.+ )/, "");
  114 + obj.extended = (match[1] == '.. ');
  115 + obj.noBlock = false;
  116 + }
  117 + else {
  118 + obj.type = "p";
  119 + obj.content = block;
  120 + obj.extended = false;
  121 + obj.noBlock = true;
  122 + }
  123 + return obj;
  124 + }
  125 +
  126 + var blocks = doc.split(/\n\n+/);
  127 + var result = [];
  128 + for(var i in blocks) {
  129 + var lexed = lex_block(blocks[i]);
  130 + if(lexed) result.push(lexed);
  131 + }
  132 +
  133 + return result;
  134 + }
  135 +
  136 + function lex_attrs(block) {
  137 + var attrs = [];
  138 + var starts_with_html_tag = block.match(/^<\S+[^<>]*>/) !== null;
  139 +
  140 + for(var i = 0; i < block.length; i++) {
  141 + var c = block[i];
  142 +
  143 + // look for Alignment indicators
  144 + if(c == '>') { attrs.push('>') }
  145 + else if(c == '=') { attrs.push('=') }
  146 + else if(c == '<' && !starts_with_html_tag) {
  147 + // This is a special case, because the <> pair is considered an atom
  148 + if(block[i+1] == '>') {
  149 + i++;
  150 + attrs.push('<>');
  151 + }
  152 + else {
  153 + attrs.push('<');
  154 + }
  155 + }
  156 +
  157 + // Look for classes & ids
  158 + else if(c == '(') {
  159 + var start = i;
  160 + while(block[i++] !== ')' && i < block.length) continue;
  161 + attrs.push(block.slice(start, i--));
  162 + }
  163 +
  164 + // Look for languages
  165 + else if(c == '[') {
  166 + var start = i;
  167 + while(block[i++] !== ']' && i < block.length) continue;
  168 + attrs.push(block.slice(start, i--));
  169 + }
  170 +
  171 + // look for styles
  172 + else if(c == '{') {
  173 + var start = i;
  174 + while(block[i++] !== '}' && i < block.length) continue;
  175 + attrs.push(block.slice(start, i--));
  176 + }
  177 +
  178 + // We've reached the end of what we can recognize, so we bail.
  179 + else {
  180 + break;
  181 + }
  182 + }
  183 +
  184 + return {
  185 + attrs: attrs,
  186 + content: block.slice(i, block.length)
  187 + };
  188 + }
  189 +
  190 +
  191 + function escape_everything(text) {
  192 + // TODO: Escape more things.
  193 + return text.replace(/</g, "&#60;")
  194 + .replace(/>/g, "&#62;")
  195 + .replace(/"/g, "&#34;");
  196 + }
  197 +
  198 + function do_substitutions(text) {
  199 + // This is a simple substitution based system. It might be worth
  200 + // considering implementing this with a real parser, but that does sound
  201 + // like a great deal of work. For the most part, we can fake it with
  202 + // clever regex substitutions, but sometimes there's a danger of
  203 + // substituting something that we ought to be leaving alone (like, for
  204 + // example, a URL with underscores in it).
  205 +
  206 + function make_tag(tag, body, extras) {
  207 + if(typeof extras == "undefined") extras = "";
  208 + else extras = " " + extras;
  209 + var lexed = lex_attrs(body);
  210 + var attrs = parse_attrs(lexed.attrs);
  211 + return "<" + tag + html_attrs({ attrs: attrs }) + extras + ">" + lexed.content + "</"+tag+">";
  212 + }
  213 +
  214 + function make_link(content, url) {
  215 + if(url[url.length - 1] == '.')
  216 + return make_tag('a', content, 'href="' + url.slice(0, url.length -1) + '"') + ".";
  217 + else
  218 + return make_tag('a', content, 'href="' + url + '"');
  219 + }
  220 +
  221 + function cleanup(body, count) {
  222 + if(typeof count == 'undefined')
  223 + count = 1;
  224 + return body.slice(count, body.length - count);
  225 + }
  226 +
  227 + // We do quotes first because they are problematic.
  228 + return text.replace(/(\s)"(.*?)"([^:])/g, "$1&#8220;$2&#8221;$3")
  229 +
  230 + // Links
  231 + .replace(/"([^"]+)":(http\S+)/g, function(_, content, url) { return make_link(content, url) })
  232 +
  233 + // Images
  234 + .replace(/!\b([^! \n]+)\b!:(http\S+)/g, function(_, content, url) {
  235 + return make_tag('a',
  236 + ' ' + make_tag('img', "", "src=\"" + content + "\""),
  237 + 'href="' + url + '"')
  238 + })
  239 + .replace(/!([^! \n]+)!/g, function(content) { return make_tag('img', "", "src=\"" + cleanup(content) + "\"") })
  240 +
  241 + // Punctuation
  242 + .replace(/--/g, "&#8212;")
  243 + .replace(/\n/g, "<br/>")
  244 + .replace(/(\b)'([^']*)'(\b)/g, '$1&#8216;$2&#8217;$3')
  245 + .replace(/'/g, "&#8217;")
  246 + .replace(/ - /g, " &#8211; ")
  247 + .replace(/\.\.\./g, "&#8230;")
  248 + .replace(/\(r\)/g, "&#174;")
  249 + .replace(/\(tm\)/g, "&#8482;")
  250 + .replace(/\(c\)/g, "&#169;")
  251 + // TODO: dimension sign, if I can ever be bothered to care
  252 +
  253 + // Acronyms
  254 + .replace(/([A-Z]{2,})\(([^)]+)\)/g, "<span class=\"caps\"><acronym title=\"$2\">$1</acronym></span>")
  255 + .replace(/([A-Z]{2,})/g, function(content) { return make_tag("span", content + " ", "class=\"caps\"") })
  256 +
  257 + // Citations
  258 + .replace(/\?{2}([^\?]+)\?{2}/g, function(content) { return make_tag("cite", cleanup(content)) })
  259 +
  260 + // Spans
  261 + .replace(/%([^%]+)%/g, function(content) { return make_tag("span", cleanup(content)) })
  262 +
  263 + // Code
  264 + .replace(/(@[^@]+@)/g, function(content) { return make_tag("code", cleanup(content)) })
  265 +
  266 + // Bolding
  267 + .replace(/\*([^\*]+)\*/g, function(content) { return make_tag("strong", cleanup(content)) })
  268 + .replace(/\*\*([^\*]+)\*\*/g, function(content) { return make_tag("b", cleanup(content)) })
  269 +
  270 + // Italics
  271 + .replace(/\b_([^_]+)_\b/g, function(content) { return make_tag("em", cleanup(content)) })
  272 + .replace(/\b__([^_]+)__\b/g, function(content) { return make_tag("i", cleanup(content)) })
  273 +
  274 + // Insertions & Deletions
  275 + .replace(/\s\+([^\+]+)\+\s/g, function(content) { return ' ' + make_tag("ins", cleanup(content, 2)) + ' ' })
  276 + .replace(/\s-([^-]+)-\s/g, function(content) { return ' ' + make_tag("del", cleanup(content, 2)) + ' ' })
  277 +
  278 + // Insertions & Deletions
  279 + .replace(/\^([^\^]+)\^/g, function(content) { return make_tag("sup", cleanup(content)) })
  280 + .replace(/~([^~]+)~/g, function(content) { return make_tag("sub", cleanup(content)) });
  281 + }
  282 +
  283 +
  284 + function parse(doc) {
  285 + function parse_list(list) {
  286 + var items = list.content;
  287 + var prevIndent = 1;
  288 + var ret = [];
  289 + var current = ret;
  290 + var stack = [];
  291 +
  292 + for(var i = 0; i < items.length; i++) {
  293 + var it = items[i];
  294 + it.content = do_substitutions(it.content);
  295 + it.attrs = parse_attrs(it.attrs);
  296 +
  297 + // Increase nesting if needed
  298 + if(it.indent > prevIndent) {
  299 + for(var j = 0; j < it.indent - prevIndent; j++) {
  300 + // Save current for later
  301 + stack.push(current);
  302 + var newList = [];
  303 + current.push(newList);
  304 + current = newList;
  305 + }
  306 + }
  307 +
  308 + // Decrease nesting if needed
  309 + if(it.indent < prevIndent) {
  310 + for(var j = 0; j < prevIndent - it.indent; j++)
  311 + current = stack.pop();
  312 + }
  313 +
  314 + current.push(it);
  315 + prevIndent = it.indent;
  316 + }
  317 +
  318 + return {
  319 + type: list.type,
  320 + content: ret,
  321 + attrs: ret[0].attrs,
  322 + classes: "",
  323 + id: "",
  324 + lang: "",
  325 + style: "",
  326 + alignment: ""
  327 + };
  328 + }
  329 +
  330 + function parse_table(block) {
  331 + return {
  332 + type: "table",
  333 + attrs: parse_attrs(block.attrs),
  334 + content: block.content,
  335 + classes: "",
  336 + id: "",
  337 + lang: "",
  338 + style: "",
  339 + alignment: ""
  340 + };
  341 + }
  342 +
  343 +
  344 +
  345 + function parse_block(block) {
  346 + var obj;
  347 +
  348 + // Some special stuff for lists
  349 + if(block.type == "ol" || block.type == "ul")
  350 + obj = parse_list(block);
  351 + // Some special stuff for tables
  352 + else if(block.type == "table")
  353 + obj = parse_table(block);
  354 + else if(block.type == "bc")
  355 + obj = {
  356 + type: block.type,
  357 + content: escape_everything(block.content),
  358 + attrs: parse_attrs(block.attrs)
  359 + };
  360 + // The general case
  361 + else {
  362 + obj = {
  363 + type: block.type,
  364 + content: do_substitutions(block.content),
  365 + attrs: parse_attrs(block.attrs)
  366 + };
  367 + }
  368 +
  369 + return obj;
  370 + }
  371 +
  372 + // TODO: Handle the "extended" attribute, which indicates extended blocks.
  373 +
  374 + var res = [];
  375 + for(var i in doc)
  376 + res.push(parse_block(doc[i]));
  377 + return res;
  378 + }
  379 +
  380 + function parse_attrs(attrs) {
  381 + var ret = {
  382 + classes: "",
  383 + id: "",
  384 + lang: "",
  385 + style: "",
  386 + alignment: ""
  387 + };
  388 + for(var i in attrs) {
  389 + var atom = attrs[i];
  390 +
  391 + // Look for classes and/or an id
  392 + if(atom[0] == '(') {
  393 + // Remove parens and split on #, for a quick and dirty parse
  394 + var parts = atom.replace(/^\(/, "").replace(/\)$/, "").split("#");
  395 +
  396 + // Add to classes
  397 + if(ret.classes == "")
  398 + ret.classes = parts[0];
  399 + else
  400 + ret.classes = ret.classes + " " + parts[0];
  401 +
  402 + // Set ID if it hasn't already been set
  403 + if(parts.length > 1 && ret.id == "")
  404 + ret.id = parts[1];
  405 + }
  406 +
  407 + // Look for a language
  408 + else if(atom[0] == '[') {
  409 + ret.lang = atom.replace(/^\[/, "").replace(/\]$/, "");
  410 + }
  411 +
  412 + // Look for a style
  413 + else if(atom[0] == '{') {
  414 + ret.style = atom.replace(/^\{/, "").replace(/\}$/, "");
  415 + }
  416 +
  417 + // Look for alignment
  418 + else {
  419 + // Shortcut out if we already have an alignment set
  420 + if(ret.alignment == "") {
  421 + if(atom == '>') ret.alignment = "right";
  422 + if(atom == '<') ret.alignment = "left";
  423 + if(atom == '=') ret.alignment = "center";
  424 + if(atom == '<>') ret.alignment = "justify";
  425 + }
  426 + }
  427 + }
  428 + return ret;
  429 + }
  430 +
  431 +
  432 + function html_attrs(obj) {
  433 + var attrs = " ";
  434 + var attrObj = obj.attrs;
  435 + if(attrObj.classes)
  436 + attrs += "class=\"" + attrObj.classes + "\" ";
  437 +
  438 + if(attrObj.id)
  439 + attrs += "id=\"" + attrObj.id + "\" ";
  440 +
  441 + if(attrObj.align)
  442 + attrObj.style = "text-align: " + attrObj.align + "; " + attrObj.style
  443 +
  444 + if(attrObj.style)
  445 + attrs += "style=\"" + attrObj.style + "\" ";
  446 +
  447 + if(attrObj.lang)
  448 + attrs += "lang=\"" + attrObj.lang + "\" ";
  449 +
  450 + return attrs.slice(0, attrs.length - 1);
  451 + }
  452 +
  453 +
  454 + function generate_code(blocks) {
  455 + function generate_block(block) {
  456 +
  457 +
  458 + // Generate different kinds of code blocks:
  459 + function heading(b) {
  460 + return "<" + b.type + html_attrs(b) + ">" +
  461 + b.content +
  462 + "</" + b.type + ">";
  463 + }
  464 + function blockquote(b) {
  465 + return "<blockquote" + html_attrs(b) + "><p>" +
  466 + b.content +
  467 + "</p></blockquote>";
  468 + }
  469 + function footnote(b) {
  470 + // TODO: Handle footnote numbers properly
  471 + return "<p" + html_attrs(b) + "><sup>" + b.number + "</sup>" +
  472 + b.content +
  473 + "</p>"
  474 + }
  475 + function paragraph(b) {
  476 + return "<p" + html_attrs(b) + ">" +
  477 + b.content +
  478 + "</p>";
  479 + }
  480 + function blockcode(b) {
  481 + return "<pre" + html_attrs(b) + "><code>" +
  482 + b.content +
  483 + "</code></pre>";
  484 + }
  485 + function preformatted(b) {
  486 + return "<pre" + html_attrs(b) + ">" +
  487 + b.content +
  488 + "</pre>";
  489 + }
  490 + function table(b) {
  491 + var ret = "<table" + html_attrs(b) + ">";
  492 + for(var i in b.content) {
  493 + var line = b.content[i];
  494 + ret += "<tr>";
  495 + for(var j in line) {
  496 + ret += "<td>" + line[j] + "</td>";
  497 + }
  498 + ret += "</tr>";
  499 + }
  500 + ret += "</table>";
  501 + return ret;
  502 + }
  503 +
  504 + function list(listObj) {
  505 + function list_generator(items) {
  506 + var ret = "<" + items[0].type + html_attrs(items[0]) + ">";
  507 +
  508 + for(var i in items) {
  509 + var it = items[i];
  510 + if(it.type) {
  511 + ret += "<li>" + it.content;
  512 + // Check for a sublist
  513 + if(items[i+1] && items[i+1].type == undefined)
  514 + ret += list_generator(items[++i]);
  515 + ret += "</li>";
  516 + }
  517 + else {
  518 + ret += list_generator(it);
  519 + }
  520 + }
  521 + ret += "</" + (items[0].type) + ">";
  522 +
  523 + return ret;
  524 + }
  525 +
  526 + return list_generator(listObj.content);
  527 + }
  528 +
  529 + switch(block.type) {
  530 + case "h1":
  531 + case "h2":
  532 + case "h3":
  533 + case "h4":
  534 + case "h5":
  535 + case "h6":
  536 + return heading(block);
  537 + case "bq":
  538 + return blockquote(block);
  539 + case "fn":
  540 + return footnote(block);
  541 + case "p":
  542 + return paragraph(block);
  543 + case "bc":
  544 + return blockcode(block);
  545 + case "pre":
  546 + return preformatted(block);
  547 + case "table":
  548 + return table(block);
  549 + case "ul":
  550 + case "ol":
  551 + return list(block);
  552 + }
  553 + }
  554 +
  555 + ret = "";
  556 + for(var i in blocks)
  557 + ret += generate_block(blocks[i]) + "\n";
  558 + return ret;
  559 + }
  560 +
  561 + // And finally, we'll export this outside this crazy scope.
  562 + return function(textile) { return generate_code(parse(lex(textile))) };
  563 +})();
  564 +
  565 +// Export to let node.js and other CommonJS
  566 +// compatible frameworks see our code:
  567 +if(typeof exports != 'undefined') {
  568 + exports.linen = linen;
  569 + exports.textilize = linen;
  570 +}
33 liveslides.css
@@ -2,22 +2,19 @@
2 2 div#index {
3 3 float: right;
4 4 width: 200px;
5   - -webkit-border-radius: 7px;
6   - -moz-border-radius: 7px;
7   - border: solid black 3px;
8 5 }
9 6
10 7 li.slide-entry.current {
11   - border: solid green 2px;
  8 + background: #ffb;
12 9 }
13 10
14 11 li.slide-entry {
15 12 list-style-type: none;
16   - margin: 10px;
  13 + padding: 5px 15px;
17 14 }
18 15
19 16 #index ul {
20   - margin: 10px;
  17 + margin: 0;
21 18 padding: 0;
22 19 }
23 20
@@ -27,4 +24,28 @@ h1 {
27 24
28 25 div#main-slide {
29 26 font-size: +3;
  27 + margin: 0 25px;
  28 +}
  29 +
  30 +a#new-slide {
  31 + margin: 15px 20px;
  32 + display: block;
  33 +}
  34 +
  35 +a, a:visited, a:active, a:hover {
  36 + text-decoration: none;
  37 + color: #333;
  38 +}
  39 +
  40 +a:hover {
  41 + color: #555;
  42 +}
  43 +
  44 +#title-box {
  45 + width: 80%;
  46 +}
  47 +
  48 +#body-box {
  49 + width: 80%;
  50 + height: 100%;
30 51 }
14 liveslides.html
@@ -15,6 +15,7 @@
15 15 <li class="slide-entry{{is_current}}" id="{{_id}}">{{title}}</li>
16 16 {{/each}}
17 17 </ul>
  18 + <a href="#" id="new-slide">another!</a>
18 19 </div>
19 20 </template>
20 21
@@ -22,11 +23,18 @@
22 23 {{#with slide}}
23 24 <div id="main-slide">
24 25 <div class="slide">
25   - <h1>{{title}}</h1>
26   - <div class="slide-body">
27   - {{body}}
  26 + <h1 id="slide-title">{{title}}</h1>
  27 + <div id="slide-body">
  28 + {{{html_body}}}
28 29 </div>
29 30 </div>
30 31 </div>
31 32 {{/with}}
32 33 </template>
  34 +
  35 +<template name="edit_slide">
  36 + <div id="edit-slide">
  37 + <div><input type="text" id="title-box" value="{{title}}" /></div>
  38 + <div><textarea rows="25" cols="80" id="body-box">{{body}}</textarea></div>
  39 + </div>
  40 +</template>
60 liveslides.js
... ... @@ -1,6 +1,23 @@
1 1 var slides = new Meteor.Collection("slides");
2 2
  3 +function set_current_slide(id) {
  4 + slides.update({}, { $set: { current: false } }, { multi: true });
  5 + slides.update({ _id: id }, { $set: { current: true } });
  6 +}
  7 +
  8 +function current_slide() {
  9 + return slides.findOne({ current: true });
  10 +}
  11 +
  12 +function add_slide() {
  13 + set_current_slide(slides.insert({ title: "New Slide", body: "Edit me!" }));
  14 +}
  15 +
  16 +
3 17 if (Meteor.is_client) {
  18 + //
  19 + // Template Helpers
  20 + //
4 21 Template.slide_list.slides = function () {
5 22 return slides.find();
6 23 };
@@ -10,18 +27,47 @@ if (Meteor.is_client) {
10 27 };
11 28
12 29 Template.current_slide.slide = function() {
13   - return slides.findOne({ current: true }) || false;
  30 + var slide = current_slide();
  31 + if(slide) slide.html_body = linen(slide.body);
  32 + return slide || false;
14 33 };
15 34
16   - $('#index ul li').live('click', function(e) {
17   - console.log(e.currentTarget.id);
18   - slides.update({}, { $set: { current: false } }, { multi: true });
19   - slides.update({ _id: e.currentTarget.id }, { $set: { current: true } });
20   - });
  35 + //
  36 + // Event handlers
  37 + //
  38 + function save_slide() {
  39 + var new_values = {
  40 + title: $('#title-box').attr('value'),
  41 + body: $('#body-box').attr('value')
  42 + };
  43 + slides.update({ _id: current_slide()._id }, { $set: new_values } );
  44 + }
  45 + function start_editing() {
  46 + $('#main-slide').replaceWith(Template.edit_slide(current_slide()));
  47 + $('#title-box, #body-box').blur(save_slide);
  48 + }
  49 +
  50 + Template.slide_list.events = {
  51 + 'click #index ul li': function(e) { set_current_slide(e.currentTarget.id); },
  52 + 'click #new-slide': add_slide
  53 + };
  54 + Template.current_slide.events = {
  55 + 'click #slide-title': function(e) {
  56 + start_editing();
  57 + $("#title-box").focus();
  58 + },
  59 + 'click #slide-body': function(e) {
  60 + start_editing();
  61 + $("#body-box").focus();
  62 + },
  63 + };
21 64 }
22 65
23 66 if (Meteor.is_server) {
24 67 Meteor.startup(function () {
25   - // code to run on server at startup
  68 + if(!current_slide()) {
  69 + // Set first slide to be current
  70 + slides.update({}, { $set: { current: true } });
  71 + }
26 72 });
27 73 }

0 comments on commit cc4dd49

Please sign in to comment.
Something went wrong with that request. Please try again.