Render loaded HTML pages
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 23 Apr 2017 15:23:00 +0000 (17:23 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 23 Apr 2017 15:23:00 +0000 (17:23 +0200)
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilter.kt
src/main/resources/static/css/sone.css
src/main/resources/static/javascript/sone.js
src/main/resources/templates/linked/html-page.html [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementRenderFilterTest.kt

index 8d328be..e15f68b 100644 (file)
@@ -16,6 +16,7 @@ class LinkedElementRenderFilter @Inject constructor(private val templateContextF
 
        companion object {
                private val loadedImageTemplate = """<%include linked/image.html>""".parse()
+               private val loadedHtmlPageTemplate = """<%include linked/html-page.html>""".parse()
                private val notLoadedImageTemplate = """<%include linked/notLoaded.html>""".parse()
 
                private fun String.parse() = StringReader(this).use { TemplateParser.parse(it) }!!
@@ -24,7 +25,8 @@ class LinkedElementRenderFilter @Inject constructor(private val templateContextF
        override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map<String, Any?>?) =
                        when {
                                data is LinkedElement && data.loading -> renderNotLoadedLinkedElement(data)
-                               data is LinkedElement -> renderLinkedImage(data)
+                               data is LinkedElement && data.properties["type"] == "image" -> renderLinkedImage(data)
+                               data is LinkedElement && data.properties["type"] == "html" -> renderHtmlPage(data)
                                else -> null
                        }
 
@@ -36,6 +38,15 @@ class LinkedElementRenderFilter @Inject constructor(private val templateContextF
                                it
                        }.toString()
 
+       private fun renderHtmlPage(linkedElement: LinkedElement) =
+                       StringWriter().use {
+                               val templateContext = templateContextFactory.createTemplateContext()
+                               templateContext["link"] = linkedElement.link
+                               templateContext["title"] = linkedElement.properties["title"] ?: "No title"
+                               templateContext["description"] = linkedElement.properties["description"] ?: "No description"
+                               it.also { loadedHtmlPageTemplate.render(templateContext, it) }
+                       }.toString()
+
        private fun renderNotLoadedLinkedElement(linkedElement: LinkedElement) =
                        StringWriter().use {
                                val templateContext = templateContextFactory.createTemplateContext()
index 7392483..1c85743 100644 (file)
@@ -444,7 +444,7 @@ textarea {
        margin-top: 1ex;
 }
 
-#sone .post .linked-element.loaded .image {
+#sone .linked-element.loaded .image {
        display: inline-block;
        border: solid 1px black;
        width: 160px;
@@ -455,6 +455,31 @@ textarea {
        margin-bottom: 1ex;
 }
 
+#sone .linked-element.loaded .html-page {
+       display: inline-block;
+       width: 160px;
+       max-height: 120px;
+       overflow: hidden;
+       vertical-align: top;
+}
+
+#sone .reply .linked-element.loaded .html-page {
+       width: 120px;
+       max-height: 90px;
+}
+
+#sone .linked-element.loaded .html-page .heading {
+       font-size: 125%;
+       font-weight: 600;
+       text-overflow: ellipsis;
+       overflow: hidden;
+       white-space: nowrap;
+}
+
+#sone .linked-element.loaded .html-page {
+       display: inline-block;
+}
+
 #sone .post .replies {
        clear: both;
        padding-top: 0.2ex;
index a003780..b3f5c83 100644 (file)
@@ -1535,7 +1535,9 @@ function loadLinkedElements(links) {
        });
        if (failedElements.length > 0) {
                failedElements.forEach(function(element) {
-                       $(getLinkedElement(element.link)).remove()
+                       getLinkedElements(element.link).each(function() {
+                               $(this).remove()
+                       });
                });
        }
        var loadedElements = links.filter(function(element) {
@@ -1549,15 +1551,17 @@ function loadLinkedElements(links) {
                }, function (data, textStatus) {
                        if ((data != null) && (data.success)) {
                                data.linkedElements.forEach(function (linkedElement) {
-                                       $(getLinkedElement(linkedElement.link)).replaceWith(linkedElement.html);
+                                       getLinkedElements(linkedElement.link).each(function() {
+                                               $(this).replaceWith(linkedElement.html);
+                                       });
                                });
                        }
                });
        }
 }
 
-function getLinkedElement(link) {
-       return $(".linked-element[title='" + link + "']")[0]
+function getLinkedElements(link) {
+       return $(".linked-element[title='" + link + "']")
 }
 
 /**
diff --git a/src/main/resources/templates/linked/html-page.html b/src/main/resources/templates/linked/html-page.html
new file mode 100644 (file)
index 0000000..e60fb9e
--- /dev/null
@@ -0,0 +1,6 @@
+<span class="linked-element loaded" title="<%link|html>">
+       <a class="html-page" href="/<% link|html>">
+               <div class="heading"><% title|html></div>
+               <div class="description"><% description|html></div>
+       </a>
+</span>
index c3e26c0..07cba3d 100644 (file)
@@ -9,9 +9,12 @@ import net.pterodactylus.util.template.HtmlFilter
 import net.pterodactylus.util.template.TemplateContextFactory
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.contains
+import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.notNullValue
 import org.hamcrest.Matchers.nullValue
 import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
 import org.junit.Test
 
 /**
@@ -46,7 +49,7 @@ class LinkedElementRenderFilterTest {
 
        @Test
        fun `filter can render linked images`() {
-               val html = filter.format(null, LinkedElement("KSK@gpl.png"), emptyMap<String, Any?>()) as String
+               val html = filter.format(null, LinkedElement("KSK@gpl.png", properties = mapOf("type" to "image")), emptyMap<String, Any?>()) as String
                val outerSpanNode = Jsoup.parseBodyFragment(html).body().child(0)
                assertThat(outerSpanNode.nodeName(), `is`("span"))
                assertThat(outerSpanNode.attr("class"), `is`("linked-element loaded"))
@@ -59,8 +62,24 @@ class LinkedElementRenderFilterTest {
        }
 
        @Test
+       fun `filter can render HTML pages`() {
+               val html = filter.format(null, LinkedElement("KSK@gpl.html", properties = mapOf("type" to "html", "title" to "Page Title", "description" to "This is the description.")), emptyMap<String, Any?>()) as String
+               val outerSpanNode = Jsoup.parseBodyFragment(html).body().child(0)
+               assertThat(outerSpanNode.nodeName(), equalTo("span"))
+               assertThat(outerSpanNode.attr("class"), `is`("linked-element loaded"))
+               assertThat(outerSpanNode.attr("title"), `is`("KSK@gpl.html"))
+               val linkNode = outerSpanNode.child(0)
+               assertThat(linkNode.nodeName(), equalTo("a"))
+               assertThat(linkNode.attr("href"), equalTo("/KSK@gpl.html"))
+               val divNodes = linkNode.children()
+               assertThat(divNodes.map(Element::nodeName), contains("div", "div"))
+               assertThat(divNodes.map { it.attr("class") }, contains("heading", "description"))
+               assertThat(divNodes.map(Element::text), contains("Page Title", "This is the description."))
+       }
+
+       @Test
        fun `render filter can be created by guice`() {
-           val injector = Guice.createInjector(TemplateContextFactory::class.isProvidedByMock())
+               val injector = Guice.createInjector(TemplateContextFactory::class.isProvidedByMock())
                assertThat(injector.getInstance<LinkedElementRenderFilter>(), notNullValue())
        }