mirror of
https://github.com/gitpitch/gitpitch.git
synced 2024-04-18 07:30:55 +08:00
Added support for code-block delimiter and code fragment highlights.
This commit add support for a new code-block delimiter that provides a simple way to inject any source code file found within the repo as a Markdown code-block on a presentation slide. The delimiter syntax looks as follows: —?code=path/to/source/code.file This commit also introduces support for code fragment highlighting. The CF-marker syntax, one or more of which must follow a code-block in your markdown, looks as follows: @[fragment-range](optional-note) Where fragment-range identifies a single line number, @[1], or a range of numbers, @[5-10]. The optional note is plain text that is displayed on the slide below the code-block when the fragment is in focus.
This commit is contained in:
parent
15a9a95369
commit
1f834dbf73
|
@ -51,6 +51,7 @@ public class Module extends AbstractModule {
|
|||
bind(ImageService.class).asEagerSingleton();
|
||||
bind(VideoService.class).asEagerSingleton();
|
||||
bind(GISTService.class).asEagerSingleton();
|
||||
bind(CodeService.class).asEagerSingleton();
|
||||
bind(ShortcutsService.class).asEagerSingleton();
|
||||
bind(WebService.class).asEagerSingleton();
|
||||
bind(ComposableService.class).asEagerSingleton();
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.gitpitch.utils.*;
|
|||
import com.gitpitch.services.ImageService;
|
||||
import com.gitpitch.services.VideoService;
|
||||
import com.gitpitch.services.GISTService;
|
||||
import com.gitpitch.services.CodeService;
|
||||
import com.gitpitch.services.ShortcutsService;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
@ -53,6 +54,7 @@ public class MarkdownModel implements Markdown {
|
|||
private final ImageService imageService;
|
||||
private final VideoService videoService;
|
||||
private final GISTService gistService;
|
||||
private final CodeService codeService;
|
||||
private final ShortcutsService shortcutsService;
|
||||
private final MarkdownRenderer mrndr;
|
||||
private final String markdown;
|
||||
|
@ -63,12 +65,14 @@ public class MarkdownModel implements Markdown {
|
|||
public MarkdownModel(ImageService imageService,
|
||||
VideoService videoService,
|
||||
GISTService gistService,
|
||||
CodeService codeService,
|
||||
ShortcutsService shortcutsService,
|
||||
@Nullable @Assisted MarkdownRenderer mrndr) {
|
||||
|
||||
this.imageService = imageService;
|
||||
this.videoService = videoService;
|
||||
this.gistService = gistService;
|
||||
this.codeService = codeService;
|
||||
this.shortcutsService = shortcutsService;
|
||||
this.mrndr = mrndr;
|
||||
|
||||
|
@ -122,7 +126,7 @@ public class MarkdownModel implements Markdown {
|
|||
}
|
||||
|
||||
/*
|
||||
* Process text, image, video and gist content
|
||||
* Process text, image, video, gist, and code content
|
||||
* in PITCHME.md for online viewing.
|
||||
*/
|
||||
private String process(String md,
|
||||
|
@ -175,11 +179,9 @@ public class MarkdownModel implements Markdown {
|
|||
.toString();
|
||||
|
||||
} else if (gistDelimFound(md)) {
|
||||
|
||||
String gist = new StringBuffer(gistService.build(md,
|
||||
pp, yOpts, this)).toString();
|
||||
return gist;
|
||||
|
||||
return gistService.build(md, pp, yOpts, this);
|
||||
} else if(codeDelimFound(md)) {
|
||||
return codeService.build(md, pp, yOpts, gitRawBase, this);
|
||||
}
|
||||
|
||||
if (yOpts != null && yOpts.hasImageBg()) {
|
||||
|
@ -253,8 +255,10 @@ public class MarkdownModel implements Markdown {
|
|||
String absTagLink = gitRawBase + tagLink;
|
||||
return md.replace(tagLink, absTagLink);
|
||||
}
|
||||
} else if(shortcutsService.fragmentFound(md)) {
|
||||
return shortcutsService.expandFragment(md);
|
||||
} else if(shortcutsService.listFragmentFound(md)) {
|
||||
return shortcutsService.expandListFragment(md);
|
||||
} else if(shortcutsService.codeFragmentFound(md)) {
|
||||
return shortcutsService.expandCodeFragment(md);
|
||||
} else {
|
||||
|
||||
/*
|
||||
|
@ -269,7 +273,7 @@ public class MarkdownModel implements Markdown {
|
|||
}
|
||||
|
||||
/*
|
||||
* Process text, image, video and gist content
|
||||
* Process text, image, video, gist, and code content
|
||||
* in PITCHME.md for offline viewing.
|
||||
*/
|
||||
public String offline(String md) {
|
||||
|
@ -402,6 +406,10 @@ public class MarkdownModel implements Markdown {
|
|||
return md.startsWith(horizGISTDelim()) || md.startsWith(vertGISTDelim());
|
||||
}
|
||||
|
||||
private boolean codeDelimFound(String md) {
|
||||
return md.startsWith(horizCodeDelim()) || md.startsWith(vertCodeDelim());
|
||||
}
|
||||
|
||||
private String delimiter(String md) {
|
||||
return (md.startsWith(hSlideDelim)) ? horizDelim() : vertDelim();
|
||||
}
|
||||
|
@ -579,6 +587,10 @@ public class MarkdownModel implements Markdown {
|
|||
return isHorizontal(md) ? horizGISTDelim() : vertGISTDelim();
|
||||
}
|
||||
|
||||
public String extractCodeDelim(String md) {
|
||||
return isHorizontal(md) ? horizCodeDelim() : vertCodeDelim();
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a key for querying the cache for matching MarkdownModel.
|
||||
*/
|
||||
|
@ -629,6 +641,14 @@ public class MarkdownModel implements Markdown {
|
|||
return vertDelim() + MD_VSLIDE_GIST;
|
||||
}
|
||||
|
||||
public String horizCodeDelim() {
|
||||
return horizDelim() + MD_HSLIDE_CODE;
|
||||
}
|
||||
|
||||
public String vertCodeDelim() {
|
||||
return vertDelim() + MD_VSLIDE_CODE;
|
||||
}
|
||||
|
||||
/*
|
||||
* The initial releases of GitPitch used #HSLIDE and #VSLIDE
|
||||
* as default delimiters. To provide backwards compatability
|
||||
|
@ -677,8 +697,14 @@ public class MarkdownModel implements Markdown {
|
|||
public static final String MD_CLOSER = "\" -->";
|
||||
public static final String MD_SPACER = "\n";
|
||||
public static final String DATA_IMAGE_ATTR = "data-background-image=";
|
||||
public static final String MD_FRAG_OPEN = "- ";
|
||||
public static final String MD_FRAG_CLOSE = "|";
|
||||
public static final String MD_LIST_FRAG_OPEN = "- ";
|
||||
public static final String MD_LIST_FRAG_CLOSE = "|";
|
||||
public static final String MD_CODE_FRAG_OPEN = "@[";
|
||||
public static final String MD_CODE_FRAG_CLOSE = "]";
|
||||
public static final String MD_CODE_FRAG_NOTE_OPEN = "(";
|
||||
public static final String MD_CODE_FRAG_NOTE_CLOSE = ")";
|
||||
public static final String MD_CODE_BLOCK_OPEN = "```";
|
||||
public static final String MD_CODE_BLOCK_CLOSE = "```";
|
||||
|
||||
private static final String MD_HSLIDE_IMAGE = "?image=";
|
||||
private static final String MD_VSLIDE_IMAGE = "?image=";
|
||||
|
@ -686,6 +712,8 @@ public class MarkdownModel implements Markdown {
|
|||
private static final String MD_VSLIDE_VIDEO = "?video=";
|
||||
private static final String MD_HSLIDE_GIST = "?gist=";
|
||||
private static final String MD_VSLIDE_GIST = "?gist=";
|
||||
private static final String MD_HSLIDE_CODE = "?code=";
|
||||
private static final String MD_VSLIDE_CODE = "?code=";
|
||||
|
||||
private static final String SLASH = "/";
|
||||
private static final String PARAM_BRANCH = "?b=";
|
||||
|
|
|
@ -40,6 +40,7 @@ public final class Dependencies {
|
|||
private final String fontawesomeVersion;
|
||||
private final String octiconsVersion;
|
||||
private final String highlightjsVersion;
|
||||
private final Boolean highlightPluginEnabled;
|
||||
|
||||
@Inject
|
||||
public Dependencies(Configuration cfg) {
|
||||
|
@ -51,6 +52,8 @@ public final class Dependencies {
|
|||
this.fontawesomeVersion = cfg.getString("gitpitch.dependency.fontawesome");
|
||||
this.octiconsVersion = cfg.getString("gitpitch.dependency.octicons");
|
||||
this.highlightjsVersion = cfg.getString("gitpitch.dependency.highlightjs");
|
||||
this.highlightPluginEnabled =
|
||||
cfg.getBoolean("gitpitch.dependency.highlight.plugin", false);
|
||||
}
|
||||
|
||||
public String revealjs(boolean offline, String versionOverride) {
|
||||
|
@ -86,6 +89,10 @@ public final class Dependencies {
|
|||
return build(offline, GIPITCHIMG);
|
||||
}
|
||||
|
||||
public Boolean highlightPluginEnabled() {
|
||||
return highlightPluginEnabled;
|
||||
}
|
||||
|
||||
private String build(boolean offline, String libName) {
|
||||
|
||||
if(offline) {
|
||||
|
|
153
app/com/gitpitch/services/CodeService.java
Normal file
153
app/com/gitpitch/services/CodeService.java
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016 David Russell
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.gitpitch.services;
|
||||
|
||||
import com.gitpitch.models.MarkdownModel;
|
||||
import com.gitpitch.git.GRS;
|
||||
import com.gitpitch.git.GRSService;
|
||||
import com.gitpitch.git.GRSManager;
|
||||
import com.gitpitch.services.DiskService;
|
||||
import com.gitpitch.utils.PitchParams;
|
||||
import com.gitpitch.utils.YAMLOptions;
|
||||
import java.util.*;
|
||||
import java.nio.file.*;
|
||||
import javax.inject.*;
|
||||
import play.Logger;
|
||||
import play.Logger.ALogger;
|
||||
|
||||
/*
|
||||
* PITCHME.md code support service.
|
||||
*/
|
||||
@Singleton
|
||||
public class CodeService {
|
||||
|
||||
private final Logger.ALogger log = Logger.of(this.getClass());
|
||||
|
||||
private final GRSManager grsManager;
|
||||
private final DiskService diskService;
|
||||
|
||||
@Inject
|
||||
public CodeService(GRSManager grsManager,
|
||||
DiskService diskService) {
|
||||
|
||||
this.grsManager = grsManager;
|
||||
this.diskService = diskService;
|
||||
}
|
||||
|
||||
public String build(String md,
|
||||
PitchParams pp,
|
||||
YAMLOptions yOpts,
|
||||
String gitRawBase,
|
||||
MarkdownModel mdm) {
|
||||
|
||||
String codeBlock = mdm.extractCodeDelim(md);
|
||||
|
||||
try {
|
||||
|
||||
String codePath = extractCodePath(md, gitRawBase, mdm);
|
||||
|
||||
GRS grs = grsManager.get(pp);
|
||||
GRSService grsService = grsManager.getService(grs);
|
||||
|
||||
int downStatus =
|
||||
grsService.download(pp, SOURCE_CODE, codePath);
|
||||
|
||||
if(downStatus == 0) {
|
||||
String code = diskService.asText(pp, SOURCE_CODE);
|
||||
return buildCodeBlock(mdm.extractCodeDelim(md), code);
|
||||
} else {
|
||||
return buildCodeBlockError(mdm.extractCodeDelim(md),
|
||||
extractPath(md, mdm));
|
||||
}
|
||||
|
||||
} catch (Exception gex) {}
|
||||
return codeBlock;
|
||||
}
|
||||
|
||||
public String extractCodePath(String md,
|
||||
String gitRawBase,
|
||||
MarkdownModel mdm) {
|
||||
|
||||
String codePath = null;
|
||||
|
||||
try {
|
||||
|
||||
String delim = mdm.extractCodeDelim(md);
|
||||
codePath = md.substring(delim.length());
|
||||
|
||||
if (!mdm.linkAbsolute(codePath)) {
|
||||
codePath =
|
||||
new StringBuffer(gitRawBase).append(codePath)
|
||||
.toString();
|
||||
}
|
||||
|
||||
} catch (Exception pex) {
|
||||
log.warn("extractCodePath: ex={}", pex);
|
||||
}
|
||||
return codePath;
|
||||
}
|
||||
|
||||
private String extractPath(String md, MarkdownModel mdm) {
|
||||
String path = null;
|
||||
|
||||
try {
|
||||
String delim = mdm.extractCodeDelim(md);
|
||||
path = md.substring(delim.length());
|
||||
} catch (Exception pex) {
|
||||
log.warn("extractPath: ex={}", pex);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String buildCodeBlock(String delim, String code) {
|
||||
return new StringBuffer(delim)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(MarkdownModel.MD_CODE_BLOCK_OPEN)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(code)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(MarkdownModel.MD_CODE_BLOCK_CLOSE)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private String buildCodeBlockError(String delim, String codePath) {
|
||||
return new StringBuffer(delim)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(SOURCE_CODE_DELIMITER)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(codePath)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.append(SOURCE_CODE_NOT_FOUND)
|
||||
.append(MarkdownModel.MD_SPACER)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private static final String SOURCE_CODE = "PITCHME.code";
|
||||
private static final String SOURCE_CODE_DELIMITER =
|
||||
"### Code Block Delimiter";
|
||||
private static final String SOURCE_CODE_NOT_FOUND =
|
||||
"### Source File Not Found";
|
||||
|
||||
}
|
|
@ -91,6 +91,19 @@ public class DiskService {
|
|||
return asPath(pp, filename).toFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return text contents for file within PitchParams branch working directory.
|
||||
*/
|
||||
public String asText(PitchParams pp,
|
||||
String filename) {
|
||||
String text = "";
|
||||
try {
|
||||
Path filePath = asPath(pp, filename);
|
||||
text = new String(Files.readAllBytes(filePath));
|
||||
} catch(Exception tex) {}
|
||||
return text;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure PitchParams branch working directory exists.
|
||||
*/
|
||||
|
|
|
@ -40,12 +40,23 @@ public class ShortcutsService {
|
|||
|
||||
private final Logger.ALogger log = Logger.of(this.getClass());
|
||||
|
||||
public boolean fragmentFound(String md) {
|
||||
public boolean listFragmentFound(String md) {
|
||||
boolean found = false;
|
||||
if(md != null) {
|
||||
String trimmed = md.trim();
|
||||
if(trimmed.startsWith(MarkdownModel.MD_FRAG_OPEN) &&
|
||||
trimmed.endsWith(MarkdownModel.MD_FRAG_CLOSE)) {
|
||||
if(trimmed.startsWith(MarkdownModel.MD_LIST_FRAG_OPEN) &&
|
||||
trimmed.endsWith(MarkdownModel.MD_LIST_FRAG_CLOSE)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public boolean codeFragmentFound(String md) {
|
||||
boolean found = false;
|
||||
if(md != null) {
|
||||
String trimmed = md.trim();
|
||||
if(trimmed.startsWith(MarkdownModel.MD_CODE_FRAG_OPEN)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
@ -53,14 +64,69 @@ public class ShortcutsService {
|
|||
}
|
||||
|
||||
/*
|
||||
* Expand shortcut syntax for fragment with fully expanded
|
||||
* Expand shortcut syntax for list fragment with fully expanded
|
||||
* HTML comment syntax.
|
||||
*/
|
||||
public String expandFragment(String md) {
|
||||
int fragCloseIdx = md.lastIndexOf(MarkdownModel.MD_FRAG_CLOSE);
|
||||
public String expandListFragment(String md) {
|
||||
int fragCloseIdx = md.lastIndexOf(MarkdownModel.MD_LIST_FRAG_CLOSE);
|
||||
return md.substring(0, fragCloseIdx) + FRAGMENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand shortcut syntax for code fragment with fully expanded
|
||||
* HTML span syntax with data-code-focus class.
|
||||
*/
|
||||
public String expandCodeFragment(String md) {
|
||||
|
||||
try {
|
||||
String codeFragRange = null;
|
||||
String codeFragNote = null;
|
||||
|
||||
int codeFragStart =
|
||||
md.indexOf(MarkdownModel.MD_CODE_FRAG_OPEN);
|
||||
int codeFragEnd =
|
||||
md.indexOf(MarkdownModel.MD_CODE_FRAG_CLOSE);
|
||||
|
||||
if(codeFragEnd > codeFragStart) {
|
||||
codeFragRange =
|
||||
md.substring(codeFragStart+2, codeFragEnd);
|
||||
}
|
||||
|
||||
int codeFragNoteStart =
|
||||
md.indexOf(MarkdownModel.MD_CODE_FRAG_NOTE_OPEN);
|
||||
int codeFragNoteEnd =
|
||||
md.indexOf(MarkdownModel.MD_CODE_FRAG_NOTE_CLOSE);
|
||||
|
||||
if(codeFragNoteEnd > codeFragNoteStart) {
|
||||
codeFragNote =
|
||||
md.substring(codeFragNoteStart+1, codeFragNoteEnd);
|
||||
}
|
||||
|
||||
if(codeFragRange != null) {
|
||||
md = buildCodeFragment(codeFragRange, codeFragNote);
|
||||
}
|
||||
|
||||
} catch(Exception cfex) {
|
||||
} finally {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a HTML fragment for a code fragment based on the
|
||||
* reveal-code-focus plugin syntax:
|
||||
* <span class="fragment current-only" data-code-focus="1-9">Note</span>
|
||||
*/
|
||||
private String buildCodeFragment(String range, String note) {
|
||||
if(note == null) note = "";
|
||||
return new StringBuffer(HTML_CODE_FRAG_OPEN)
|
||||
.append(range)
|
||||
.append(HTML_CODE_FRAG_CLOSE)
|
||||
.append(note)
|
||||
.append(HTML_CODE_FRAG_NOTE_CLOSE)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* Note, the space before opening bracket is intentional
|
||||
* so tag injection can happen directly alongside other
|
||||
|
@ -68,4 +134,8 @@ public class ShortcutsService {
|
|||
*/
|
||||
private static final String FRAGMENT =
|
||||
" <!-- .element: class=\"fragment\" -->";
|
||||
private static final String HTML_CODE_FRAG_OPEN =
|
||||
"<span class=\"fragment current-only\" data-code-focus=\"";
|
||||
private static final String HTML_CODE_FRAG_CLOSE = "\">";
|
||||
private static final String HTML_CODE_FRAG_NOTE_CLOSE = "</span>";
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<link href="@deps.octicons(offline)/octicons.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="@deps.fontawesome(offline)/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
|
||||
@SlideshowStyle()
|
||||
@SlideshowCodeFragHighlightStyle()
|
||||
@if(ssm.hasThemeOverride()) {
|
||||
<style>
|
||||
@Html(ssm.fetchThemeOverride())
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<style>
|
||||
.reveal .slides section .fragment.current-only {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: none;
|
||||
}
|
||||
.reveal .slides section .fragment.current-only.current-fragment {
|
||||
display: block;
|
||||
}
|
||||
.line { display: block; }
|
||||
.line.focus { background: #fdf6e3; color: #657b83; }
|
||||
.line.focus .hljs-comment, .line.focus .hljs-quote { color: #93a1a1; }
|
||||
.line.focus .hljs-keyword, .line.focus .hljs-selector-tag, .line.focus .hljs-addition { color: #859900; }
|
||||
.line.focus .hljs-number, .line.focus .hljs-string, .line.focus .hljs-meta .hljs-meta-string, .line.focus .hljs-literal, .line.focus .hljs-doctag, .line.focus .hljs-regexp { color: #2aa198; }
|
||||
.line.focus .hljs-title, .line.focus .hljs-section, .line.focus .hljs-name, .line.focus .hljs-selector-id, .line.focus .hljs-selector-class { color: #268bd2; }
|
||||
.line.focus .hljs-attribute, .line.focus .hljs-attr, .line.focus .hljs-variable, .line.focus .hljs-template-variable, .line.focus .hljs-class .hljs-title, .line.focus .hljs-type { color: #b58900; }
|
||||
.line.focus .hljs-symbol, .line.focus .hljs-bullet, .line.focus .hljs-subst, .line.focus .hljs-meta, .line.focus .hljs-meta .hljs-keyword, .line.focus .hljs-selector-attr, .line.focus .hljs-selector-pseudo, .line.focus .hljs-link { color: #cb4b16; }
|
||||
.line.focus .hljs-built_in, .line.focus .hljs-deletion { color: #dc322f; }
|
||||
.line.focus .hljs-formula { background: #eee8d5; }
|
||||
.line.focus .hljs-emphasis { font-style: italic; }
|
||||
.line.focus .hljs-strong { font-weight: bold; }
|
||||
.yellow-slide .line.focus:nth-child(2) { background: yellow; }
|
||||
</style>
|
|
@ -34,7 +34,16 @@
|
|||
@if(offline) {
|
||||
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/notes/notes.js", async: true },
|
||||
}
|
||||
@if(deps.highlightPluginEnabled()) {
|
||||
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/highlight/highlight.js", async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
|
||||
} else {
|
||||
{ src: '@deps.highlightjs(offline)/highlight.js' },
|
||||
{ src: '@deps.highlightjs(offline)/reveal-code-focus.js',
|
||||
callback: function() {
|
||||
RevealCodeFocus();
|
||||
}
|
||||
},
|
||||
}
|
||||
@if(ssm.mathEnabled()) {
|
||||
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/math/math.js", async: true }
|
||||
}
|
||||
|
|
|
@ -409,6 +409,9 @@ gitpitch {
|
|||
fontawesome = "4.6.3"
|
||||
octicons = "3.5.0"
|
||||
highlightjs = "9.6.0"
|
||||
highlight {
|
||||
plugin = false
|
||||
}
|
||||
}
|
||||
|
||||
github {
|
||||
|
|
78
public/libs/highlight.js/9.6.0/highlight.js
Normal file
78
public/libs/highlight.js/9.6.0/highlight.js
Normal file
File diff suppressed because one or more lines are too long
229
public/libs/highlight.js/9.6.0/reveal-code-focus.js
Normal file
229
public/libs/highlight.js/9.6.0/reveal-code-focus.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*!
|
||||
* reveal-code-focus 1.0.0
|
||||
* Copyright 2015-2017 Benjamin Tan <https://demoneaux.github.io/>
|
||||
* Available under MIT license <https://github.com/demoneaux/reveal-code-focus/blob/master/LICENSE>
|
||||
*/
|
||||
;(function(window, Reveal, hljs) {
|
||||
if (typeof window.RevealCodeFocus == 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentSlide, currentFragments, scrollToFocused = true, prevSlideData = null;
|
||||
|
||||
// Iterates through `array`, running `callback` for each `array` element.
|
||||
function forEach(array, callback) {
|
||||
var i = -1, length = array ? array.length : 0;
|
||||
while (++i < length) {
|
||||
callback(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function indexOf(array, elem) {
|
||||
var i = -1, length = array ? array.length : 0;
|
||||
while (++i < length) {
|
||||
if (array[i] === elem) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initialize(e) {
|
||||
// Initialize code only once.
|
||||
// TODO: figure out why `initialize` is being called twice.
|
||||
if (initialize.ran) {
|
||||
return;
|
||||
}
|
||||
initialize.ran = true;
|
||||
|
||||
// TODO: mark as parsed.
|
||||
forEach(document.querySelectorAll('pre code'), function(element) {
|
||||
// Trim whitespace if the `data-trim` attribute is present.
|
||||
if (element.hasAttribute('data-trim') && typeof element.innerHTML.trim == 'function') {
|
||||
element.innerHTML = element.innerHTML.trim();
|
||||
}
|
||||
|
||||
// Highlight code using highlight.js.
|
||||
hljs.highlightBlock(element);
|
||||
|
||||
// Split highlighted code into lines.
|
||||
var openTags = [], reHtmlTag = /<(\/?)span(?:\s+(?:class=(['"])hljs-.*?\2)?\s*|\s*)>/g;
|
||||
element.innerHTML = element.innerHTML.replace(/(.*?)\r?\n/g, function(_, string) {
|
||||
if (!string) {
|
||||
return '<span class=line> </span>';
|
||||
}
|
||||
var openTag, stringPrepend;
|
||||
// Re-open all tags that were previously closed.
|
||||
if (openTags.length) {
|
||||
stringPrepend = openTags.join('');
|
||||
}
|
||||
// Match all HTML `<span>` tags.
|
||||
reHtmlTag.lastIndex = 0;
|
||||
while (openTag = reHtmlTag.exec(string)) {
|
||||
// If it is a closing tag, remove the opening tag from the list.
|
||||
if (openTag[1]) {
|
||||
openTags.pop();
|
||||
}
|
||||
// Otherwise if it is an opening tag, push it to the list.
|
||||
else {
|
||||
openTags.push(openTag[0]);
|
||||
}
|
||||
}
|
||||
// Close all opened tags, so that strings can be wrapped with `span.line`.
|
||||
if (openTags.length) {
|
||||
string += Array(openTags.length + 1).join('</span>');
|
||||
}
|
||||
if (stringPrepend) {
|
||||
string = stringPrepend + string;
|
||||
}
|
||||
return '<span class=line>' + string + '</span>';
|
||||
});
|
||||
});
|
||||
|
||||
Reveal.addEventListener('slidechanged', updateCurrentSlide);
|
||||
|
||||
Reveal.addEventListener('fragmentshown', function(e) {
|
||||
focusFragment(e.fragment);
|
||||
});
|
||||
|
||||
// TODO: make this configurable.
|
||||
// When a fragment is hidden, clear the current focused fragment,
|
||||
// and focus on the previous fragment.
|
||||
Reveal.addEventListener('fragmenthidden', function(e) {
|
||||
var index = indexOf(currentFragments, e.fragment);
|
||||
focusFragment(currentFragments[index - 1]);
|
||||
});
|
||||
|
||||
updateCurrentSlide(e);
|
||||
}
|
||||
initialize.ran = false;
|
||||
|
||||
function updateCurrentSlide(e) {
|
||||
currentSlide = e.currentSlide;
|
||||
currentFragments = currentSlide.getElementsByClassName('fragment');
|
||||
clearPreviousFocus();
|
||||
|
||||
// If moving back to a previous slide…
|
||||
if (
|
||||
currentFragments.length &&
|
||||
prevSlideData &&
|
||||
(
|
||||
prevSlideData.indexh > e.indexh ||
|
||||
(prevSlideData.indexh == e.indexh && prevSlideData.indexv > e.indexv)
|
||||
)
|
||||
) {
|
||||
// …return to the last fragment and highlight the code.
|
||||
while (Reveal.nextFragment()) {}
|
||||
var currentFragment = currentFragments[currentFragments.length - 1];
|
||||
currentFragment.classList.add('current-fragment');
|
||||
focusFragment(currentFragment);
|
||||
}
|
||||
|
||||
// Update previous slide information.
|
||||
prevSlideData = {
|
||||
'indexh': e.indexh,
|
||||
'indexv': e.indexv
|
||||
};
|
||||
}
|
||||
|
||||
// Removes any previously focused lines.
|
||||
function clearPreviousFocus() {
|
||||
forEach(currentSlide.querySelectorAll('pre code .line.focus'), function(line) {
|
||||
line.classList.remove('focus');
|
||||
});
|
||||
}
|
||||
|
||||
function focusFragment(fragment) {
|
||||
clearPreviousFocus();
|
||||
if (!fragment) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = fragment.getAttribute('data-code-focus');
|
||||
if (!lines) {
|
||||
return;
|
||||
}
|
||||
|
||||
var codeBlock = parseInt(fragment.getAttribute('data-code-block'));
|
||||
if (isNaN(codeBlock)) {
|
||||
codeBlock = 1;
|
||||
}
|
||||
|
||||
var preElems = currentSlide.querySelectorAll('pre');
|
||||
if (!preElems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pre = preElems[codeBlock - 1];
|
||||
var code = pre.querySelectorAll('code .line');
|
||||
if (!code.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var topLineNumber, bottomLineNumber;
|
||||
|
||||
forEach(lines.split(','), function(line) {
|
||||
lines = line.split('-');
|
||||
if (lines.length == 1) {
|
||||
focusLine(lines[0]);
|
||||
} else {
|
||||
var i = lines[0] - 1, j = lines[1];
|
||||
|
||||
while (++i <= j) {
|
||||
focusLine(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function focusLine(lineNumber) {
|
||||
// Convert from 1-based index to 0-based index.
|
||||
lineNumber -= 1;
|
||||
|
||||
var line = code[lineNumber];
|
||||
if (!line) {
|
||||
return;
|
||||
}
|
||||
|
||||
line.classList.add('focus');
|
||||
|
||||
if (scrollToFocused) {
|
||||
if (topLineNumber == null) {
|
||||
topLineNumber = bottomLineNumber = lineNumber;
|
||||
} else {
|
||||
if (lineNumber < topLineNumber) {
|
||||
topLineNumber = lineNumber;
|
||||
}
|
||||
if (lineNumber > bottomLineNumber) {
|
||||
bottomLineNumber = lineNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollToFocused && topLineNumber != null) {
|
||||
var topLine = code[topLineNumber];
|
||||
var bottomLine = code[bottomLineNumber];
|
||||
var codeParent = topLine.parentNode;
|
||||
var scrollTop = topLine.offsetTop;
|
||||
var scrollBottom = bottomLine.offsetTop + bottomLine.clientHeight;
|
||||
codeParent.scrollTop = scrollTop - (codeParent.clientHeight - (scrollBottom - scrollTop)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
function RevealCodeFocus(options) {
|
||||
options || (options = {
|
||||
'scrollToFocused': true
|
||||
});
|
||||
|
||||
if (options.scrollToFocused != null) {
|
||||
scrollToFocused = options.scrollToFocused;
|
||||
}
|
||||
|
||||
if (Reveal.isReady()) {
|
||||
initialize({ 'currentSlide': Reveal.getCurrentSlide() });
|
||||
} else {
|
||||
Reveal.addEventListener('ready', initialize);
|
||||
}
|
||||
}
|
||||
|
||||
window.RevealCodeFocus = RevealCodeFocus;
|
||||
}(this, this.Reveal, this.hljs));
|
Loading…
Reference in New Issue
Block a user