forked from j62/ctbrec
1
0
Fork 0

Add index and search to documentation app

This commit is contained in:
0xboobface 2019-04-09 12:06:38 +02:00
parent 482a7289b7
commit f0bf6c5d7c
14 changed files with 20631 additions and 72 deletions

View File

@ -0,0 +1,106 @@
package ctbrec.docs;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractDocServlet extends HttpServlet {
private static final transient Logger LOG = LoggerFactory.getLogger(AbstractDocServlet.class);
String loadFile(String resource) throws IOException {
InputStream resourceAsStream = getClass().getResourceAsStream(resource);
if(resourceAsStream == null) {
throw new FileNotFoundException();
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int length = 0;
byte[] buffer = new byte[1024];
while( (length = resourceAsStream.read(buffer)) >= 0 ) {
out.write(buffer, 0, length);
}
return new String(out.toByteArray(), "utf-8");
}
String getHeader() throws IOException {
return loadFile("/html/docs/header.html");
}
String getFooter() throws IOException {
return loadFile("/html/docs/footer.html");
}
List<String> getPages() throws IOException {
List<String> pages = new ArrayList<>();
URL resource = getClass().getResource("/html/docs");
if(Objects.equals(resource.getProtocol(), "file")) {
LOG.debug("FILE {}", resource.toString());
indexDirectory(resource, pages);
} else if(Objects.equals(resource.getProtocol(), "jar")) {
LOG.debug("JAR {}", resource.toString());
indexJar(resource, pages);
}
pages.add("index.md");
Collections.sort(pages, (a, b) -> a.compareToIgnoreCase(b));
return pages;
}
private void indexJar(URL resource, List<String> pages) throws IOException {
String fileUrl = resource.getFile();
int colon = fileUrl.indexOf(':');
int exclamation = fileUrl.indexOf('!');
String jar = fileUrl.substring(colon + 1, exclamation);
String internalFile = fileUrl.substring(exclamation + 2);
try (JarFile jarFile = new JarFile(jar)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if (name.startsWith(internalFile) && name.toLowerCase().endsWith(".md")) {
pages.add(name.substring(name.lastIndexOf('/') + 1));
}
}
}
}
private void indexDirectory(URL resource, List<String> pages) {
File docs = new File(resource.getFile());
String[] files = docs.list((dir, name) -> name.toLowerCase().endsWith(".md"));
for (String file : files) {
pages.add(file);
}
}
String loadMarkdown(String path) throws IOException {
String resource = "/html" + path;
return loadFile(resource);
}
protected void error(HttpServletResponse resp, int status, String message) throws IOException {
resp.setStatus(status);
resp.getWriter().println(getHeader());
String html = loadFile("/html/docs/" + status + ".html");
if(message == null || message.trim().isEmpty()) {
message = "";
}
html = html.replace("{message}", message);
resp.getWriter().println(html);
resp.getWriter().println(getFooter());
}
}

View File

@ -41,10 +41,16 @@ public class DocServer {
MarkdownServlet markdownServlet = new MarkdownServlet();
ServletHolder holder = new ServletHolder(markdownServlet);
handler.addServletWithMapping(holder, "/docs/*");
AbstractDocServlet searchServlet = new SearchServlet();
holder = new ServletHolder(searchServlet);
handler.addServletWithMapping(holder, "/search/*");
StaticFileServlet staticFileServlet = new StaticFileServlet();
holder = new ServletHolder(staticFileServlet);
handler.addServletWithMapping(holder, "/static/*");
try {
server.start();
started = true;

View File

@ -1,46 +1,59 @@
package ctbrec.docs;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.options.MutableDataSet;
public class MarkdownServlet extends HttpServlet {
public class MarkdownServlet extends AbstractDocServlet {
private static final transient Logger LOG = LoggerFactory.getLogger(MarkdownServlet.class);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getRequestURI();
LOG.debug("Path: [{}]", path);
LOG.trace("Path: [{}]", path);
try {
String md = loadMarkdown(path);
String html = markdownToHtml(md);
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println(getHeader());
resp.getWriter().println(html);
resp.getWriter().println(getFooter());
if(Objects.equal(path, "/docs/index.md")) {
listPages(resp);
} else {
String md = loadMarkdown(path);
String html = markdownToHtml(md);
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println(getHeader());
resp.getWriter().println(html);
resp.getWriter().println(getFooter());
}
} catch (FileNotFoundException e) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
resp.setContentType("text/plain");
resp.getWriter().println("Hö?");
resp.getWriter().println();
error(resp, HttpServletResponse.SC_NOT_FOUND, "");
}
}
private void listPages(HttpServletResponse resp) throws IOException {
List<String> pages = getPages();
String html = "<ul>";
for (String page : pages) {
html += "<li><a href=\"/docs/" + page + "\">" + page + "</a></li>";
}
html += "</ul>";
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println(getHeader());
resp.getWriter().println(html);
resp.getWriter().println(getFooter());
}
private String markdownToHtml(String markdown) {
MutableDataSet options = new MutableDataSet();
Parser parser = Parser.builder(options).build();
@ -49,31 +62,4 @@ public class MarkdownServlet extends HttpServlet {
String html = renderer.render(document);
return html;
}
private String loadMarkdown(String path) throws IOException {
String resource = "/html" + path;
return loadFile(resource);
}
private String loadFile(String resource) throws IOException {
InputStream resourceAsStream = getClass().getResourceAsStream(resource);
if(resourceAsStream == null) {
throw new FileNotFoundException();
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int length = 0;
byte[] buffer = new byte[1024];
while( (length = resourceAsStream.read(buffer)) >= 0 ) {
out.write(buffer, 0, length);
}
return new String(out.toByteArray(), "utf-8");
}
private String getHeader() throws IOException {
return loadFile("/html/docs/header.html");
}
private String getFooter() throws IOException {
return loadFile("/html/docs/footer.html");
}
}

View File

@ -0,0 +1,49 @@
package ctbrec.docs;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
public class SearchServlet extends AbstractDocServlet {
private static final String Q = "term";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(req.getParameter(Q) == null) {
error(resp, HttpServletResponse.SC_BAD_REQUEST, "Parameter \""+Q+"\" is missing");
return;
}
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
JSONArray result = new JSONArray();
List<String> pages = getPages();
String q = req.getParameter(Q).toLowerCase();
String[] tokens = q.split("\\s+");
for (String page : pages) {
try {
String content = loadMarkdown("/docs/" + page).toLowerCase();
boolean allFound = true;
for (String token : tokens) {
if(!content.contains(token)) {
allFound = false;
}
}
if(allFound) {
result.put(page);
}
} catch(FileNotFoundException e) {
// virtual page like index.md -> ignore
}
}
resp.getWriter().println(result.toString());
}
}

View File

@ -0,0 +1,3 @@
<h1>400 Bad Request</h1>
<p>{message}</p>
<a href="/docs/index.md">Try something else!</a>

View File

@ -0,0 +1,2 @@
<h1>404 File Not Found</h1>
<a href="/docs/index.md">Try something else!</a>

View File

@ -1,5 +1,5 @@
How To Run The Server
=======================
------------
The archive you downloaded contains a `server.bat` or `server.sh`, which can be used to start the server. On the first start, the server uses a default configuration. Once you terminate the server by pressing <kbd>ctrl</kbd> + <kbd>c</kbd>, the config is stored in your user home.
On Windows that is `C:\Users\{your user name}\AppData\Roaming\ctbrec\server.json`
@ -8,4 +8,4 @@ On Linux it is `~/.config/ctbrec/server.json`
On macOS it is `/Users/{your user name}/Library/Preferences/ctbrec`
You can open this file in a text editor and change it to your likings. You probably only want to change httpPort and recordingsDir. Most of the other stuff is irrelevant since the server and CTB Recorder use the same config file format. When the server is running, you can connect to it with CTB Recorder by changing the setting "Record location" to "Remote".
You can open this file in a text editor and change it to your likings. You probably only want to change `httpPort` and `recordingsDir`. Most of the other stuff is irrelevant since the server and CTB Recorder use the same config file format. When the server is running, you can connect to it with CTB Recorder by changing the setting "Record location" to "Remote".

View File

@ -59,11 +59,22 @@
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Plugin JavaScript -->
<script src="/static/vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="/static/vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
<!-- Custom scripts for this template -->
<script src="/static/freelancer.min.js"></script>
<script>
$( "#search" ).autocomplete({
source: "/search",
minLength: 2,
select: function( event, ui ) {
location.href = "/docs/" + ui.item.value;
}
});
</script>
</body>

View File

@ -29,32 +29,47 @@
<!-- Plugin CSS -->
<link href="/static/vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
<link href="/static/vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
<!-- Custom styles for this template -->
<link href="/static/freelancer.min.css" rel="stylesheet">
<link href="/static/freelancer.css" rel="stylesheet">
<link rel="shortcut icon" href="/static/favicon.png" type="image/x-icon" />
<style>
.ui-front {
z-index: 2000;
}
</style>
</head>
<body id="page-top">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav">
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="#page-top"><img src="/static/icon64.png" alt="Logo"/>CTB Recorder</a>
<a class="navbar-brand js-scroll-trigger" href="/docs/index.md"><img src="/static/icon64.png" alt="Logo"/>CTB Recorder</a>
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu
<i class="fa fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#about">Download</a>
<div class="ui-widget">
<label for="search">Search: </label>
<input id="search" style="margin-top: .7rem" placeholder="Search">
</div>
<!--
<div class="ui-widget"><input id="search" class="ui-autocomplete-input" autocomplete="off" placeholder="Search" style="margin-top: .7rem"></div>
-->
</li>
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#portfolio">Screenshots</a>
<a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="/docs/index.md">Index</a>
</li>
<!--
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded js-scroll-trigger" href="#faq">FAQ</a>
</li>
@ -66,30 +81,27 @@
<img src="/static/button-red.png" alt="Buy a coffee" style="width:150px; padding-top: 7px"/>
</a>
</li>
-->
</ul>
</div>
</div>
</nav>
<!-- Header -->
<!-- Header -
<header class="masthead bg-primary text-white text-center">
<div class="container">
<!--
<img class="img-fluid mb-5 d-block mx-auto" src="img/profile.png" alt="">
-->
<h1 class="text-uppercase mb-0">CTB Recorder</h1>
<hr class="star-light">
<h2 class="font-weight-light mb-0">A free recording software for different camsites.<br/>Currently supported: BongaCams, Cam4, CamSoda, Chaturbate, FC2Live, LiveJasmin, MyFreeCams, Streamate</h2>
</div>
</header>
-->
<!-- Source Code Section -->
<section id="code">
<div class="container">
<h2 class="text-center text-uppercase text-secondary mb-0">Source Code</h2>
<hr class="star-dark mb-5">
<div class="row">
<div class="col-lg-8 mx-auto text-center">
<div class="col-lg-10 mx-auto">
<p class="lead">

View File

@ -1,14 +0,0 @@
hallo
================
- sadf
- bsdf
- csdf
was geht?
================
```
pre
formatted
text
or what?
```

View File

@ -0,0 +1,382 @@
body {
color: #2C3E50;
font-family: 'Lato';
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #2C3E50;
font-weight: 700;
font-family: 'Montserrat';
}
hr.star-light,
hr.star-dark {
max-width: 15rem;
padding: 0;
text-align: center;
border: none;
border-top: solid 0.25rem;
margin-top: 2.5rem;
margin-bottom: 2.5rem;
}
hr.star-light:after,
hr.star-dark:after {
position: relative;
top: -.8em;
display: inline-block;
padding: 0 0.25em;
content: '\f005';
font-family: FontAwesome;
font-size: 2em;
}
hr.star-light {
border-color: #fff;
}
hr.star-light:after {
color: #fff;
background-color: #dc4444;
}
hr.star-dark {
border-color: #2C3E50;
}
hr.star-dark:after {
color: #2C3E50;
background-color: white;
}
section {
padding: 9rem 6rem 0 6rem;
}
section h2 {
font-size: 2.25rem;
line-height: 2rem;
}
@media (min-width: 992px) {
section h2 {
font-size: 3rem;
line-height: 2.5rem;
}
}
.btn-xl {
padding: 1rem 1.75rem;
font-size: 1.25rem;
}
.btn-social {
width: 3.25rem;
height: 3.25rem;
font-size: 1.25rem;
line-height: 2rem;
}
.scroll-to-top {
z-index: 1042;
right: 1rem;
bottom: 1rem;
display: none;
}
.scroll-to-top a {
width: 3.5rem;
height: 3.5rem;
background-color: rgba(33, 37, 41, 0.5);
line-height: 3.1rem;
}
#mainNav {
padding-top: 1rem;
padding-bottom: 1rem;
font-weight: 700;
font-family: 'Montserrat';
}
#mainNav .navbar-brand {
color: #fff;
}
#mainNav .navbar-nav {
margin-top: 1rem;
letter-spacing: 0.0625rem;
}
#mainNav .navbar-nav li.nav-item a.nav-link {
color: #fff;
}
#mainNav .navbar-nav li.nav-item a.nav-link:hover {
color: #dc4444;
}
#mainNav .navbar-nav li.nav-item a.nav-link:active, #mainNav .navbar-nav li.nav-item a.nav-link:focus {
color: #fff;
}
#mainNav .navbar-nav li.nav-item a.nav-link.active {
color: #dc4444;
}
#mainNav .navbar-toggler {
font-size: 80%;
padding: 0.8rem;
}
@media (min-width: 992px) {
#mainNav {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
-webkit-transition: padding-top 0.3s, padding-bottom 0.3s;
-moz-transition: padding-top 0.3s, padding-bottom 0.3s;
transition: padding-top 0.3s, padding-bottom 0.3s;
}
#mainNav .navbar-brand {
font-size: 2em;
-webkit-transition: font-size 0.3s;
-moz-transition: font-size 0.3s;
transition: font-size 0.3s;
}
#mainNav .navbar-nav {
margin-top: 0;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link.active {
color: #fff;
background: #dc4444;
}
#mainNav .navbar-nav > li.nav-item > a.nav-link.active:active, #mainNav .navbar-nav > li.nav-item > a.nav-link.active:focus, #mainNav .navbar-nav > li.nav-item > a.nav-link.active:hover {
color: #fff;
background: #dc4444;
}
#mainNav.navbar-shrink {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
#mainNav.navbar-shrink .navbar-brand {
font-size: 1.5em;
}
}
header.masthead {
padding-top: calc(6rem + 72px);
padding-bottom: 6rem;
}
header.masthead h1 {
font-size: 3rem;
line-height: 3rem;
}
header.masthead h2 {
font-size: 1.3rem;
font-family: 'Lato';
}
@media (min-width: 992px) {
header.masthead {
padding-top: calc(6rem + 106px);
padding-bottom: 6rem;
}
header.masthead h1 {
font-size: 4.75em;
line-height: 4rem;
}
header.masthead h2 {
font-size: 1.75em;
}
}
.portfolio {
margin-bottom: -15px;
}
.portfolio .portfolio-item {
position: relative;
display: block;
max-width: 25rem;
margin-bottom: 15px;
}
.portfolio .portfolio-item .portfolio-item-caption {
-webkit-transition: all ease 0.5s;
-moz-transition: all ease 0.5s;
transition: all ease 0.5s;
opacity: 0;
background-color: rgba(220, 68, 68, 0.9);
}
.portfolio .portfolio-item .portfolio-item-caption:hover {
opacity: 1;
}
.portfolio .portfolio-item .portfolio-item-caption .portfolio-item-caption-content {
font-size: 1.5rem;
}
@media (min-width: 576px) {
.portfolio {
margin-bottom: -30px;
}
.portfolio .portfolio-item {
margin-bottom: 30px;
}
}
.portfolio-modal .portfolio-modal-dialog {
padding: 3rem 1rem;
min-height: calc(100vh - 2rem);
margin: 1rem calc(1rem - 8px);
position: relative;
z-index: 2;
-moz-box-shadow: 0 0 3rem 1rem rgba(0, 0, 0, 0.5);
-webkit-box-shadow: 0 0 3rem 1rem rgba(0, 0, 0, 0.5);
box-shadow: 0 0 3rem 1rem rgba(0, 0, 0, 0.5);
}
.portfolio-modal .portfolio-modal-dialog .close-button {
position: absolute;
top: 2rem;
right: 2rem;
}
.portfolio-modal .portfolio-modal-dialog .close-button i {
line-height: 38px;
}
.portfolio-modal .portfolio-modal-dialog h2 {
font-size: 2rem;
}
@media (min-width: 768px) {
.portfolio-modal .portfolio-modal-dialog {
min-height: 100vh;
padding: 5rem;
margin: 3rem calc(3rem - 8px);
}
.portfolio-modal .portfolio-modal-dialog h2 {
font-size: 3rem;
}
}
.floating-label-form-group {
position: relative;
border-bottom: 1px solid #e9ecef;
}
.floating-label-form-group input,
.floating-label-form-group textarea {
font-size: 1.5em;
position: relative;
z-index: 1;
padding-right: 0;
padding-left: 0;
resize: none;
border: none;
border-radius: 0;
background: none;
box-shadow: none !important;
}
.floating-label-form-group label {
font-size: 0.85em;
line-height: 1.764705882em;
position: relative;
z-index: 0;
top: 2em;
display: block;
margin: 0;
-webkit-transition: top 0.3s ease, opacity 0.3s ease;
-moz-transition: top 0.3s ease, opacity 0.3s ease;
-ms-transition: top 0.3s ease, opacity 0.3s ease;
transition: top 0.3s ease, opacity 0.3s ease;
vertical-align: middle;
vertical-align: baseline;
opacity: 0;
}
.floating-label-form-group:not(:first-child) {
padding-left: 14px;
border-left: 1px solid #e9ecef;
}
.floating-label-form-group-with-value label {
top: 0;
opacity: 1;
}
.floating-label-form-group-with-focus label {
color: #dc4444;
}
form .row:first-child .floating-label-form-group {
border-top: 1px solid #e9ecef;
}
.footer {
padding-top: 5rem;
padding-bottom: 5rem;
background-color: #2C3E50;
color: #fff;
}
.copyright {
background-color: #1a252f;
}
a {
color: #dc4444;
}
a:focus, a:hover, a:active {
color: #c82525;
}
.btn {
border-width: 2px;
}
.bg-primary {
background-color: #dc4444 !important;
}
.bg-secondary {
background-color: #2C3E50 !important;
}
.text-primary {
color: #dc4444 !important;
}
.text-secondary {
color: #2C3E50 !important;
}
.btn-primary {
background-color: #dc4444;
border-color: #dc4444;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
background-color: #c82525;
border-color: #c82525;
}
.btn-secondary {
background-color: #2C3E50;
border-color: #2C3E50;
}
.btn-secondary:hover, .btn-secondary:focus, .btn-secondary:active {
background-color: #1a252f;
border-color: #1a252f;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff