Browse Source

first version of multi-step code highlights

Hakim El Hattab 6 years ago
parent
commit
7eb2cec6b6
5 changed files with 166 additions and 19 deletions
  1. 10 0
      css/reveal.css
  2. 12 0
      css/reveal.scss
  3. 2 2
      demo.html
  4. 2 2
      index.html
  5. 140 15
      plugin/highlight/highlight.js

+ 10 - 0
css/reveal.css

@@ -1456,6 +1456,16 @@ body {
 .reveal .hljs[data-line-numbers]:not([data-line-numbers=""]) tr:not(.highlight-line) {
   opacity: 0.4; }
 
+.reveal .hljs .highlight-line .hljs-ln-numbers {
+  font-weight: 600; }
+
+.reveal .hljs:not(:first-child).fragment {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  box-sizing: border-box; }
+
 /*********************************************
  * ROLLING LINKS
  *********************************************/

+ 12 - 0
css/reveal.scss

@@ -1594,6 +1594,18 @@ $controlsArrowAngleActive: 36deg;
 	opacity: 0.4;
 }
 
+.reveal .hljs .highlight-line .hljs-ln-numbers  {
+	font-weight: 600;
+}
+
+.reveal .hljs:not(:first-child).fragment {
+	position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    box-sizing: border-box;
+}
+
 
 /*********************************************
  * ROLLING LINKS

+ 2 - 2
demo.html

@@ -241,7 +241,7 @@
 
 				<section>
 					<h2>Pretty Code</h2>
-					<pre><code class="hljs" data-trim data-line-numbers="4,8-11">
+					<pre><code class="hljs" data-trim data-line-numbers="4|9|4,8-11">
 import React, { useState } from 'react';
 
 function Example() {
@@ -412,7 +412,7 @@ Reveal.addEventListener( 'customevent', function() {
 				dependencies: [
 					{ src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
 					{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
-					{ src: 'plugin/highlight/highlight.js', async: true },
+					{ src: 'plugin/highlight/highlight.js' },
 					{ src: 'plugin/search/search.js', async: true },
 					{ src: 'plugin/zoom-js/zoom.js', async: true },
 					{ src: 'plugin/notes/notes.js', async: true }

+ 2 - 2
index.html

@@ -40,8 +40,8 @@
 				dependencies: [
 					{ src: 'plugin/markdown/marked.js' },
 					{ src: 'plugin/markdown/markdown.js' },
-					{ src: 'plugin/notes/notes.js', async: true },
-					{ src: 'plugin/highlight/highlight.js', async: true }
+					{ src: 'plugin/highlight/highlight.js' },
+					{ src: 'plugin/notes/notes.js', async: true }
 				]
 			});
 		</script>

+ 140 - 15
plugin/highlight/highlight.js

@@ -68,6 +68,11 @@ c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{b:/\{/,e:/\}/,i:/:/}]}});
 	}
 
 	var RevealHighlight = {
+
+		HIGHLIGHT_STEP_DELIMITER: '|',
+		HIGHLIGHT_LINE_DELIMITER: ',',
+		HIGHLIGHT_LINE_RANGE_DELIMITER: '-',
+
 		init: function() {
 
 			// Read the plugin config options and provide fallbacks
@@ -103,6 +108,10 @@ c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{b:/\{/,e:/\}/,i:/:/}]}});
 		 * Highlights a code block. If the <code> node has the
 		 * 'data-line-numbers' attribute we also generate slide
 		 * numbers.
+		 *
+		 * If a code block contains multiple line highlight steps
+		 * we duplicate the code block once per lines that should
+		 * be highlighted.
 		 */
 		highlightBlock: function( block ) {
 
@@ -113,7 +122,45 @@ c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{b:/\{/,e:/\}/,i:/:/}]}});
 
 				// hljs.lineNumbersBlock runs async code on the next cycle,
 				// so we need to do the same to execute after it's done
-				setTimeout( RevealHighlight.highlightLines.bind( this, block ), 0 );
+				setTimeout( function() {
+
+					var highlightSteps = RevealHighlight.deserializeHighlightSteps( block.getAttribute( 'data-line-numbers' ) );
+
+					// If there are at least two highlight steps, generate
+					// fragment clones for each
+					if( highlightSteps.length > 1 ) {
+
+						// If the original code block has a fragment-index,
+						// each clone should increment from that index
+						var fragmentIndex = parseInt( block.getAttribute( 'data-fragment-index' ), 10 );
+						if( typeof fragmentIndex !== 'number' || isNaN( fragmentIndex ) ) {
+							fragmentIndex = null;
+						}
+
+						// Generate fragments for all except the first step/original block
+						highlightSteps.slice(1).forEach( function( highlight ) {
+
+							var fragmentBlock = block.cloneNode( true );
+							fragmentBlock.setAttribute( 'data-line-numbers', RevealHighlight.serializeHighlightSteps( [ highlight ] ) );
+							fragmentBlock.classList.add( 'fragment' );
+							block.parentNode.appendChild( fragmentBlock );
+							RevealHighlight.highlightLines( fragmentBlock );
+
+							if( fragmentIndex ) {
+								fragmentBlock.setAttribute( 'data-fragment-index', fragmentIndex );
+								fragmentIndex += 1;
+							}
+
+						} );
+
+						block.setAttribute( 'data-line-numbers', RevealHighlight.serializeHighlightSteps( [ highlightSteps[0] ] ) );
+
+					}
+
+					RevealHighlight.highlightLines( block );
+
+				}.bind( this ), 0 );
+
 			}
 
 		},
@@ -131,34 +178,112 @@ c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{b:/\{/,e:/\}/,i:/:/}]}});
 		 */
 		highlightLines: function( block, linesToHighlight ) {
 
-			linesToHighlight = linesToHighlight || block.getAttribute( 'data-line-numbers' );
+			var highlightSteps = RevealHighlight.deserializeHighlightSteps( linesToHighlight || block.getAttribute( 'data-line-numbers' ) );
 
-			if( typeof linesToHighlight === 'string' && linesToHighlight !== '' ) {
+			if( highlightSteps.length ) {
 
-				linesToHighlight.split( ',' ).forEach( function( lineNumbers ) {
+				highlightSteps[0].forEach( function( highlight ) {
 
-					// Avoid failures becase of whitespace
-					lineNumbers = lineNumbers.replace( /\s/g, '' );
+					var elementsToHighlight = [];
 
-					// Ensure that we looking at a valid slide number (1 or 1-2)
-					if( /^[\d-]+$/.test( lineNumbers ) ) {
+					// Highlight a range
+					if( typeof highlight.end === 'number' ) {
+						elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+highlight.start+'):nth-child(-n+'+highlight.end+')' ) );
+					}
+					// Highlight a single line
+					else if( typeof highlight.start === 'number' ) {
+						elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child('+highlight.start+')' ) );
+					}
 
-						lineNumbers = lineNumbers.split( '-' );
+					elementsToHighlight.forEach( function( lineElement ) {
+						lineElement.classList.add( 'highlight-line' );
+					} );
 
-						var lineStart = lineNumbers[0];
-						var lineEnd = lineNumbers[1] || lineStart;
+				} );
 
-						[].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+lineStart+'):nth-child(-n+'+lineEnd+')' ) ).forEach( function( lineElement ) {
-							lineElement.classList.add( 'highlight-line' );
-						} );
+			}
+
+		},
+
+		/**
+		 * Parses and formats a user-defined string of line
+		 * numbers to highlight.
+		 *
+		 * @example
+		 * RevealHighlight.deserializeHighlightSteps( '1,2|3,5-10' )
+		 * // [
+		 * //   [ { start: 1 }, { start: 2 } ],
+		 * //   [ { start: 3 }, { start: 5, end: 10 } ]
+		 * // ]
+		 */
+		deserializeHighlightSteps: function( highlightSteps ) {
+
+			// Remove whitespace
+			highlightSteps = highlightSteps.replace( /\s/g, '' );
+
+			// Divide up our line number groups
+			highlightSteps = highlightSteps.split( RevealHighlight.HIGHLIGHT_STEP_DELIMITER );
+
+			return highlightSteps.map( function( highlights ) {
+
+				return highlights.split( RevealHighlight.HIGHLIGHT_LINE_DELIMITER ).map( function( highlight ) {
+
+					// Parse valid line numbers
+					if( /^[\d-]+$/.test( highlight ) ) {
+
+						highlight = highlight.split( RevealHighlight.HIGHLIGHT_LINE_RANGE_DELIMITER );
+
+						var lineStart = parseInt( highlight[0], 10 ),
+							lineEnd = parseInt( highlight[1], 10 );
+
+						if( isNaN( lineEnd ) ) {
+							return {
+								start: lineStart
+							};
+						}
+						else {
+							return {
+								start: lineStart,
+								end: lineEnd
+							};
+						}
+
+					}
+					// If no line numbers are provided, no code will be highlighted
+					else {
+
+						return {};
 
 					}
 
 				} );
 
-			}
+			} );
+
+		},
+
+		/**
+		 * Serializes parsed line number data into a string so
+		 * that we can store it in the DOM.
+		 */
+		serializeHighlightSteps: function( highlightSteps ) {
+
+			return highlightSteps.map( function( highlights ) {
+				return highlights.map( function( highlight ) {
+					if( typeof highlight.end === 'number' ) {
+						return highlight.start + RevealHighlight.HIGHLIGHT_LINE_RANGE_DELIMITER + highlight.end;
+					}
+					else if( typeof highlight.start === 'number' ) {
+						return highlight.start;
+					}
+					else {
+						return '';
+					}
+				} ).join( RevealHighlight.HIGHLIGHT_LINE_DELIMITER );
+			} ).join( RevealHighlight.HIGHLIGHT_STEP_DELIMITER );
 
 		}
+
 	}
 
 	Reveal.registerPlugin( 'highlight', RevealHighlight );