+++ /dev/null
-/*
- * Sone - PostAccessor.java - Copyright © 2010â2019 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.template;
-
-import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.ReflectionAccessor;
-import net.pterodactylus.util.template.TemplateContext;
-
-import com.google.common.collect.Collections2;
-
-/**
- * Accessor for {@link Post} objects that adds additional properties:
- * <dl>
- * <dd>replies</dd>
- * <dt>All replies to this post, sorted by time, oldest first</dt>
- * </dl>
- */
-public class PostAccessor extends ReflectionAccessor {
-
- /** The core to get the replies from. */
- private final Core core;
-
- /**
- * Creates a new post accessor.
- *
- * @param core
- * The core to get the replies from
- */
- public PostAccessor(Core core) {
- this.core = core;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Object get(TemplateContext templateContext, Object object, String member) {
- Post post = (Post) object;
- if ("replies".equals(member)) {
- return Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER);
- } else if (member.equals("likes")) {
- return core.getLikes(post);
- } else if (member.equals("liked")) {
- Sone currentSone = (Sone) templateContext.get("currentSone");
- return (currentSone != null) && (currentSone.isLikedPostId(post.getId()));
- } else if (member.equals("new")) {
- return !post.isKnown();
- } else if (member.equals("bookmarked")) {
- return core.isBookmarked(post);
- }
- return super.get(templateContext, object, member);
- }
-
-}
--- /dev/null
+/*
+ * Sone - PostAccessor.java - Copyright © 2010â2019 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package net.pterodactylus.sone.template
+
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.util.template.*
+
+/**
+ * Accessor for [Post] objects that adds additional properties:
+ *
+ * * `replies`: All replies to this post, sorted by time, oldest first
+ * * `likes`: All Sones that have liked the post
+ * * `liked`: `true` if the current Sone from the [template context][TemplateContext] has liked the post
+ * * `new`: `true` if the post is not known
+ * * `bookmarked`: `true` if the post is bookmarked
+ */
+class PostAccessor(private val core: Core) : ReflectionAccessor() {
+
+ override fun get(templateContext: TemplateContext?, `object`: Any?, member: String): Any? =
+ (`object` as Post).let { post ->
+ when (member) {
+ "replies" -> core.getReplies(post)
+ "likes" -> core.getLikes(post)
+ "liked" -> templateContext.currentSone?.isLikedPostId(post.id) ?: false
+ "new" -> !post.isKnown
+ "bookmarked" -> core.isBookmarked(post)
+ "replySone" -> core.getReplies(post)
+ .lastOrNull { it.sone.isLocal }
+ ?.sone
+ ?: post.sone.takeIf { it.isLocal }
+ ?: templateContext.currentSone
+ else -> super.get(templateContext, `object`, member)
+ }
+ }
+
+}
+
+private fun Core.getReplies(post: Post) = getReplies(post.id).filter { Reply.FUTURE_REPLY_FILTER.apply(it) }
+private val TemplateContext?.currentSone: Sone? get() = this?.get("currentSone") as? Sone
Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Ne montrer que les avatars des Sones auxquelles vous ĂȘtes assignĂ©s manuellement une confiance superieure Ă 0.
Page.Options.Option.ShowAvatars.Trusted.Description=Ne montrer que les avatars des Sones qui ont une confiance superieure Ă 0.
Page.Options.Option.ShowAvatars.Always.Description=Montre tout le temps les avatars personnalisĂ©s. Attention: certains avatars peuvent ĂȘtre offensants !
-Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
-Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
-Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
-Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
-Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
-Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
-Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
-Page.Options.Section.RuntimeOptions.Title=Comportement runtime
+Page.Options.Section.LoadLinkedImagesOptions.Title=Chargement des images liées
+Page.Options.Option.LoadLinkedImages.Description=Sone essaie automatiquement de charger les images liées à des messages ou à des réponses. Tous les chargements sont faits depuis Freenet, jamais depuis internet !
+Page.Options.Option.LoadLinkedImages.Never.Description=Ne jamais charger les images liées.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Charge uniquement les images liées à Sones que vous suivez.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Ne charger que des images liĂ©es des Sones auxquelles vous ĂȘtes assignĂ©s manuellement une confiance superieure Ă 0.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Ne charger que des images liées à des Sones qui ont une confiance superieure à 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Charge tout le temps les images liĂ©es. Attention: certains avatars peuvent ĂȘtre offensants !
+Page.Options.Section.RuntimeOptions.Title=Comportement de l'exécution
Page.Options.Option.InsertionDelay.Description=Le délai d'insertion d'un Sone.
Page.Options.Option.PostsPerPage.Description=Le nombre de message à afficher par page avant que les boutons de pagination soit affichés.
Page.Options.Option.ImagesPerPage.Description=Le nombre de message à afficher par page avant que les boutons de pagination soit affichés.
Page.ViewSone.Profile.Label.Name=Nom
Page.ViewSone.Profile.Label.Albums=Albums
Page.ViewSone.Profile.Albums.Text.All=Tous les albums
-Page.ViewSone.Profile.Name.WoTLink=Profile Web of trust
+Page.ViewSone.Profile.Name.WoTLink=Profil Web of Trust
Page.ViewSone.Replies.Title=Messages {sone} a rĂ©pondu Ă
Page.ViewPost.Title=Voir message - Sone
Page.Invalid.Page.Title=Action invalide réalisée
Page.Invalid.Text=Une action invalide a été effectuée, ou l'action était valide mes les paramÚtres ne l'étaient pas. Veuillez retourner à la {link}page d'index{/link} et veuillez réessayer. Si l'erreur persiste, vous avez probablement trouvé un bug.
-Page.Metrics.Title=Metrics
-Page.Metrics.Page.Title=Metrics
-Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration
-Page.Metrics.SoneParseDuration.Title=Sone Parse Duration
-Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration
+Page.Metrics.Title=MĂ©triques
+Page.Metrics.Page.Title=MĂ©triques
+Page.Metrics.SoneInsertDuration.Title=Durée d'insertion de Sone
+Page.Metrics.SoneParseDuration.Title=Durée d'analyse de Sone
+Page.Metrics.ConfigurationSaveDuration.Title=Durée de sauvegarde de configuration
View.Search.Button.Search=Recherche
View.SoneMenu.WebOfTrustLink=Profile web of trust
View.Post.UnknownAuthor=(inconnu)
-View.Post.WebOfTrustLink=Profile web of trust
+View.Post.WebOfTrustLink=Profil Web of Trust
View.Post.Permalink=Lier le message
View.Post.PermalinkAuthor=Lier l'auteur
View.Post.Bookmarks.PostIsBookmarked=Ce message a été ajouté aux marque-pages, cliquer pour retirer des marque-pages
Notification.SoneIsInserting.Text=Votre Sone sone://{0} va maintenant ĂȘtre insĂ©rĂ©.
Notification.SoneIsInserted.Text=votre Sone sone://{0} a été inséré dans {1,number} {1,choice,0#seconds|1#second|1<seconds}.
Notification.SoneInsertAborted.Text=Votre Sone sone://{0} ne peut pas ĂȘtre insĂ©rĂ©.
-Notification.SoneLockedOnStartup.Text=Versions prior to v81 had a bug that resulted in empty Sones. To prevent buggy Sones from being inserted they have been automatically locked. Please check your Sones, use the Rescue Mode if necessary, and unlock your Sones once you are satisfied with the results. Locked Sones are:
-# 55-61, 324â328, 360, 464
+Notification.SoneLockedOnStartup.Text=Les versions antĂ©rieures Ă v81 avaient un bug vidant les Sones. Pour Ă©viter d'insĂ©rer des Sones vides ils ont Ă©tĂ© automatiquement vĂ©rouillĂ©s. VĂ©rifiez vos Sones, utilisez le mode rĂ©cupĂ©ration si nĂ©cessaire puis dĂ©vĂ©rouillez vos Sones lorsque vous ĂȘtes satisfait du rĂ©sultat. Les Sones vĂ©rouillĂ©s sont:
+# 464
<div class="sender">
<select name="sender" title="<%= View.UpdateStatus.Text.ChooseSenderIdentity|l10n|html>">
<%foreach localSones localSone|sort>
- <option value="<% localSone.id|html>"<%if localSone.current> selected="selected"<%/if>><% localSone.niceName|html></option>
+ <option value="<% localSone.id|html>"<%if localSone|match value=post.replySone> selected="selected"<%/if>><% localSone.niceName|html></option>
<%/foreach>
</select>
</div>
+++ /dev/null
-package net.pterodactylus.sone.template;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.template.TemplateContext;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link PostAccessor}.
- */
-public class PostAccessorTest {
-
- private final Core core = mock(Core.class);
- private final PostAccessor accessor = new PostAccessor(core);
- private final Post post = mock(Post.class);
-
- private final long now = System.currentTimeMillis();
-
- @Before
- public void setupPost() {
- when(post.getId()).thenReturn("post-id");
- }
-
- @Test
- @SuppressWarnings("unchecked")
- public void accessorReturnsTheCorrectReplies() {
- List<PostReply> replies = new ArrayList<>();
- replies.add(createPostReply(2000));
- replies.add(createPostReply(-1000));
- replies.add(createPostReply(-2000));
- replies.add(createPostReply(-3000));
- replies.add(createPostReply(-4000));
- when(core.getReplies("post-id")).thenReturn(replies);
- Collection<PostReply> repliesForPost = (Collection<PostReply>) accessor.get(null, post, "replies");
- assertThat(repliesForPost, contains(
- replies.get(1),
- replies.get(2),
- replies.get(3),
- replies.get(4)
- ));
- }
-
- private PostReply createPostReply(long timeOffset) {
- PostReply postReply = mock(PostReply.class);
- when(postReply.getTime()).thenReturn(now + timeOffset);
- return postReply;
- }
-
- @Test
- @SuppressWarnings("unchecked")
- public void accessorReturnsTheLikingSones() {
- Set<Sone> sones = mock(Set.class);
- when(core.getLikes(post)).thenReturn(sones);
- Set<Sone> likingSones = (Set<Sone>) accessor.get(null, post, "likes");
- assertThat(likingSones, is(sones));
- }
-
- @Test
- public void accessorReturnsWhetherTheCurrentSoneLikedAPost() {
- Sone sone = mock(Sone.class);
- when(sone.isLikedPostId("post-id")).thenReturn(true);
- TemplateContext templateContext = new TemplateContext();
- templateContext.set("currentSone", sone);
- assertThat(accessor.get(templateContext, post, "liked"), is((Object) true));
- }
-
- @Test
- public void accessorReturnsFalseIfPostIsNotLiked() {
- Sone sone = mock(Sone.class);
- TemplateContext templateContext = new TemplateContext();
- templateContext.set("currentSone", sone);
- assertThat(accessor.get(templateContext, post, "liked"), is((Object) false));
- }
-
- @Test
- public void accessorReturnsFalseIfThereIsNoCurrentSone() {
- TemplateContext templateContext = new TemplateContext();
- assertThat(accessor.get(templateContext, post, "liked"), is((Object) false));
- }
-
- @Test
- public void accessorReturnsThatNotKnownPostIsNew() {
- assertThat(accessor.get(null, post, "new"), is((Object) true));
- }
-
- @Test
- public void accessorReturnsThatKnownPostIsNotNew() {
- when(post.isKnown()).thenReturn(true);
- assertThat(accessor.get(null, post, "new"), is((Object) false));
- }
-
- @Test
- public void accessorReturnsIfPostIsBookmarked() {
- when(core.isBookmarked(post)).thenReturn(true);
- assertThat(accessor.get(null, post, "bookmarked"), is((Object) true));
- }
-
- @Test
- public void accessorReturnsOtherProperties() {
- assertThat(accessor.get(null, post, "hashCode"), is((Object) post.hashCode()));
- }
-
-}
--- /dev/null
+package net.pterodactylus.sone.template
+
+import net.pterodactylus.sone.core.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import net.pterodactylus.util.template.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+/**
+ * Unit test for [PostAccessor].
+ */
+class PostAccessorTest {
+
+ private val core = mock<Core>()
+ private val accessor = PostAccessor(core)
+ private val post = mock<Post>()
+ private val now = System.currentTimeMillis()
+
+ @Before
+ fun setupPost() {
+ whenever(post.id).thenReturn("post-id")
+ }
+
+ @Test
+ fun `accessor returns the correct replies`() {
+ val replies = listOf(
+ createPostReply(2000),
+ createPostReply(-1000),
+ createPostReply(-2000),
+ createPostReply(-3000),
+ createPostReply(-4000)
+ )
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ val repliesForPost = accessor[null, post, "replies"] as Collection<PostReply>
+ assertThat(repliesForPost, contains(
+ replies[1],
+ replies[2],
+ replies[3],
+ replies[4]
+ ))
+ }
+
+ private fun createPostReply(timeOffset: Long) = mock<PostReply>().apply {
+ whenever(time).thenReturn(now + timeOffset)
+ }
+
+ @Test
+ fun `accessor returns the liking sones`() {
+ val sones = setOf<Sone>()
+ whenever(core.getLikes(post)).thenReturn(sones)
+ val likingSones = accessor[null, post, "likes"] as Set<Sone>
+ assertThat(likingSones, equalTo(sones))
+ }
+
+ @Test
+ fun `accessor returns whether the current sone liked a post`() {
+ val sone = mock<Sone>()
+ whenever(sone.isLikedPostId("post-id")).thenReturn(true)
+ val templateContext = TemplateContext()
+ templateContext["currentSone"] = sone
+ assertThat(accessor[templateContext, post, "liked"], equalTo<Any>(true))
+ }
+
+ @Test
+ fun `accessor returns false if post is not liked`() {
+ val sone = mock<Sone>()
+ val templateContext = TemplateContext()
+ templateContext["currentSone"] = sone
+ assertThat(accessor[templateContext, post, "liked"], equalTo<Any>(false))
+ }
+
+ @Test
+ fun `accessor returns false if there is no current sone`() {
+ val templateContext = TemplateContext()
+ assertThat(accessor[templateContext, post, "liked"], equalTo<Any>(false))
+ }
+
+ @Test
+ fun `accessor returns that not known post is new`() {
+ assertThat(accessor[null, post, "new"], equalTo<Any>(true))
+ }
+
+ @Test
+ fun `accessor returns that known post is not new`() {
+ whenever(post.isKnown).thenReturn(true)
+ assertThat(accessor[null, post, "new"], equalTo<Any>(false))
+ }
+
+ @Test
+ fun `accessor returns if post is bookmarked`() {
+ whenever(core.isBookmarked(post)).thenReturn(true)
+ assertThat(accessor[null, post, "bookmarked"], equalTo<Any>(true))
+ }
+
+ @Test
+ fun `reply sone for remote post without replies is current sone`() {
+ val post = mockPostFrom(remoteSone)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(currentSone))
+ }
+
+ @Test
+ fun `reply sone for remote post with remote replies is current sone`() {
+ val post = mockPostFrom(remoteSone)
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(remoteSone))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(currentSone))
+ }
+
+ @Test
+ fun `reply sone for remote post with remote and one local replies is sone of local reply`() {
+ val post = mockPostFrom(remoteSone)
+ val localSone = mockLocalSone()
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(localSone))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone))
+ }
+
+ @Test
+ fun `reply sone for remote post with remote and several local replies is sone of latest local reply`() {
+ val post = mockPostFrom(remoteSone)
+ val localSone1 = mockLocalSone()
+ val localSone2 = mockLocalSone()
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(localSone1), mockReplyFrom(localSone2))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone2))
+ }
+
+ @Test
+ fun `reply sone for local post without replies is post sone`() {
+ val localSone = mockLocalSone()
+ val post = mockPostFrom(localSone)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone))
+ }
+
+ @Test
+ fun `reply sone for local post with remote replies is local sone`() {
+ val localSone = mockLocalSone()
+ val post = mockPostFrom(localSone)
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(remoteSone))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone))
+ }
+
+ @Test
+ fun `reply sone for local post with remote and one local replies is local reply sone`() {
+ val localSone1 = mockLocalSone()
+ val post = mockPostFrom(localSone1)
+ val localSone2 = mockLocalSone()
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(localSone2))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone2))
+ }
+
+ @Test
+ fun `reply sone for local post with remote and several local replies is latest local reply sone`() {
+ val localSone1 = mockLocalSone()
+ val post = mockPostFrom(localSone1)
+ val localSone2 = mockLocalSone()
+ val localSone3 = mockLocalSone()
+ val replies = listOf(mockReplyFrom(remoteSone), mockReplyFrom(localSone2), mockReplyFrom(localSone3))
+ whenever(core.getReplies("post-id")).thenReturn(replies)
+ assertThat(accessor[templateContext, post, "replySone"], equalTo<Any>(localSone3))
+ }
+
+ @Test
+ fun `accessor returns other properties`() {
+ assertThat(accessor[null, post, "hashCode"], equalTo<Any>(post.hashCode()))
+ }
+
+}
+
+private val currentSone = mock<Sone>()
+private val remoteSone = mock<Sone>()
+private fun mockLocalSone() = mock<Sone>().apply { whenever(isLocal).thenReturn(true) }
+
+private val templateContext = TemplateContext().apply {
+ this["currentSone"] = currentSone
+}
+
+private fun mockPostFrom(sone: Sone) = mock<Post>().apply {
+ whenever(id).thenReturn("post-id")
+ whenever(this.sone).thenReturn(sone)
+}
+
+private fun mockReplyFrom(sone: Sone) = mock<PostReply>().apply { whenever(this.sone).thenReturn(sone) }