import net.pterodactylus.sone.template.ReplyAccessor;
import net.pterodactylus.sone.template.ReplyGroupFilter;
import net.pterodactylus.sone.template.RequestChangeFilter;
+import net.pterodactylus.sone.template.ShortenFilter;
import net.pterodactylus.sone.template.SoneAccessor;
import net.pterodactylus.sone.template.SubstringFilter;
import net.pterodactylus.sone.template.TrustAccessor;
/** The parser filter. */
private final ParserFilter parserFilter;
+ private final ShortenFilter shortenFilter;
private final RenderFilter renderFilter;
private final ListNotificationFilter listNotificationFilter;
templateContextFactory.addFilter("css", new CssClassNameFilter());
templateContextFactory.addFilter("js", new JavascriptFilter());
templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), soneTextParser));
+ templateContextFactory.addFilter("shorten", shortenFilter = new ShortenFilter());
templateContextFactory.addFilter("render", renderFilter = new RenderFilter(getCore(), templateContextFactory));
templateContextFactory.addFilter("reparse", new ReparseFilter());
templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumAjaxPage(this)));
- pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter, renderFilter)));
+ pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this)));
pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this)));
package net.pterodactylus.sone.web.ajax;
+import com.google.common.collect.ImmutableMap;
import net.pterodactylus.sone.data.Image;
import net.pterodactylus.sone.template.ParserFilter;
import net.pterodactylus.sone.template.RenderFilter;
-import net.pterodactylus.sone.text.Part;
+import net.pterodactylus.sone.template.ShortenFilter;
import net.pterodactylus.sone.text.TextFilter;
import net.pterodactylus.sone.web.WebInterface;
import net.pterodactylus.sone.web.page.FreenetRequest;
import net.pterodactylus.util.template.TemplateContext;
-import com.google.common.collect.ImmutableMap;
-
/**
* Page that stores a user’s image modifications.
*
public class EditImageAjaxPage extends JsonPage {
private final ParserFilter parserFilter;
+ private final ShortenFilter shortenFilter;
private final RenderFilter renderFilter;
/**
* @param parserFilter
* The parser filter for image descriptions
*/
- public EditImageAjaxPage(WebInterface webInterface, ParserFilter parserFilter, RenderFilter renderFilter) {
+ public EditImageAjaxPage(WebInterface webInterface, ParserFilter parserFilter, ShortenFilter shortenFilter, RenderFilter renderFilter) {
super("editImage.ajax", webInterface);
this.parserFilter = parserFilter;
+ this.shortenFilter = shortenFilter;
this.renderFilter = renderFilter;
}
TemplateContext templateContext = new TemplateContext();
ImmutableMap<String, Object> parameters = ImmutableMap.<String, Object>builder().put("sone", image.getSone()).build();
Object parts = parserFilter.format(templateContext, image.getDescription(), parameters);
- return (String) renderFilter.format(templateContext, parts, parameters);
+ Object shortenedParts = shortenFilter.format(templateContext, parts, parameters);
+ return (String) renderFilter.format(templateContext, shortenedParts, parameters);
}
}
import java.io.StringWriter
import java.io.Writer
import java.net.URLEncoder
-import java.util.ArrayList
/**
* Renders a number of pre-parsed [Part] into a [String].
override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap<String, Any?>?): Any? {
@Suppress("UNCHECKED_CAST")
- val parts = getPartsToRender(parameters, data as? Iterable<Part> ?: return null)
+ val parts = data as? Iterable<Part> ?: return null
val parsedTextWriter = StringWriter()
render(parsedTextWriter, parts)
return parsedTextWriter.toString()
}
- private fun Map<String, Any?>.parseInt(key: String) = this[key]?.toString()?.toInt()
-
- private fun getPartsToRender(parameters: MutableMap<String, Any?>?, parts: Iterable<Part>): Iterable<Part> {
- val length = parameters?.parseInt("length") ?: -1
- val cutOffLength = parameters?.parseInt("cut-off-length") ?: length
- if (length > -1) {
- var allPartsLength = 0
- val shortenedParts = ArrayList<Part>()
- for (part in parts) {
- if (part is PlainTextPart) {
- val longText = part.text
- if (allPartsLength < cutOffLength) {
- if (allPartsLength + longText.length > cutOffLength) {
- shortenedParts.add(PlainTextPart(longText.substring(0, cutOffLength - allPartsLength) + "…"))
- } else {
- shortenedParts.add(part)
- }
- }
- allPartsLength += longText.length
- } else if (part is LinkPart) {
- if (allPartsLength < cutOffLength) {
- shortenedParts.add(part)
- }
- allPartsLength += part.text.length
- } else {
- if (allPartsLength < cutOffLength) {
- shortenedParts.add(part)
- }
- }
- }
- if (allPartsLength >= length) {
- return shortenedParts
- }
- }
- return parts
- }
-
private fun render(writer: Writer, parts: Iterable<Part>) {
parts.forEach { render(writer, it) }
}
is SonePart -> render(writer, part)
is PostPart -> render(writer, part)
is FreemailPart -> render(writer, part)
- is Iterable<*> -> render(writer, part as Iterable<Part>)
}
}
<%/if>
<%/if>
<% post.text|html|store key==originalText text==true>
- <% post.text|parse sone=post.sone|render|store key==parsedText text==true>
- <% post.text|parse sone=post.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
+ <% post.text|parse sone=post.sone|store key==parsedText>
+ <% parsedText|render|store key==renderedText text==true>
+ <% parsedText|shorten length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
<div class="post-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
- <div class="post-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
- <div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
- <%if !shortText|match value=parsedText><%if !raw><a class="expand-post-text" href="viewPost.html?post=<% post.id|html>&raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
- <%if !shortText|match value=parsedText><%if !raw><a class="shrink-post-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
+ <div class="post-text text<%if raw> hidden<%/if><%if !shortText|match key=renderedText> hidden<%/if>"><% renderedText></div>
+ <div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=renderedText> hidden<%/if>"><% shortText></div>
+ <%if !shortText|match value=renderedText><%if !raw><a class="expand-post-text" href="viewPost.html?post=<% post.id|html>&raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
+ <%if !shortText|match value=renderedText><%if !raw><a class="shrink-post-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
</div>
<div class="post-status-line status-line<%if !post.loaded> hidden<%/if>">
<div class="bookmarks">
<div class="permalink permalink-post"><a href="post://<%post.id|html>">[<%= View.Post.Permalink|l10n|html>]</a></div>
<span class='separator'>·</span>
<div class="permalink permalink-author"><a href="sone://<%post.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
- <%if ! originalText|match value=parsedText>
+ <%if ! originalText|match value=renderedText>
<span class='separator'>·</span>
<div class="show-source"><a href="viewPost.html?post=<% post.id|html>&raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
<%/if>
<div>
<div class="author profile-link"><a href="viewSone.html?sone=<% reply.sone.id|html>"><% reply.sone.niceName|html></a></div>
<% reply.text|html|store key==originalText text==true>
- <% reply.text|parse sone=reply.sone|render|store key==parsedText text==true>
- <% reply.text|parse sone=reply.sone length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
+ <% reply.text|parse sone=reply.sone|store key==parsedText>
+ <% parsedText|render|store key==renderedText text==true>
+ <% parsedText|shorten length=core.preferences.charactersPerPost cut-off-length=core.preferences.postCutOffLength|render|store key==shortText text==true>
<div class="reply-text raw-text<%if !raw> hidden<%/if>"><% originalText></div>
- <div class="reply-text text<%if raw> hidden<%/if><%if !shortText|match key=parsedText> hidden<%/if>"><% parsedText></div>
- <div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=parsedText> hidden<%/if>"><% shortText></div>
- <%if !shortText|match value=parsedText><%if !raw><a class="expand-reply-text" href="viewPost.html?post=<% reply.postId|html>&raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
- <%if !shortText|match value=parsedText><%if !raw><a class="shrink-reply-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
+ <div class="reply-text text<%if raw> hidden<%/if><%if !shortText|match key=renderedText> hidden<%/if>"><% renderedText></div>
+ <div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=renderedText> hidden<%/if>"><% shortText></div>
+ <%if !shortText|match value=renderedText><%if !raw><a class="expand-reply-text" href="viewPost.html?post=<% reply.postId|html>&raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
+ <%if !shortText|match value=renderedText><%if !raw><a class="shrink-reply-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
</div>
<div class="reply-status-line status-line">
<div class="time"><% reply.time|date format=="MMM d, yyyy, HH:mm:ss"></div>
<span class='separator'>·</span>
<div class="permalink permalink-author"><a href="sone://<%reply.sone.id|html>">[<%= View.Post.PermalinkAuthor|l10n|html>]</a></div>
- <%if ! originalText|match value=parsedText>
+ <%if ! originalText|match value=renderedText>
<span class='separator'>·</span>
<div class="show-reply-source"><a href="viewPost.html?post=<% post.id|html>&raw=<%if raw>false<%else>true<%/if>"><%= View.Post.ShowSource|l10n|html></a></div>
<%/if>
import org.jsoup.Jsoup
import org.jsoup.nodes.Attribute
import org.jsoup.nodes.Element
-import org.jsoup.nodes.TextNode
import org.junit.Test
import org.mockito.Mockito.`when`
import java.net.URLEncoder
private fun renderParts(vararg part: Part) = filter.format(templateContext, listOf(*part), parameters) as String
@Test
- fun `plain text part is shortened if length exceeds maxl ength`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This is a long text.")), `is`("This is a …"))
- }
-
- @Test
- fun `plain text part is not shortened if length does not exceed max length`() {
- setLengthAndCutOffLength(20, 10)
- assertThat(renderParts(PlainTextPart("This is a long text.")), `is`("This is a …"))
- }
-
- @Test
- fun `short parts are not shortened`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This.")), `is`("This."))
- }
-
- @Test
- fun `multiple plain text parts are shortened`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This "), PlainTextPart("is a long text.")), `is`("This is a …"))
- }
-
- @Test
- fun `parts after length has been reached are ignored`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This is a long text."), PlainTextPart(" And even more.")), `is`("This is a …"))
- }
-
- @Test
- fun `link parts are not shortened`() {
- setLengthAndCutOffLength(15, 10)
- val linkNode = Jsoup.parseBodyFragment(renderParts(FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false))).body().child(0)
- verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "This is a long text.")
- }
-
- @Test
- fun `additional link parts are ignored`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This is a long text."), FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false)), `is`("This is a …"))
- }
-
- private fun setLengthAndCutOffLength(length: Int, cutOffLength: Int) {
- parameters.put("length", length)
- parameters.put("cut-off-length", cutOffLength)
- }
-
- @Test
- fun `sone parts are added but their length is ignored`() {
- setLengthAndCutOffLength(15, 10)
- val body = Jsoup.parseBodyFragment(renderParts(SonePart(sone), PlainTextPart("This is a long text."))).body()
- val linkNode = body.childNode(0) as Element
- println(linkNode)
- verifyLink(linkNode, "viewSone.html?sone=$SONE_IDENTITY", "in-sone", "First", "First")
- assertThat((body.childNode(1) as TextNode).text(), `is`("This is a …"))
- }
-
- @Test
- fun `additional sone parts are ignored`() {
- setLengthAndCutOffLength(15, 10)
- assertThat(renderParts(PlainTextPart("This is a long text."), SonePart(sone)), `is`("This is a …"))
- }
-
- @Test
fun `freenet link is rendered correctly`() {
val linkNode = renderParts(FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)).toLinkNode()
verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "gpl.txt")