Wiki: Render WikiWord links and autolinks on the server side.
Change-Id: I46f972bcebf765a3d9fb55b7b35f40deb978dc5d
diff --git a/build.gradle b/build.gradle
index f0b037c..ae57661 100644
--- a/build.gradle
+++ b/build.gradle
@@ -108,6 +108,8 @@
processResources {
exclude("META-INF/resources/node_modules/**/*")
+ exclude("META-INF/resources/package.json")
+ exclude("META-INF/resources/yarn.lock")
}
quarkusBuild.dependsOn compileWeb
diff --git a/pom.xml b/pom.xml
index 0f26d3e..3fcdc0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -298,6 +298,8 @@
<directory>src/main/resources</directory>
<excludes>
<exclude>META-INF/resources/node_modules/**/*</exclude>
+ <exclude>META-INF/resources/package.json</exclude>
+ <exclude>META-INF/resources/yarn.lock</exclude>
</excludes>
<filtering>false</filtering>
</resource>
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiPageRevision.java b/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiPageRevision.java
index 5783166..4054312 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiPageRevision.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/wiki/WikiPageRevision.java
@@ -3,6 +3,9 @@
import eu.mulk.mulkcms2.benki.users.User;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import java.time.OffsetDateTime;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -12,6 +15,11 @@
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.parser.Tag;
@Entity
@Table(name = "wiki_page_revisions", schema = "benki")
@@ -53,9 +61,76 @@
User author) {
this.date = date;
this.title = title;
- this.content = content;
+ this.content = unhrefify(unwikilinkify(Jsoup.parse(content))).select("body").html();
this.format = format;
this.page = page;
this.author = author;
}
+
+ public String enrichedContent() {
+ return wikilinkify(hrefify(Jsoup.parse(content))).select("body").html();
+ }
+
+ private static Document tagsoupMapText(Document soup, Function<String, String> fn) {
+ for (var subnode :
+ soup.select(":not(a):not(a *)").stream()
+ .flatMap(node -> node.childNodes().stream())
+ .collect(Collectors.toUnmodifiableList())) {
+ if (subnode instanceof TextNode) {
+ var newNode = new Element(Tag.valueOf("span"), "");
+ newNode.html(fn.apply(((TextNode) subnode).text()));
+ subnode.replaceWith(newNode);
+ newNode.unwrap();
+ }
+ }
+ return soup;
+ }
+
+ private static Pattern WIKIWORD_REGEX =
+ Pattern.compile(
+ "\\p{javaUpperCase}+\\p{javaLowerCase}+\\p{javaUpperCase}+\\p{javaLowerCase}+\\w+");
+ private static Pattern URL_REGEX =
+ Pattern.compile("\\(?\\bhttps?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]");
+
+ private static Document hrefify(Document soup) {
+ return tagsoupMapText(
+ soup,
+ x ->
+ URL_REGEX
+ .matcher(x)
+ .replaceAll(
+ match -> {
+ var s = match.group();
+ var leftParen = s.startsWith("(");
+ var rightParen = s.endsWith(")");
+ var url =
+ s.substring(leftParen ? 1 : 0, rightParen ? s.length() - 1 : s.length());
+ return String.format(
+ "%s<a href=\"%s\" class=\"benkiautohref\">%s</a>%s",
+ leftParen ? "(" : "", url, url, rightParen ? ")" : "");
+ }));
+ }
+
+ private static Document unhrefify(Document soup) {
+ soup.select(".benkiautohref").unwrap();
+ return soup;
+ }
+
+ private static Document wikilinkify(Document soup) {
+ return tagsoupMapText(
+ soup,
+ x ->
+ WIKIWORD_REGEX
+ .matcher(x)
+ .replaceAll(
+ match ->
+ String.format(
+ "<a href=\"/wiki/%s\" class=\"benkilink\">%s</a>",
+ match.group(), match.group())));
+ }
+
+ private static Document unwikilinkify(Document soup) {
+ soup.select(".benkilink").unwrap();
+ return soup;
+ }
}
diff --git a/src/main/resources/templates/benki/wiki/wikiPage.html b/src/main/resources/templates/benki/wiki/wikiPage.html
index f9f5214..901b300 100644
--- a/src/main/resources/templates/benki/wiki/wikiPage.html
+++ b/src/main/resources/templates/benki/wiki/wikiPage.html
@@ -25,10 +25,11 @@
requestParams.append(name, regions[name]);
}
- var response = await fetch("/wiki/{page.title}", {
+ let response = await fetch("/wiki/{page.title}", {
method: 'POST',
body: requestParams
});
+
this.busy(false);
});
});
@@ -45,7 +46,7 @@
<main>
<div data-editable data-name="wiki-content">
- {#with page}{content.raw}{/}
+ {#with page}{enrichedContent.raw}{/}
</div>
</main>