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(ImageService.class).asEagerSingleton();
|
||||||
bind(VideoService.class).asEagerSingleton();
|
bind(VideoService.class).asEagerSingleton();
|
||||||
bind(GISTService.class).asEagerSingleton();
|
bind(GISTService.class).asEagerSingleton();
|
||||||
|
bind(CodeService.class).asEagerSingleton();
|
||||||
bind(ShortcutsService.class).asEagerSingleton();
|
bind(ShortcutsService.class).asEagerSingleton();
|
||||||
bind(WebService.class).asEagerSingleton();
|
bind(WebService.class).asEagerSingleton();
|
||||||
bind(ComposableService.class).asEagerSingleton();
|
bind(ComposableService.class).asEagerSingleton();
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.gitpitch.utils.*;
|
||||||
import com.gitpitch.services.ImageService;
|
import com.gitpitch.services.ImageService;
|
||||||
import com.gitpitch.services.VideoService;
|
import com.gitpitch.services.VideoService;
|
||||||
import com.gitpitch.services.GISTService;
|
import com.gitpitch.services.GISTService;
|
||||||
|
import com.gitpitch.services.CodeService;
|
||||||
import com.gitpitch.services.ShortcutsService;
|
import com.gitpitch.services.ShortcutsService;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
@ -53,6 +54,7 @@ public class MarkdownModel implements Markdown {
|
||||||
private final ImageService imageService;
|
private final ImageService imageService;
|
||||||
private final VideoService videoService;
|
private final VideoService videoService;
|
||||||
private final GISTService gistService;
|
private final GISTService gistService;
|
||||||
|
private final CodeService codeService;
|
||||||
private final ShortcutsService shortcutsService;
|
private final ShortcutsService shortcutsService;
|
||||||
private final MarkdownRenderer mrndr;
|
private final MarkdownRenderer mrndr;
|
||||||
private final String markdown;
|
private final String markdown;
|
||||||
|
@ -63,12 +65,14 @@ public class MarkdownModel implements Markdown {
|
||||||
public MarkdownModel(ImageService imageService,
|
public MarkdownModel(ImageService imageService,
|
||||||
VideoService videoService,
|
VideoService videoService,
|
||||||
GISTService gistService,
|
GISTService gistService,
|
||||||
|
CodeService codeService,
|
||||||
ShortcutsService shortcutsService,
|
ShortcutsService shortcutsService,
|
||||||
@Nullable @Assisted MarkdownRenderer mrndr) {
|
@Nullable @Assisted MarkdownRenderer mrndr) {
|
||||||
|
|
||||||
this.imageService = imageService;
|
this.imageService = imageService;
|
||||||
this.videoService = videoService;
|
this.videoService = videoService;
|
||||||
this.gistService = gistService;
|
this.gistService = gistService;
|
||||||
|
this.codeService = codeService;
|
||||||
this.shortcutsService = shortcutsService;
|
this.shortcutsService = shortcutsService;
|
||||||
this.mrndr = mrndr;
|
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.
|
* in PITCHME.md for online viewing.
|
||||||
*/
|
*/
|
||||||
private String process(String md,
|
private String process(String md,
|
||||||
|
@ -175,11 +179,9 @@ public class MarkdownModel implements Markdown {
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
} else if (gistDelimFound(md)) {
|
} else if (gistDelimFound(md)) {
|
||||||
|
return gistService.build(md, pp, yOpts, this);
|
||||||
String gist = new StringBuffer(gistService.build(md,
|
} else if(codeDelimFound(md)) {
|
||||||
pp, yOpts, this)).toString();
|
return codeService.build(md, pp, yOpts, gitRawBase, this);
|
||||||
return gist;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (yOpts != null && yOpts.hasImageBg()) {
|
if (yOpts != null && yOpts.hasImageBg()) {
|
||||||
|
@ -253,8 +255,10 @@ public class MarkdownModel implements Markdown {
|
||||||
String absTagLink = gitRawBase + tagLink;
|
String absTagLink = gitRawBase + tagLink;
|
||||||
return md.replace(tagLink, absTagLink);
|
return md.replace(tagLink, absTagLink);
|
||||||
}
|
}
|
||||||
} else if(shortcutsService.fragmentFound(md)) {
|
} else if(shortcutsService.listFragmentFound(md)) {
|
||||||
return shortcutsService.expandFragment(md);
|
return shortcutsService.expandListFragment(md);
|
||||||
|
} else if(shortcutsService.codeFragmentFound(md)) {
|
||||||
|
return shortcutsService.expandCodeFragment(md);
|
||||||
} else {
|
} 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.
|
* in PITCHME.md for offline viewing.
|
||||||
*/
|
*/
|
||||||
public String offline(String md) {
|
public String offline(String md) {
|
||||||
|
@ -402,6 +406,10 @@ public class MarkdownModel implements Markdown {
|
||||||
return md.startsWith(horizGISTDelim()) || md.startsWith(vertGISTDelim());
|
return md.startsWith(horizGISTDelim()) || md.startsWith(vertGISTDelim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean codeDelimFound(String md) {
|
||||||
|
return md.startsWith(horizCodeDelim()) || md.startsWith(vertCodeDelim());
|
||||||
|
}
|
||||||
|
|
||||||
private String delimiter(String md) {
|
private String delimiter(String md) {
|
||||||
return (md.startsWith(hSlideDelim)) ? horizDelim() : vertDelim();
|
return (md.startsWith(hSlideDelim)) ? horizDelim() : vertDelim();
|
||||||
}
|
}
|
||||||
|
@ -579,6 +587,10 @@ public class MarkdownModel implements Markdown {
|
||||||
return isHorizontal(md) ? horizGISTDelim() : vertGISTDelim();
|
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.
|
* Generate a key for querying the cache for matching MarkdownModel.
|
||||||
*/
|
*/
|
||||||
|
@ -629,6 +641,14 @@ public class MarkdownModel implements Markdown {
|
||||||
return vertDelim() + MD_VSLIDE_GIST;
|
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
|
* The initial releases of GitPitch used #HSLIDE and #VSLIDE
|
||||||
* as default delimiters. To provide backwards compatability
|
* 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_CLOSER = "\" -->";
|
||||||
public static final String MD_SPACER = "\n";
|
public static final String MD_SPACER = "\n";
|
||||||
public static final String DATA_IMAGE_ATTR = "data-background-image=";
|
public static final String DATA_IMAGE_ATTR = "data-background-image=";
|
||||||
public static final String MD_FRAG_OPEN = "- ";
|
public static final String MD_LIST_FRAG_OPEN = "- ";
|
||||||
public static final String MD_FRAG_CLOSE = "|";
|
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_HSLIDE_IMAGE = "?image=";
|
||||||
private static final String MD_VSLIDE_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_VSLIDE_VIDEO = "?video=";
|
||||||
private static final String MD_HSLIDE_GIST = "?gist=";
|
private static final String MD_HSLIDE_GIST = "?gist=";
|
||||||
private static final String MD_VSLIDE_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 SLASH = "/";
|
||||||
private static final String PARAM_BRANCH = "?b=";
|
private static final String PARAM_BRANCH = "?b=";
|
||||||
|
|
|
@ -40,6 +40,7 @@ public final class Dependencies {
|
||||||
private final String fontawesomeVersion;
|
private final String fontawesomeVersion;
|
||||||
private final String octiconsVersion;
|
private final String octiconsVersion;
|
||||||
private final String highlightjsVersion;
|
private final String highlightjsVersion;
|
||||||
|
private final Boolean highlightPluginEnabled;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Dependencies(Configuration cfg) {
|
public Dependencies(Configuration cfg) {
|
||||||
|
@ -51,6 +52,8 @@ public final class Dependencies {
|
||||||
this.fontawesomeVersion = cfg.getString("gitpitch.dependency.fontawesome");
|
this.fontawesomeVersion = cfg.getString("gitpitch.dependency.fontawesome");
|
||||||
this.octiconsVersion = cfg.getString("gitpitch.dependency.octicons");
|
this.octiconsVersion = cfg.getString("gitpitch.dependency.octicons");
|
||||||
this.highlightjsVersion = cfg.getString("gitpitch.dependency.highlightjs");
|
this.highlightjsVersion = cfg.getString("gitpitch.dependency.highlightjs");
|
||||||
|
this.highlightPluginEnabled =
|
||||||
|
cfg.getBoolean("gitpitch.dependency.highlight.plugin", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String revealjs(boolean offline, String versionOverride) {
|
public String revealjs(boolean offline, String versionOverride) {
|
||||||
|
@ -86,6 +89,10 @@ public final class Dependencies {
|
||||||
return build(offline, GIPITCHIMG);
|
return build(offline, GIPITCHIMG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean highlightPluginEnabled() {
|
||||||
|
return highlightPluginEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
private String build(boolean offline, String libName) {
|
private String build(boolean offline, String libName) {
|
||||||
|
|
||||||
if(offline) {
|
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 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.
|
* Ensure PitchParams branch working directory exists.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -40,12 +40,23 @@ public class ShortcutsService {
|
||||||
|
|
||||||
private final Logger.ALogger log = Logger.of(this.getClass());
|
private final Logger.ALogger log = Logger.of(this.getClass());
|
||||||
|
|
||||||
public boolean fragmentFound(String md) {
|
public boolean listFragmentFound(String md) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
if(md != null) {
|
if(md != null) {
|
||||||
String trimmed = md.trim();
|
String trimmed = md.trim();
|
||||||
if(trimmed.startsWith(MarkdownModel.MD_FRAG_OPEN) &&
|
if(trimmed.startsWith(MarkdownModel.MD_LIST_FRAG_OPEN) &&
|
||||||
trimmed.endsWith(MarkdownModel.MD_FRAG_CLOSE)) {
|
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;
|
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.
|
* HTML comment syntax.
|
||||||
*/
|
*/
|
||||||
public String expandFragment(String md) {
|
public String expandListFragment(String md) {
|
||||||
int fragCloseIdx = md.lastIndexOf(MarkdownModel.MD_FRAG_CLOSE);
|
int fragCloseIdx = md.lastIndexOf(MarkdownModel.MD_LIST_FRAG_CLOSE);
|
||||||
return md.substring(0, fragCloseIdx) + FRAGMENT;
|
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
|
* Note, the space before opening bracket is intentional
|
||||||
* so tag injection can happen directly alongside other
|
* so tag injection can happen directly alongside other
|
||||||
|
@ -68,4 +134,8 @@ public class ShortcutsService {
|
||||||
*/
|
*/
|
||||||
private static final String FRAGMENT =
|
private static final String FRAGMENT =
|
||||||
" <!-- .element: class=\"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.octicons(offline)/octicons.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="@deps.fontawesome(offline)/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
|
<link href="@deps.fontawesome(offline)/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>
|
||||||
@SlideshowStyle()
|
@SlideshowStyle()
|
||||||
|
@SlideshowCodeFragHighlightStyle()
|
||||||
@if(ssm.hasThemeOverride()) {
|
@if(ssm.hasThemeOverride()) {
|
||||||
<style>
|
<style>
|
||||||
@Html(ssm.fetchThemeOverride())
|
@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) {
|
@if(offline) {
|
||||||
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/notes/notes.js", async: true },
|
{ 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(); } },
|
{ 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()) {
|
@if(ssm.mathEnabled()) {
|
||||||
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/math/math.js", async: true }
|
{ src: "@deps.revealjs(offline, ssm.fetchRevealVersionOverride())/plugin/math/math.js", async: true }
|
||||||
}
|
}
|
||||||
|
|
|
@ -409,6 +409,9 @@ gitpitch {
|
||||||
fontawesome = "4.6.3"
|
fontawesome = "4.6.3"
|
||||||
octicons = "3.5.0"
|
octicons = "3.5.0"
|
||||||
highlightjs = "9.6.0"
|
highlightjs = "9.6.0"
|
||||||
|
highlight {
|
||||||
|
plugin = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
github {
|
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