import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import be.nikiroo.fanfix.Instance;
import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.bundles.UiConfig;
import be.nikiroo.fanfix.data.Chapter;
import be.nikiroo.fanfix.data.MetaData;
import be.nikiroo.fanfix.data.Paragraph;
import be.nikiroo.fanfix.data.Story;
import be.nikiroo.fanfix.library.WebLibraryServer.WLoginResult;
import be.nikiroo.fanfix.library.web.WebLibraryServerIndex;
+import be.nikiroo.fanfix.library.web.templates.WebLibraryServerTemplates;
import be.nikiroo.fanfix.reader.TextOutput;
import be.nikiroo.utils.IOUtils;
import be.nikiroo.utils.NanoHTTPD;
private NanoHTTPD server;
protected TraceHandler tracer = new TraceHandler();
+ WebLibraryServerTemplates templates = WebLibraryServerTemplates
+ .getInstance();
+
abstract protected WLoginResult login(String who, String cookie);
abstract protected WLoginResult login(String who, String key,
}
Response rep = null;
- if (!login.isSuccess() && WebLibraryUrls.isSupportedUrl(uri)) {
- rep = loginPage(login, uri);
- }
+ try {
+ if (!login.isSuccess()
+ && WebLibraryUrls.isSupportedUrl(uri, true)) {
+ rep = loginPage(login, uri);
+ }
- if (rep == null) {
- try {
- if (WebLibraryUrls.isSupportedUrl(uri)) {
+ if (rep == null) {
+ if (WebLibraryUrls.isSupportedUrl(uri, false)) {
if (WebLibraryUrls.INDEX_URL.equals(uri)) {
rep = root(session, cookies, login);
} else if (WebLibraryUrls.VERSION_URL.equals(uri)) {
mimeType = "text/html";
} else if (uri.endsWith(".js")) {
mimeType = "text/javascript";
+ } else if (uri.endsWith(".png")) {
+ mimeType = "image/png";
+ } else if (uri.endsWith(".ico")) {
+ mimeType = "image/x-icon";
+ } else if (uri.endsWith(".java")) {
+ mimeType = "text/plain";
}
rep = newChunkedResponse(Status.OK, mimeType,
in);
NanoHTTPD.MIME_PLAINTEXT, "Not Found");
}
}
- } catch (Exception e) {
- Instance.getInstance().getTraceHandler().error(
- new IOException("Cannot process web request",
- e));
- rep = newFixedLengthResponse(Status.INTERNAL_ERROR,
- NanoHTTPD.MIME_PLAINTEXT, "An error occured");
}
+ } catch (Exception e) {
+ Instance.getInstance().getTraceHandler().error(
+ new IOException("Cannot process web request", e));
+ rep = newFixedLengthResponse(Status.INTERNAL_ERROR,
+ NanoHTTPD.MIME_PLAINTEXT, "An error occured");
}
return rep;
this.tracer = tracer;
}
- private Response loginPage(WLoginResult login, String uri) {
- StringBuilder builder = new StringBuilder();
-
- appendPreHtml(builder, true);
+ private Response loginPage(WLoginResult login, String uri)
+ throws IOException {
+ List<Template> content = new ArrayList<Template>();
if (login.isBadLogin()) {
- builder.append("<div class='error'>Bad login or password</div>");
+ content.add(templates.message("Bad login or password", true));
} else if (login.isBadCookie()) {
- builder.append("<div class='error'>Your session timed out</div>");
+ content.add(templates.message("Your session timed out", true));
}
- if (WebLibraryUrls.LOGOUT_URL.equals(uri)) {
- uri = WebLibraryUrls.INDEX_URL;
- }
-
- builder.append(
- "<form method='POST' action='" + uri + "' class='login'>\n");
- builder.append(
- "<p>You must be logged into the system to see the stories.</p>");
- builder.append("\t<input type='text' name='login' />\n");
- builder.append("\t<input type='password' name='password' />\n");
- builder.append("\t<input type='submit' value='Login' />\n");
- builder.append("</form>\n");
-
- appendPostHtml(builder);
+ content.add(templates.login(uri));
- return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN,
- NanoHTTPD.MIME_HTML, builder.toString());
+ return NanoHTTPD.newChunkedResponse(Status.FORBIDDEN,
+ NanoHTTPD.MIME_HTML,
+ templates.index(true, false, content).read());
}
private Response root(IHTTPSession session, Map<String, String> cookies,
WLoginResult login) throws IOException {
BasicLibrary lib = Instance.getInstance().getLibrary();
MetaResultList result = new MetaResultList(metas(login));
- StringBuilder builder = new StringBuilder();
-
- appendPreHtml(builder, true);
Map<String, String> params = session.getParms();
// TODO: javascript in realtime, using visible=false + hide [submit]
- builder.append("<form class='browser'>\n");
- builder.append("<div class='breadcrumbs'>\n");
-
- builder.append("\t<select name='browser'>");
- appendOption(builder, 2, "", "", browser);
- appendOption(builder, 2, "Sources", "sources", browser);
- appendOption(builder, 2, "Authors", "authors", browser);
- appendOption(builder, 2, "Tags", "tags", browser);
- builder.append("\t</select>\n");
+ List<Template> selects = new ArrayList<Template>();
+ boolean sourcesSel = false;
+ boolean authorsSel = false;
+ boolean tagsSel = false;
if (!browser.isEmpty()) {
- builder.append("\t<select name='browser2'>");
+ List<Template> options = new ArrayList<Template>();
+
if (browser.equals("sources")) {
+ sourcesSel = true;
filterSource = browser2.isEmpty() ? filterSource : browser2;
+
// TODO: if 1 group -> no group
- appendOption(builder, 2, "", "", browser2);
Map<String, List<String>> sources = result.getSourcesGrouped();
for (String source : sources.keySet()) {
- appendOption(builder, 2, source, source, browser2);
+ options.add(
+ templates.browserOption(source, source, browser2));
}
} else if (browser.equals("authors")) {
+ authorsSel = true;
filterAuthor = browser2.isEmpty() ? filterAuthor : browser2;
+
// TODO: if 1 group -> no group
- appendOption(builder, 2, "", "", browser2);
Map<String, List<String>> authors = result.getAuthorsGrouped();
for (String author : authors.keySet()) {
- appendOption(builder, 2, author, author, browser2);
+ options.add(
+ templates.browserOption(author, author, browser2));
}
} else if (browser.equals("tags")) {
+ tagsSel = true;
filterTag = browser2.isEmpty() ? filterTag : browser2;
- appendOption(builder, 2, "", "", browser2);
+
for (String tag : result.getTags()) {
- appendOption(builder, 2, tag, tag, browser2);
+ options.add(templates.browserOption(tag, tag, browser2));
}
}
- builder.append("\t</select>\n");
+
+ selects.add(templates.browserSelect("browser2", browser2, options));
}
if (!browser2.isEmpty()) {
+ List<Template> options = new ArrayList<Template>();
+
if (browser.equals("sources")) {
filterSource = browser3.isEmpty() ? filterSource : browser3;
Map<String, List<String>> sourcesGrouped = result
List<String> sources = sourcesGrouped.get(browser2);
if (sources != null && !sources.isEmpty()) {
// TODO: single empty value
- builder.append("\t<select name='browser3'>");
- appendOption(builder, 2, "", "", browser3);
for (String source : sources) {
- appendOption(builder, 2, source, source, browser3);
+ options.add(templates.browserOption(source, source,
+ browser3));
}
- builder.append("\t</select>\n");
}
} else if (browser.equals("authors")) {
filterAuthor = browser3.isEmpty() ? filterAuthor : browser3;
List<String> authors = authorsGrouped.get(browser2);
if (authors != null && !authors.isEmpty()) {
// TODO: single empty value
- builder.append("\t<select name='browser3'>");
- appendOption(builder, 2, "", "", browser3);
for (String author : authors) {
- appendOption(builder, 2, author, author, browser3);
+ options.add(templates.browserOption(author, author,
+ browser3));
}
- builder.append("\t</select>\n");
}
}
- }
- builder.append("\t<input type='submit' value='Select'/>\n");
- builder.append("</div>\n");
+ selects.add(templates.browserSelect("browser3", browser3, options));
+ }
- // TODO: javascript in realtime, using visible=false + hide [submit]
- builder.append("<div class='filter'>\n");
- builder.append("\t<span class='label'>Filter: </span>\n");
- builder.append(
- "\t<input name='optionName' type='hidden' value='filter' />\n");
- builder.append("\t<input name='optionValue' type='text' value='"
- + filter + "' place-holder='...' />\n");
- builder.append("\t<input name='optionNo' type='submit' value='x' />");
- builder.append(
- "\t<input name='submit' type='submit' value='Filter' />\n");
- builder.append("</div>\n");
- builder.append("</form>\n");
-
- builder.append("\t<div class='books'>");
+ List<Template> booklines = new ArrayList<Template>();
for (MetaData meta : result.getMetas()) {
if (!filter.isEmpty() && !meta.getTitle().toLowerCase()
.contains(filter.toLowerCase())) {
continue;
}
- builder.append("<div class='book_line'>");
- builder.append("<a href='");
- builder.append(
- WebLibraryUrls.getViewUrl(meta.getLuid(), null, null));
- builder.append("'");
- builder.append(" class='link'>");
-
- if (lib.isCached(meta.getLuid())) {
- // â—‰ = ◉
- builder.append(
- "<span class='cache_icon cached'>◉</span>");
- } else {
- // â—‹ = ○
- builder.append(
- "<span class='cache_icon uncached'>○</span>");
- }
- builder.append("<span class='luid'>");
- builder.append(meta.getLuid());
- builder.append("</span>");
- builder.append("<span class='title'>");
- builder.append(meta.getTitle());
- builder.append("</span>");
- builder.append("<span class='author'>");
+ String author = "";
if (meta.getAuthor() != null && !meta.getAuthor().isEmpty()) {
- builder.append("(").append(meta.getAuthor()).append(")");
+ author = "(" + meta.getAuthor() + ")";
}
- builder.append("</span>");
- builder.append("</a></div>\n");
+
+ booklines.add(templates.bookline( //
+ meta.getLuid(), //
+ WebLibraryUrls.getViewUrl(meta.getLuid(), null, null), //
+ meta.getTitle(), //
+ author, //
+ lib.isCached(meta.getLuid()) //
+ ));
}
- builder.append("</div>");
- appendPostHtml(builder);
- return NanoHTTPD.newFixedLengthResponse(builder.toString());
+ // Add the browser in front of the booklines
+ booklines.add(0, templates.browser(browser, filter, selects));
+
+ return newInputStreamResponse(NanoHTTPD.MIME_HTML,
+ templates.index(true, false, booklines).read());
}
private Response getViewer(Map<String, String> cookies, String uri,
NanoHTTPD.MIME_PLAINTEXT, "Story not found");
}
- StringBuilder builder = new StringBuilder();
- appendPreHtml(builder, false);
-
// For images documents, always go to the images if not chap 0 desc
if (story.getMeta().isImageDocument()) {
if (chapter > 0 && paragraph <= 0)
String first, previous, next, last;
- StringBuilder content = new StringBuilder();
-
- String disabledLeft = "";
- String disabledRight = "";
- String disabledZoomReal = "";
- String disabledZoomWidth = "";
- String disabledZoomHeight = "";
+ boolean disabledLeft = false;
+ boolean disabledRight = false;
+ boolean disabledZoomReal = false;
+ boolean disabledZoomWidth = false;
+ boolean disabledZoomWidthLimited = false;
+ boolean disabledZoomHeight = false;
+ Template viewerItem = null;
if (paragraph <= 0) {
first = WebLibraryUrls.getViewUrl(luid, 0, null);
previous = WebLibraryUrls.getViewUrl(luid,
last = WebLibraryUrls.getViewUrl(luid,
story.getChapters().size(), null);
- StringBuilder desc = new StringBuilder();
-
+ Template desc = null;
if (chapter <= 0) {
- desc.append("<h1 class='title'>");
- desc.append(story.getMeta().getTitle());
- desc.append("</h1>\n");
- desc.append("<div class='desc'>\n");
- desc.append("\t<a href='" + next + "' class='cover'>\n");
- desc.append("\t\t<img src='/story/" + luid + "/cover'/>\n");
- desc.append("\t</a>\n");
- desc.append("\t<table class='details'>\n");
+ List<Template> desclines = new ArrayList<Template>();
Map<String, String> details = BasicLibrary
.getMetaDesc(story.getMeta());
for (String key : details.keySet()) {
- appendTableRow(desc, 2, key, details.get(key));
+ desclines.add(templates.viewerDescline(key,
+ details.get(key)));
}
- desc.append("\t</table>\n");
- desc.append("</div>\n");
- desc.append("<h1 class='title'>Description</h1>\n");
+
+ desc = templates.viewerDesc( //
+ story.getMeta().getTitle(), //
+ next, //
+ WebLibraryUrls.getStoryUrlCover(luid), //
+ desclines //
+ );
+ }
+
+ String content;
+ if (chap.getParagraphs().size() <= 0) {
+ content = "No content provided.";
+ } else {
+ content = new TextOutput(false).convert(chap, chapter > 0);
}
- content.append("<div class='viewer text'>\n");
- content.append(desc);
- String description = new TextOutput(false).convert(chap,
- chapter > 0);
- content.append(chap.getParagraphs().size() <= 0
- ? "No content provided."
- : description);
- content.append("</div>\n");
+ viewerItem = templates.viewerText(desc, content);
if (chapter <= 0)
- disabledLeft = " disabled='disbaled'";
+ disabledLeft = true;
if (chapter >= story.getChapters().size())
- disabledRight = " disabled='disbaled'";
+ disabledRight = true;
} else {
first = WebLibraryUrls.getViewUrl(luid, chapter, 1);
previous = WebLibraryUrls.getViewUrl(luid, chapter,
chap.getParagraphs().size());
if (paragraph <= 1)
- disabledLeft = " disabled='disbaled'";
+ disabledLeft = true;
if (paragraph >= chap.getParagraphs().size())
- disabledRight = " disabled='disbaled'";
+ disabledRight = true;
// First -> previous *chapter*
if (chapter > 0)
- disabledLeft = "";
+ disabledLeft = false;
first = WebLibraryUrls.getViewUrl(luid,
(Math.max(chapter - 1, 0)), null);
if (paragraph <= 1) {
}
if (para.getType() == ParagraphType.IMAGE) {
- String zoomStyle = "max-width: 100%;";
- disabledZoomWidth = " disabled='disabled'";
+ // default values:
+ String zoomStyle = "max-width: 800px;";
+ disabledZoomWidthLimited = true;
+
String zoomOption = cookies.get("zoom");
if (zoomOption != null && !zoomOption.isEmpty()) {
if (zoomOption.equals("real")) {
zoomStyle = "";
- disabledZoomWidth = "";
- disabledZoomReal = " disabled='disabled'";
+ disabledZoomWidthLimited = false;
+ disabledZoomReal = true;
+ } else if (zoomOption.equals("widthlimited")) {
+ // default
} else if (zoomOption.equals("width")) {
zoomStyle = "max-width: 100%;";
+ disabledZoomWidthLimited = false;
+ disabledZoomWidth = true;
} else if (zoomOption.equals("height")) {
// see height of navbar + optionbar
zoomStyle = "max-height: calc(100% - 128px);";
- disabledZoomWidth = "";
- disabledZoomHeight = " disabled='disabled'";
+ disabledZoomWidthLimited = false;
+ disabledZoomHeight = true;
}
}
- String javascript = "document.getElementById(\"previous\").click(); return false;";
- content.append(String.format("" //
- + "<a class='viewer link' oncontextmenu='%s' href='%s'>"
- + "<img class='viewer img' style='%s' src='%s'/>"
- + "</a>", //
- javascript, //
- next, //
- zoomStyle, //
+ viewerItem = templates.viewerImage(
WebLibraryUrls.getStoryUrl(luid, chapter,
- paragraph)));
+ paragraph), //
+ disabledRight ? null : next, //
+ zoomStyle //
+ );
} else {
- content.append(String.format("" //
- + "<div class='viewer text'>%s</div>", //
- para.getContent()));
+ viewerItem = templates.viewerText(null,
+ new TextOutput(false).convert(para));
}
}
- builder.append(String.format("" //
- + "<div class='bar navbar'>\n" //
- + "\t<a%s class='button first' href='%s'><<</a>\n"//
- + "\t<a%s id='previous' class='button previous' href='%s'><</a>\n" //
- + "\t<div class='gotobox itemsbox'>\n" //
- + "\t\t<div class='button goto'>%d</div>\n" //
- + "\t\t<div class='items goto'>\n", //
- disabledLeft, first, //
- disabledLeft, previous, //
- paragraph > 0 ? paragraph : chapter //
- ));
-
- // List of chap/para links
+ // List of chap/para links for navbar
- appendItemA(builder, 3, WebLibraryUrls.getViewUrl(luid, 0, null),
- "Description", paragraph == 0 && chapter == 0);
+ List<Template> links = new ArrayList<Template>();
+ links.add(templates.viewerLink( //
+ "Description", //
+ WebLibraryUrls.getViewUrl(luid, 0, null), //
+ paragraph == 0 && chapter == 0 //
+ ));
if (paragraph > 0) {
for (int i = 1; i <= chap.getParagraphs().size(); i++) {
- appendItemA(builder, 3,
- WebLibraryUrls.getViewUrl(luid, chapter, i),
- "Image " + i, paragraph == i);
+ links.add(templates.viewerLink( //
+ "Image " + i, //
+ WebLibraryUrls.getViewUrl(luid, chapter, i), //
+ paragraph == i //
+ ));
}
} else {
int i = 1;
chapName += ": " + c.getName();
}
- appendItemA(builder, 3,
- WebLibraryUrls.getViewUrl(luid, i, null), chapName,
- chapter == i);
+ links.add(templates.viewerLink( //
+ chapName, //
+ WebLibraryUrls.getViewUrl(luid, i, null), //
+ chapter == i //
+ ));
i++;
}
}
- builder.append(String.format("" //
- + "\t\t</div>\n" //
- + "\t</div>\n" //
- + "\t<a%s class='button next' href='%s'>></a>\n" //
- + "\t<a%s class='button last' href='%s'>>></a>\n"//
- + "</div>\n", //
- disabledRight, next, //
- disabledRight, last //
- ));
-
- builder.append(content);
-
- builder.append("<div class='bar optionbar ");
+ // Navbar
+
+ Template navbar = templates.viewerNavbar( //
+ paragraph > 0 ? paragraph : chapter, //
+ links, //
+ first, //
+ previous, //
+ next, //
+ last, //
+ disabledLeft, //
+ disabledLeft, //
+ disabledRight, //
+ disabledRight //
+ );
+
+ // Buttons on the optionbar
+
+ List<Template> buttons = new ArrayList<Template>();
+ buttons.add(templates.viewerOptionbarButton( //
+ "Back", "/", "back", false));
if (paragraph > 0) {
- builder.append("s4");
- } else {
- builder.append("s1");
+ buttons.add(templates.viewerOptionbarButton( //
+ "1:1", uri + "?optionName=zoom&optionValue=real",
+ "zoomreal", disabledZoomReal));
+ buttons.add(templates.viewerOptionbarButton( //
+ "]width[",
+ uri + "?optionName=zoom&optionValue=widthlimited",
+ "zoomwidthlimited", disabledZoomWidthLimited));
+ buttons.add(templates.viewerOptionbarButton( //
+ "Width", uri + "?optionName=zoom&optionValue=width",
+ "zoomwidth", disabledZoomWidth));
+ buttons.add(templates.viewerOptionbarButton( //
+ "Height", uri + "?optionName=zoom&optionValue=height",
+ "zoomheight", disabledZoomHeight));
}
- builder.append("'>\n");
- builder.append(" <a class='button back' href='/'>BACK</a>\n");
- if (paragraph > 0) {
- builder.append(String.format("" //
- + "\t<a%s class='button zoomreal' href='%s'>REAL</a>\n"//
- + "\t<a%s class='button zoomwidth' href='%s'>WIDTH</a>\n"//
- + "\t<a%s class='button zoomheight' href='%s'>HEIGHT</a>\n"//
- + "</div>\n", //
- disabledZoomReal,
- uri + "?optionName=zoom&optionValue=real", //
- disabledZoomWidth,
- uri + "?optionName=zoom&optionValue=width", //
- disabledZoomHeight,
- uri + "?optionName=zoom&optionValue=height" //
- ));
- }
+ // Optionbar
+
+ Template optionbar = templates.viewerOptionbar( //
+ (paragraph > 0) ? 5 : 1, //
+ buttons //
+ );
- appendPostHtml(builder);
- return NanoHTTPD.newFixedLengthResponse(Status.OK,
- NanoHTTPD.MIME_HTML, builder.toString());
+ // Full content
+
+ return newInputStreamResponse(NanoHTTPD.MIME_HTML, //
+ templates.index(false, (paragraph > 0), Arrays.asList( //
+ navbar, //
+ viewerItem, //
+ optionbar //
+ )).read());
} catch (IOException e) {
Instance.getInstance().getTraceHandler()
.error(new IOException("Cannot get image: " + uri, e));
NanoHTTPD.MIME_PLAINTEXT, "Error when processing request");
}
}
-
+
protected Response newInputStreamResponse(String mimeType, InputStream in) {
if (in == null) {
return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "",
}
return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in);
}
-
- private String getContentOf(String file) {
- InputStream in = IOUtils.openResource(WebLibraryServerIndex.class,
- file);
- if (in != null) {
- try {
- return IOUtils.readSmallStream(in);
- } catch (IOException e) {
- Instance.getInstance().getTraceHandler().error(
- new IOException("Cannot get file: index.pre.html", e));
- }
- }
-
- return "";
- }
-
- private void appendPreHtml(StringBuilder builder, boolean banner) {
- String favicon = "favicon.ico";
- String icon = Instance.getInstance().getUiConfig()
- .getString(UiConfig.PROGRAM_ICON);
- if (icon != null) {
- favicon = "icon_" + icon.replace("-", "_") + ".png";
- }
-
- builder.append(
- getContentOf("index.pre.html").replace("favicon.ico", favicon));
-
- if (banner) {
- builder.append("<div class='banner'>\n");
- builder.append("\t<img class='ico' src='/") //
- .append(favicon) //
- .append("'/>\n");
- builder.append("\t<h1>Fanfix</h1>\n");
- builder.append("\t<h2>") //
- .append(Version.getCurrentVersion()) //
- .append("</h2>\n");
- builder.append("</div>\n");
- }
- }
-
- private void appendPostHtml(StringBuilder builder) {
- builder.append(getContentOf("index.post.html"));
- }
-
- private void appendOption(StringBuilder builder, int depth, String name,
- String value, String selected) {
- for (int i = 0; i < depth; i++) {
- builder.append("\t");
- }
- builder.append("<option value='").append(value).append("'");
- if (value.equals(selected)) {
- builder.append(" selected='selected'");
- }
- builder.append(">").append(name).append("</option>\n");
- }
-
- private void appendTableRow(StringBuilder builder, int depth,
- String... tds) {
- for (int i = 0; i < depth; i++) {
- builder.append("\t");
- }
-
- int col = 1;
- builder.append("<tr>");
- for (String td : tds) {
- builder.append("<td class='col");
- builder.append(col++);
- builder.append("'>");
- builder.append(td);
- builder.append("</td>");
- }
- builder.append("</tr>\n");
- }
-
- private void appendItemA(StringBuilder builder, int depth, String link,
- String name, boolean selected) {
- for (int i = 0; i < depth; i++) {
- builder.append("\t");
- }
-
- builder.append("<a href='");
- builder.append(link);
- builder.append("' class='item goto");
- if (selected) {
- builder.append(" selected");
- }
- builder.append("'>");
- builder.append(name);
- builder.append("</a>\n");
- }
}