1<?php 2/** 3 * Renderer for XHTML output 4 * 5 * @author Emmanuel Klinger <emmanuel.klinger@gmail.com> 6 * @author Ottmar Gobrecht <ottmar.gobrecht@gmail.com> 7 */ 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11// we inherit from the XHTML renderer instead directly of the base renderer 12require_once DOKU_INC.'inc/parser/xhtml.php'; 13 14/** 15 * The Renderer 16 */ 17class renderer_plugin_revealjs extends Doku_Renderer_xhtml { 18 var $base = ''; 19 var $tpl = ''; 20 var $slide_indicator_headers = true; 21 var $slide_number = 0; 22 var $slide_open = false; 23 var $column_open = false; 24 var $notes_open = false; 25 var $quote_open = false; 26 var $fragment_list_open = false; 27 var $no_fragment_list_open = false; 28 var $fragment_style = ''; 29 var $next_slide_background_color = ''; 30 var $next_slide_background_image = ''; 31 var $next_slide_background_size = ''; 32 var $next_slide_background_position = ''; 33 var $next_slide_background_repeat = ''; 34 var $next_slide_background_transition = ''; 35 var $next_slide_transition = ''; 36 var $next_slide_transition_speed = ''; 37 var $next_slide_no_footer = false; 38 39 /** 40 * the format we produce 41 */ 42 function getFormat(){ 43 // this should be 'revealjs' usally, but we inherit from the xhtml renderer 44 // and produce XHTML as well, so we can gain magically compatibility 45 // by saying we're the 'xhtml' renderer here. 46 return 'xhtml'; 47 } 48 49 50 /** 51 * Initialize the rendering 52 */ 53 function document_start() { 54 global $ID; 55 global $conf; 56 global $lang; 57 58 // merge URL params into plugin conf - changing params direct in the URL is only working, when page is not cached (~~NOCACHE~~) 59 if (count($_GET)){ 60 if (!array_key_exists('plugin', $conf)) { 61 $conf['plugin'] = array('revealjs' => $_GET); 62 } 63 elseif (!array_key_exists('revealjs', $conf['plugin'])) { 64 $conf['plugin']['revealjs'] = $_GET; 65 } 66 else { 67 $conf['plugin']['revealjs'] = array_merge($conf['plugin']['revealjs'], $_GET); 68 } 69 } 70 71 // call the parent 72 parent::document_start(); 73 74 // store the content type headers in metadata 75 $headers = array( 76 'Content-Type' => 'text/html; charset=utf-8' 77 ); 78 79 p_set_metadata($ID,array('format' => array('revealjs' => $headers) )); 80 $this->base = DOKU_BASE.'lib/plugins/revealjs/'; 81 $this->doc = '<!DOCTYPE html> 82<html lang="'.$conf['lang'].'" dir="'.$lang['direction'].'"> 83<head> 84 <meta charset="utf-8"> 85 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 86 87 <title>'.tpl_pagetitle($ID, true).'</title> 88 89 <meta name="apple-mobile-web-app-capable" content="yes" /> 90 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> 91 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui"> 92 93 <link rel="stylesheet" href="'.$this->base.'css/reveal.css"> 94 <link rel="stylesheet" href="'.$this->base.'css/theme/'.$this->getConf('theme').'.css" id="theme"> 95 <link rel="stylesheet" href="'.$this->base.'doku-substitutes.css"> 96 97 <!-- Code syntax highlighting --> 98 <link rel="stylesheet" href="'.$this->base.'lib/css/zenburn.css"> 99 100 ' . ($this->getConf('show_image_borders') ? 101 '<!-- Image borders are switched on -->' : 102 '<!-- Image borders are switched off --> 103 <style>.reveal img { border: none !important; box-shadow: none !important; background: none !important; } .level1, .level2, .level3, .level4, .level5 {min-height:300px;}</style>') . ' 104 105 <!-- Printing and PDF exports --> 106 <script> 107 var link = document.createElement( \'link\' ); 108 link.rel = \'stylesheet\'; 109 link.type = \'text/css\'; 110 link.href = window.location.search.match( /print-pdf/gi ) ? \''.$this->base.'css/print/pdf.css\' : \''.$this->base.'css/print/paper.css\'; 111 document.getElementsByTagName( \'head\' )[0].appendChild( link ); 112 </script> 113 114 <!--[if lt IE 9]> 115 <script src='.$this->base.'"lib/js/html5shiv.js"></script> 116 <![endif]--> 117</head> 118<body> 119 <div class="reveal"> 120 121 <!-- Any section element inside of this container is displayed as a slide --> 122 <div class="slides"> 123 124<!-- page content start ------------------------------------------------------->'; 125 } 126 127 128 /** 129 * Closes the document 130 */ 131 function document_end(){ 132 // we don't care for footnotes and toc 133 // but cleanup is nice 134 $this->doc = preg_replace('#<p>\s*</p>#','',$this->doc); 135 136 // cleanup quotes - too much whitspace from declaration: "> valid quote" 137 $this->doc = preg_replace('/<blockquote>“\s*/u','<blockquote>“',$this->doc); 138 139 // close maybe open slide and column 140 $this->close_slide_container(); 141 142 $show_controls = $this->getConf('controls') ? 'true' : 'false'; 143 $show_progress_bar = $this->getConf('show_progress_bar') ? 'true' : 'false'; 144 $size = explode("x", $this->getConf('size')); 145 $auto_slide = $this->getConf('auto_slide'); 146 $loop = $this->getConf('loop') ? 'true' : 'false'; 147 $this->doc .= ' 148<!-- page content stop --------------------------------------------------------> 149 150 </div><!-- slides --> 151 </div><!-- reveal --> 152 153 <script src="'.$this->base.'lib/js/head.min.js"></script> 154 <script src="'.$this->base.'js/reveal.js"></script> 155 <script> 156 Reveal.initialize({ 157 width: '. ($size[0] ? $size[0] : 960) .', 158 height: '. ($size[1] ? $size[1] : 700) .', 159 controls: '. $show_controls .', 160 progress: '. $show_progress_bar .', 161 history: true, 162 center: true, 163 autoSlide: '. $auto_slide .', 164 loop: '. $loop .', 165 transition: \''.$this->getConf('transition').'\', // none/fade/slide/convex/concave/zoom 166 math: { 167 mathjax: \'//cdn.mathjax.org/mathjax/latest/MathJax.js\', 168 config: \'TeX-AMS_HTML-full\' // See http://docs.mathjax.org/en/latest/config-files.html 169 }, 170 dependencies: [ 171 { src: \''.$this->base.'lib/js/classList.js\', condition: function() { return !document.body.classList; } }, 172 { src: \''.$this->base.'plugin/markdown/marked.js\', condition: function() { return !!document.querySelector( \'[data-markdown]\' ); } }, 173 { src: \''.$this->base.'plugin/markdown/markdown.js\', condition: function() { return !!document.querySelector( \'[data-markdown]\' ); } }, 174 { src: \''.$this->base.'plugin/highlight/highlight.js\', async: true, condition: function() { return !!document.querySelector( \'pre code\' ); }, callback: function() { hljs.initHighlightingOnLoad(); } }, 175 { src: \''.$this->base.'plugin/zoom-js/zoom.js\', async: true, condition: function() { return !!document.body.classList; } }, 176 { src: \''.$this->base.'plugin/notes/notes.js\', async: true, condition: function() { return !!document.body.classList; } }, 177 // MathJax 178 { src: \''.$this->base.'plugin/math/math.js\', async: true } 179 ] 180 }); 181 </script> 182</body> 183</html>'; 184 } 185 186 /** 187 * Creates a slide container (section), possibly containing nested slides (sections) 188 */ 189 function open_slide_container () { 190 $this->close_slide_container(); 191 $this->doc .= '<section>'.DOKU_LF; 192 $this->column_open = true; 193 } 194 195 /** 196 * Creates a slide (section) 197 */ 198 function open_slide () { 199 $this->close_slide(); 200 $this->doc .= ' <section'; 201 if ($this->next_slide_background_color) { 202 $this->doc .= ' data-background-color="'.$this->next_slide_background_color.'"'; 203 $this->next_slide_background_color = ''; 204 } 205 if ($this->next_slide_background_image) { 206 $this->doc .= ' data-background-image="'.$this->next_slide_background_image.'"'; 207 $this->next_slide_background_image = ''; 208 } 209 if ($this->next_slide_background_size) { 210 $this->doc .= ' data-background-size="'.$this->next_slide_background_size.'"'; 211 $this->next_slide_background_size = ''; 212 } 213 if ($this->next_slide_background_position) { 214 $this->doc .= ' data-background-position="'.$this->next_slide_background_position.'"'; 215 $this->next_slide_background_position = ''; 216 } 217 $data['background_position']; 218 if ($this->next_slide_background_repeat) { 219 $this->doc .= ' data-background-repeat="'.$this->next_slide_background_repeat.'"'; 220 $this->next_slide_background_repeat = ''; 221 } 222 if ($this->next_slide_background_transition) { 223 $this->doc .= ' data-background-transition="'.$this->next_slide_background_transition.'"'; 224 $this->next_slide_background_transition = ''; 225 } 226 if ($this->next_slide_transition) { 227 $this->doc .= ' data-transition="'.$this->next_slide_transition.'"'; 228 $this->next_slide_transition = ''; 229 } 230 if ($this->next_slide_transition_speed) { 231 $this->doc .= ' data-transition-speed="'.$this->next_slide_transition_speed.'"'; 232 $this->next_slide_transition_speed = ''; 233 } 234 if ($this->next_slide_no_footer) { 235 $this->doc .= ' data-state="no-footer"'; 236 $this->next_slide_no_footer = false; 237 } 238 $this->doc .= '>'.DOKU_LF; 239 240 // mark slide as open 241 $this->slide_open = true; 242 } 243 244 /** 245 * Closes a slide container (section) 246 */ 247 function close_slide_container () { 248 $this->close_slide(); 249 if ($this->column_open) { 250 $this->doc .= '</section>'.DOKU_LF; 251 $this->column_open = false; 252 } 253 } 254 255 /** 256 * Closes a slide (section) 257 */ 258 function close_slide () { 259 if ($this->slide_open) { 260 $this->doc .= ' </section>'.DOKU_LF; 261 $this->slide_open = false; 262 } 263 } 264 265 /** 266 * DokuWiki sections are not used on a slideshow - so we redeclare it here only 267 */ 268 function section_open($level) {} 269 function section_close() {} 270 271 /** 272 * Throw away footnote 273 */ 274 function footnote_close() { 275 // recover footnote into the stack and restore old content 276 $footnote = $this->doc; 277 $this->doc = $this->store; 278 $this->store = ''; 279 } 280 281 /** 282 * No acronyms in a presentation 283 */ 284 function acronym($acronym){ 285 $this->doc .= $this->_xmlEntities($acronym); 286 } 287 288 /** 289 * Start a table 290 * 291 * @param int $maxcols maximum number of columns 292 * @param int $numrows NOT IMPLEMENTED 293 * @param int $pos byte position in the original source 294 */ 295 function table_open($maxcols = null, $numrows = null, $pos = null, $classes = NULL) { 296 // initialize the row counter used for classes 297 $this->_counter['row_counter'] = 0; 298 $class = 'table'; 299 if($pos !== null) { 300 301 $sectionEditStartData = ['target' => 'table']; 302 if (!defined('SEC_EDIT_PATTERN')) { 303 // backwards-compatibility for Frusterick Manners (2017-02-19) 304 $sectionEditStartData = 'table'; 305 } 306 307 $class .= ' '.$this->startSectionEdit($pos, $sectionEditStartData); 308 } 309 $this->doc .= '<table class="doku_revealjs_table">'. 310 DOKU_LF; 311 } 312 313 /** 314 * Close a table 315 * 316 * @param int $pos byte position in the original source 317 */ 318 function table_close($pos = null) { 319 $this->doc .= '</table>'.DOKU_LF; 320 } 321 322 /** 323 * Open a table header 324 */ 325 function tablethead_open() { 326 $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; 327 } 328 329 /** 330 * Close a table header 331 */ 332 function tablethead_close() { 333 $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; 334 } 335 336 /** 337 * Open a table row 338 */ 339 function tablerow_open($classes = NULL) { 340 $this->doc .= DOKU_TAB.'<tr>'.DOKU_LF.DOKU_TAB.DOKU_TAB; 341 } 342 343 /** 344 * Close a table row 345 */ 346 function tablerow_close() { 347 $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; 348 } 349 350 /** 351 * Open a table header cell 352 * 353 * @param int $colspan 354 * @param string $align left|center|right 355 * @param int $rowspan 356 */ 357 function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = NULL) { 358 $class = 'class="col'.$this->_counter['cell_counter']++; 359 if(!is_null($align)) { 360 $class .= ' '.$align.'align'; 361 } 362 $class .= '"'; 363 $this->doc .= '<th '; 364 if($colspan > 1) { 365 $this->_counter['cell_counter'] += $colspan - 1; 366 $this->doc .= ' colspan="'.$colspan.'"'; 367 } 368 if($rowspan > 1) { 369 $this->doc .= ' rowspan="'.$rowspan.'"'; 370 } 371 $this->doc .= '>'; 372 } 373 374 /** 375 * Close a table header cell 376 */ 377 function tableheader_close() { 378 $this->doc .= '</th>'; 379 } 380 381 /** 382 * Open a table cell 383 * 384 * @param int $colspan 385 * @param string $align left|center|right 386 * @param int $rowspan 387 */ 388 function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = NULL) { 389 $class = 'class="col'.$this->_counter['cell_counter']++; 390 if(!is_null($align)) { 391 $class .= ' '.$align.'align'; 392 } 393 $class .= '"'; 394 $this->doc .= '<td '; 395 if($colspan > 1) { 396 $this->_counter['cell_counter'] += $colspan - 1; 397 $this->doc .= ' colspan="'.$colspan.'"'; 398 } 399 if($rowspan > 1) { 400 $this->doc .= ' rowspan="'.$rowspan.'"'; 401 } 402 $this->doc .= '>'; 403 } 404 405 /** 406 * Close a table cell 407 */ 408 function tablecell_close() { 409 $this->doc .= '</td>'; 410 } 411 412 413 /** 414 * Open a list item 415 * 416 * @param int $level the nesting level 417 * 418 * Default: build list item per item. 419 * This is called "fragment" in reveal.js 420 */ 421 function listitem_open($level, $node=false) { 422 if( !$this->notes_open 423 && !$this->no_fragment_list_open 424 && ($this->getConf('build_all_lists') || $this->fragment_list_open) ) { 425 $this->doc .= '<li class="fragment' . ($this->fragment_style ? ' '.$this->fragment_style : '') . '">'; 426 } 427 else { 428 $this->doc .= '<li>'; 429 } 430 } 431 432 433 /** 434 * Start a block quote 435 */ 436 function quote_open() { 437 if (!$this->quote_open) { 438 $this->doc .= '<blockquote>“'; 439 $this->quote_open = true; 440 } 441 } 442 443 /** 444 * Stop a block quote 445 */ 446 function quote_close() { 447 if ($this->quote_open) { 448 $this->doc .= '”</blockquote>'.DOKU_LF; 449 $this->quote_open = false; 450 } 451 } 452 453 454 /** 455 * Don't use Geshi. Overwrite the Geshi function. 456 * @author Emmanuel Klinger 457 * @author Andreas Gohr <andi@splitbrain.org> 458 * @param string $type code|file 459 * @param string $text text to show 460 * @param string $language programming language to use for syntax highlighting 461 * @param string $filename file path label 462 * @param string $options highlight options - not used 463 */ 464 function _highlight($type, $text, $language = null, $filename = null, $options = null) { 465 global $ID; 466 global $lang; 467 468 if($filename) { 469 // add icon 470 list($ext) = mimetype($filename, false); 471 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 472 $class = 'mediafile mf_'.$class; 473 474 $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; 475 $this->doc .= '<dt><a href="'.exportlink($ID, 'code', array('codeblock' => $this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">'; 476 $this->doc .= hsc($filename); 477 $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; 478 } 479 480 if($text{0} == "\n") { 481 $text = substr($text, 1); 482 } 483 if(substr($text, -1) == "\n") { 484 $text = substr($text, 0, -1); 485 } 486 487 if(is_null($language)) { 488 //@author Emmanuel: This line is changed from the original 489 $this->doc .= '<pre><code>'.$this->_xmlEntities($text).'</code></pre>'.DOKU_LF; 490 } else { 491 //@author Emmanuel: This line is changed from the original 492 $this->doc .= '<pre><code class="'.$language.'">'.$this->_xmlEntities($text).'</code></pre>'.DOKU_LF; 493 } 494 495 if($filename) { 496 $this->doc .= '</dd></dl>'.DOKU_LF; 497 } 498 499 $this->_codeblock++; 500 } 501 502} 503