Better (md) index page, Improve path handling
This commit is contained in:
parent
6db5cdd701
commit
4484b87c32
@ -6,7 +6,6 @@ import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -14,13 +13,14 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.mrletsplay.mdblog.blog.Post;
|
||||
import me.mrletsplay.mdblog.markdown.MdParser;
|
||||
import me.mrletsplay.mdblog.markdown.MdRenderer;
|
||||
import me.mrletsplay.mdblog.util.PostPath;
|
||||
import me.mrletsplay.simplehttpserver.dom.html.HtmlDocument;
|
||||
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
|
||||
import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
|
||||
import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
|
||||
import me.mrletsplay.simplehttpserver.http.document.FileDocument;
|
||||
import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
|
||||
import me.mrletsplay.simplehttpserver.http.response.TextResponse;
|
||||
import me.mrletsplay.simplehttpserver.http.server.HttpServer;
|
||||
|
||||
public class MdBlog {
|
||||
@ -32,7 +32,12 @@ public class MdBlog {
|
||||
private static HttpServer server;
|
||||
private static WatchService watchService;
|
||||
private static List<WatchKey> watchedDirectories;
|
||||
private static Map<String, Post> posts;
|
||||
private static Map<PostPath, Post> posts;
|
||||
|
||||
private static String
|
||||
indexTemplate,
|
||||
indexSubBlogTemplate,
|
||||
indexPostTemplate;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
server = new HttpServer(HttpServer.newConfigurationBuilder()
|
||||
@ -40,40 +45,53 @@ public class MdBlog {
|
||||
.port(3706)
|
||||
.create());
|
||||
|
||||
server.getDocumentProvider().registerPattern(HttpRequestMethod.GET, "/posts", () -> {
|
||||
createPostsIndex(POSTS_PATH);
|
||||
server.getDocumentProvider().register(HttpRequestMethod.GET, "/posts", () -> {
|
||||
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
|
||||
ctx.redirect("/posts/");
|
||||
});
|
||||
|
||||
server.getDocumentProvider().register(HttpRequestMethod.GET, "/posts/", () -> {
|
||||
createPostsIndex(null);
|
||||
});
|
||||
|
||||
server.getDocumentProvider().registerPattern(HttpRequestMethod.GET, "/posts/{path...}", () -> {
|
||||
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
|
||||
String path = ctx.getPathParameters().get("path");
|
||||
String rawPath = ctx.getPathParameters().get("path");
|
||||
PostPath path = PostPath.parse(rawPath);
|
||||
Post post = posts.get(path);
|
||||
if(post != null) {
|
||||
post.getContent().createContent();
|
||||
return;
|
||||
}
|
||||
|
||||
Path resolved = POSTS_PATH.resolve(path).normalize();
|
||||
if(posts.keySet().stream().anyMatch(p -> p.startsWith(path))) {
|
||||
if(!rawPath.endsWith("/")) {
|
||||
ctx.redirect("/posts/" + rawPath + "/");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(path);
|
||||
createPostsIndex(path);
|
||||
return;
|
||||
}
|
||||
|
||||
Path resolved = POSTS_PATH.resolve(path.toNioPath()).normalize();
|
||||
if(!resolved.startsWith(POSTS_PATH)) {
|
||||
server.getDocumentProvider().getNotFoundDocument().createContent();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Files.isRegularFile(resolved)) {
|
||||
if(!Files.isDirectory(resolved)) {
|
||||
if(!Files.isRegularFile(resolved) || !Files.isReadable(resolved)) {
|
||||
server.getDocumentProvider().getNotFoundDocument().createContent();
|
||||
return;
|
||||
}
|
||||
|
||||
createPostsIndex(resolved);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDocument(resolved).createContent();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ctx.setException(e);
|
||||
server.getDocumentProvider().getErrorDocument().createContent();
|
||||
}
|
||||
});
|
||||
|
||||
@ -81,6 +99,10 @@ public class MdBlog {
|
||||
extractAndRegister("style/index.css");
|
||||
extractAndRegister("style/post.css");
|
||||
|
||||
indexTemplate = Files.readString(extract("template/index.md"));
|
||||
indexPostTemplate = Files.readString(extract("template/index-post.md"));
|
||||
indexSubBlogTemplate = Files.readString(extract("template/index-sub-blog.md"));
|
||||
|
||||
server.start();
|
||||
|
||||
Files.createDirectories(FILES_PATH);
|
||||
@ -109,49 +131,82 @@ public class MdBlog {
|
||||
}
|
||||
}
|
||||
|
||||
private static void createPostsIndex(Path directory) {
|
||||
try {
|
||||
private static void createPostsIndex(PostPath path) {
|
||||
// Generate posts index
|
||||
List<Path> inDir = Files.list(directory)
|
||||
.filter(p -> Files.isDirectory(p) || posts.values().stream().anyMatch(post -> post.getFilePath().equals(p)))
|
||||
.collect(Collectors.toList());
|
||||
Collections.sort(inDir);
|
||||
List<PostPath> allPaths = posts.keySet().stream()
|
||||
.filter(p -> path == null || (p.startsWith(path) && !p.equals(path)))
|
||||
.map(p -> path == null ? p : p.subPath(path.length()))
|
||||
.toList();
|
||||
|
||||
List<PostPath> directories = allPaths.stream()
|
||||
.filter(p -> p.length() > 1)
|
||||
.map(p -> p.subPath(0, 1))
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
List<PostPath> postsInDir = allPaths.stream()
|
||||
.filter(p -> p.length() == 1)
|
||||
.toList();
|
||||
|
||||
// List<Path> inDir = Files.list(directory)
|
||||
// .filter(p -> Files.isDirectory(p) || posts.values().stream().anyMatch(post -> post.getFilePath().equals(p)))
|
||||
// .collect(Collectors.toList());
|
||||
// Collections.sort(inDir);
|
||||
|
||||
String blogName = path == null ? "/" : path.getName();
|
||||
|
||||
HtmlDocument index = new HtmlDocument();
|
||||
index.setTitle("Index of " + directory.getFileName());
|
||||
index.setTitle("Index of " + blogName);
|
||||
index.addStyleSheet("/style/base.css");
|
||||
index.addStyleSheet("/style/index.css");
|
||||
|
||||
HtmlElement ul = new HtmlElement("ul");
|
||||
for(Path p : inDir) {
|
||||
HtmlElement li = new HtmlElement("li");
|
||||
HtmlElement a = new HtmlElement("a");
|
||||
String relPath = directory.relativize(p).toString();
|
||||
if(Files.isRegularFile(p)) relPath = relPath.substring(0, relPath.length() - Post.FILE_EXTENSION.length());
|
||||
// TODO: only works with trailing /
|
||||
a.setAttribute("href", relPath);
|
||||
a.setText(relPath);
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
String indexMd = indexTemplate;
|
||||
indexMd = indexMd.replace("{name}", blogName);
|
||||
indexMd = indexMd.replace("{sub_blogs}", directories.stream()
|
||||
.map(p -> {
|
||||
String subBlogMd = indexSubBlogTemplate;
|
||||
|
||||
index.getBodyNode().appendChild(ul);
|
||||
HtmlElement name = new HtmlElement("a");
|
||||
name.setAttribute("href", p.toString());
|
||||
name.setText(p.getName());
|
||||
subBlogMd = subBlogMd.replace("{name}", name.toString());
|
||||
return subBlogMd;
|
||||
})
|
||||
.collect(Collectors.joining("\n\n")));
|
||||
indexMd = indexMd.replace("{posts}", postsInDir.stream()
|
||||
.map(p -> {
|
||||
String postMd = indexPostTemplate;
|
||||
Post post = posts.get(path.concat(p));
|
||||
System.out.println(p);
|
||||
|
||||
HtmlElement title = new HtmlElement("a");
|
||||
title.setAttribute("href", p.toString());
|
||||
title.setText(post.getName());
|
||||
postMd = postMd.replace("{title}", title.toString());
|
||||
|
||||
postMd = postMd.replace("{author}", post.getMetadata().author());
|
||||
postMd = postMd.replace("{date}", post.getMetadata().date().toString());
|
||||
postMd = postMd.replace("{tags}", post.getMetadata().tags().stream().collect(Collectors.joining(", ")));
|
||||
postMd = postMd.replace("{description}", post.getMetadata().description());
|
||||
return postMd;
|
||||
})
|
||||
.collect(Collectors.joining("\n\n")));
|
||||
|
||||
index.getBodyNode().appendChild(new MdRenderer().render(MdParser.parse(indexMd)));
|
||||
index.createContent();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
|
||||
ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new TextResponse("Failed to create index"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractAndRegister(String path) throws IOException {
|
||||
private static Path extract(String path) throws IOException {
|
||||
Path filePath = FILES_PATH.resolve(path);
|
||||
if(!Files.exists(filePath)) {
|
||||
Files.createDirectories(filePath.getParent());
|
||||
Files.write(filePath, MdBlog.class.getResourceAsStream("/" + path).readAllBytes());
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
server.getDocumentProvider().register(HttpRequestMethod.GET, "/" + path, new FileDocument(filePath));
|
||||
private static void extractAndRegister(String path) throws IOException {
|
||||
server.getDocumentProvider().register(HttpRequestMethod.GET, "/" + path, new FileDocument(extract(path)));
|
||||
}
|
||||
|
||||
private static void updateBlogs() throws IOException {
|
||||
@ -167,9 +222,9 @@ public class MdBlog {
|
||||
.filter(f -> posts.values().stream().noneMatch(p -> p.getFilePath().equals(f)))
|
||||
.forEach(f -> {
|
||||
try {
|
||||
String path = POSTS_PATH.relativize(f).toString();
|
||||
path = path.substring(0, path.length() - Post.FILE_EXTENSION.length());
|
||||
posts.put(path, new Post(f));
|
||||
String postName = f.getFileName().toString();
|
||||
postName = postName.substring(0, postName.length() - Post.FILE_EXTENSION.length());
|
||||
posts.put(PostPath.of(POSTS_PATH.relativize(f).getParent(), postName), new Post(f));
|
||||
} catch (IOException e) {}
|
||||
});
|
||||
}
|
||||
|
@ -7,13 +7,14 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record PostMetadata(Instant date, String title, String author, Set<String> tags) {
|
||||
public record PostMetadata(Instant date, String title, String author, Set<String> tags, String description) {
|
||||
|
||||
public static PostMetadata load(String metadataString) {
|
||||
Instant date = Instant.EPOCH;
|
||||
String title = "Untitled Post";
|
||||
String author = "Unknown Author";
|
||||
Set<String> tags = Collections.emptySet();
|
||||
String description = "No description";
|
||||
Set<String> tags = Collections.singleton("untagged");
|
||||
for(String line : metadataString.split("\n")) {
|
||||
if(line.isBlank()) continue;
|
||||
String[] spl = line.split(":", 2);
|
||||
@ -37,7 +38,7 @@ public record PostMetadata(Instant date, String title, String author, Set<String
|
||||
}
|
||||
}
|
||||
|
||||
return new PostMetadata(date, title, author, tags);
|
||||
return new PostMetadata(date, title, author, tags, description);
|
||||
}
|
||||
|
||||
}
|
||||
|
104
src/main/java/me/mrletsplay/mdblog/util/PostPath.java
Normal file
104
src/main/java/me/mrletsplay/mdblog/util/PostPath.java
Normal file
@ -0,0 +1,104 @@
|
||||
package me.mrletsplay.mdblog.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
public record PostPath(String[] segments) {
|
||||
|
||||
public PostPath {
|
||||
if(segments.length == 0) throw new IllegalArgumentException("Number of segments must be greater than 0");
|
||||
}
|
||||
|
||||
public PostPath getParent() {
|
||||
if(segments.length == 1) return null;
|
||||
return new PostPath(Arrays.copyOfRange(segments, 0, segments.length - 1));
|
||||
}
|
||||
|
||||
public PostPath concat(PostPath other) {
|
||||
String[] newPath = Arrays.copyOf(segments, segments.length + other.length());
|
||||
System.arraycopy(other.segments, 0, newPath, segments.length, other.length());
|
||||
return new PostPath(newPath);
|
||||
}
|
||||
|
||||
public boolean startsWith(PostPath other) {
|
||||
if(other.segments.length > segments.length) return false;
|
||||
for(int i = 0; i < other.segments.length; i++) {
|
||||
if(!segments[i].equals(other.segments[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public PostPath subPath(int fromIndex) throws IllegalArgumentException {
|
||||
if(fromIndex < 0 || fromIndex >= segments.length) throw new IllegalArgumentException("fromIndex must be less than path length");
|
||||
return new PostPath(Arrays.copyOfRange(segments, fromIndex, segments.length));
|
||||
}
|
||||
|
||||
public PostPath subPath(int fromIndex, int toIndex) throws IllegalArgumentException {
|
||||
if(fromIndex < 0 || fromIndex >= segments.length) throw new IllegalArgumentException("fromIndex must be less than path length");
|
||||
if(toIndex <= fromIndex || toIndex >= segments.length) throw new IllegalArgumentException("fromIndex must be less than toIndex and path length");
|
||||
return new PostPath(Arrays.copyOfRange(segments, fromIndex, toIndex));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return segments[segments.length - 1];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return segments.length;
|
||||
}
|
||||
|
||||
public Path toNioPath() {
|
||||
return Paths.get(segments[0], Arrays.copyOfRange(segments, 1, segments.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(segments);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
PostPath other = (PostPath) obj;
|
||||
return Arrays.equals(segments, other.segments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.join("/", segments);
|
||||
}
|
||||
|
||||
public static PostPath parse(String path) {
|
||||
if(path == null || path.isEmpty()) throw new IllegalArgumentException("Path must not be null or empty");
|
||||
return new PostPath(path.split("/"));
|
||||
}
|
||||
|
||||
public static PostPath of(Path path) throws IllegalArgumentException {
|
||||
if(path.getNameCount() == 0) throw new IllegalArgumentException("Path must not be a root path");
|
||||
String[] names = new String[path.getNameCount()];
|
||||
for(int i = 0; i < path.getNameCount(); i++) {
|
||||
names[i] = path.getName(i).toString();
|
||||
}
|
||||
return new PostPath(names);
|
||||
}
|
||||
|
||||
public static PostPath of(Path path, String name) {
|
||||
if(path == null) return new PostPath(new String[] {name});
|
||||
String[] names = new String[path.getNameCount() + 1];
|
||||
for(int i = 0; i < path.getNameCount(); i++) {
|
||||
names[i] = path.getName(i).toString();
|
||||
}
|
||||
names[names.length - 1] = name;
|
||||
return new PostPath(names);
|
||||
}
|
||||
|
||||
}
|
6
src/main/resources/template/index-post.md
Normal file
6
src/main/resources/template/index-post.md
Normal file
@ -0,0 +1,6 @@
|
||||
### {title}
|
||||
#### by {author} on {date}
|
||||
*{tags}*
|
||||
|
||||
|
||||
{description}
|
1
src/main/resources/template/index-sub-blog.md
Normal file
1
src/main/resources/template/index-sub-blog.md
Normal file
@ -0,0 +1 @@
|
||||
### {name}
|
7
src/main/resources/template/index.md
Normal file
7
src/main/resources/template/index.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Index of {name}
|
||||
|
||||
## Sub-Blogs
|
||||
{sub_blogs}
|
||||
|
||||
## Posts
|
||||
{posts}
|
Loading…
Reference in New Issue
Block a user