diff --git a/pom.xml b/pom.xml
index e6d15581..56db5e04 100644
--- a/pom.xml
+++ b/pom.xml
@@ -333,7 +333,12 @@
s3
2.25.35
-
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.12
+
diff --git a/src/main/java/de/holarse/backend/db/repositories/ArticleRevisionRepository.java b/src/main/java/de/holarse/backend/db/repositories/ArticleRevisionRepository.java
index b5acd778..d22fdb90 100644
--- a/src/main/java/de/holarse/backend/db/repositories/ArticleRevisionRepository.java
+++ b/src/main/java/de/holarse/backend/db/repositories/ArticleRevisionRepository.java
@@ -8,6 +8,8 @@
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
+import java.util.Optional;
+
/**
*
* @author comrad
@@ -18,4 +20,7 @@ public interface ArticleRevisionRepository extends JpaRepository findHistory(@Param("nodeId") final Integer nodeId, final Pageable pageable);
+ @Query("from ArticleRevision ar where ar.nodeId = :nodeId and ar.revision = :revisionId")
+ Optional findByRevisionId(@Param("nodeId") final Integer nodeId, @Param("revisionId") final Integer revisionId);
+
}
diff --git a/src/main/java/de/holarse/backend/view/ArticleView.java b/src/main/java/de/holarse/backend/view/ArticleView.java
index 871e5f53..a31a5770 100644
--- a/src/main/java/de/holarse/backend/view/ArticleView.java
+++ b/src/main/java/de/holarse/backend/view/ArticleView.java
@@ -39,6 +39,8 @@ public class ArticleView {
private List websiteLinks = new ArrayList<>();
private List youtubeVideos = new ArrayList<>();
private List screenshotViews = new ArrayList<>();
+
+ private Integer revision;
public static ArticleView of(final ArticleRevision ar, final NodeSlug slug) {
final ArticleView av = new ArticleView();
@@ -53,6 +55,7 @@ public static ArticleView of(final ArticleRevision ar, final NodeSlug slug) {
av.setContent(ar.getContent());
av.setCreated(ar.getCreated().toLocalDateTime());
av.setUpdated(ar.getUpdated().toLocalDateTime());
+ av.setRevision(ar.getRevision());
if (StringUtils.isNotBlank(ar.getTitle2())) { av.getAlternativeTitles().add(ar.getTitle2()); }
if (StringUtils.isNotBlank(ar.getTitle3())) { av.getAlternativeTitles().add(ar.getTitle3()); }
@@ -217,4 +220,12 @@ public List getScreenshots() {
public void setScreenshots(List screenshotViews) {
this.screenshotViews = screenshotViews;
}
+
+ public Integer getRevision() {
+ return revision;
+ }
+
+ public void setRevision(Integer revision) {
+ this.revision = revision;
+ }
}
diff --git a/src/main/java/de/holarse/web/controller/RevisionController.java b/src/main/java/de/holarse/web/controller/RevisionController.java
index 087f5429..592abb65 100644
--- a/src/main/java/de/holarse/web/controller/RevisionController.java
+++ b/src/main/java/de/holarse/web/controller/RevisionController.java
@@ -1,5 +1,8 @@
package de.holarse.web.controller;
+import com.github.difflib.DiffUtils;
+import com.github.difflib.text.DiffRow;
+import com.github.difflib.text.DiffRowGenerator;
import de.holarse.backend.db.Article;
import de.holarse.backend.db.ArticleRevision;
import de.holarse.backend.db.NodeSlug;
@@ -9,6 +12,7 @@
import de.holarse.backend.types.NodeType;
import de.holarse.backend.view.ArticleView;
import de.holarse.web.defines.WebDefines;
+import de.holarse.web.services.ArticleService;
import jakarta.persistence.EntityNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,8 +25,9 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;
-
import static de.holarse.web.defines.WebDefines.REVISION_DEFAULT_PAGE_SIZE;
+import java.util.Arrays;
+import java.util.List;
@Controller
public class RevisionController {
@@ -35,6 +40,9 @@ public class RevisionController {
@Autowired
private ArticleRevisionRepository articleRevisionRepository;
+ @Autowired
+ private ArticleService articleService;
+
@Autowired
private NodeSlugRepository nodeSlugRepository;
@@ -51,6 +59,8 @@ public ModelAndView revisions(@PathVariable final Integer nodeId, @PageableDefau
mv.addObject("view", ArticleView.of(currentArticle.getNodeRevision(), mainSlug));
mv.addObject("revisions", articleRevisionRepository.findHistory(nodeId, pageable));
+
+
return mv;
}
@@ -60,9 +70,45 @@ public ModelAndView show(@PathVariable final Integer nodeId, @PathVariable final
mv.addObject("title", "Die Linuxspiele-Seite für Linuxspieler");
mv.addObject(WebDefines.DEFAULT_VIEW_ATTRIBUTE_NAME, "sites/revisions/show");
+ final ArticleRevision articleRevision = articleRevisionRepository.findByRevisionId(nodeId, revision).orElseThrow(EntityNotFoundException::new);
+
+ final Article article = articleRepository.findByNodeId(nodeId).orElseThrow(EntityNotFoundException::new);
+ final ArticleView view = articleService.buildArticleView(article, articleRevision);
+ mv.addObject("nodeid", articleRevision.getNodeId());
+ mv.addObject("view", view);
return mv;
}
+ @GetMapping("/wiki/{nodeId}/revisions/view/{revision1}/{revision2}")
+ public ModelAndView diff(@PathVariable final Integer nodeId, @PathVariable final Integer revision1, @PathVariable final Integer revision2, final ModelAndView mv) {
+ mv.setViewName("layouts/bare");
+ mv.addObject("title", "Die Linuxspiele-Seite für Linuxspieler");
+ mv.addObject(WebDefines.DEFAULT_VIEW_ATTRIBUTE_NAME, "sites/revisions/diff");
+
+ final ArticleRevision articleRevision1 = articleRevisionRepository.findByRevisionId(nodeId, revision1).orElseThrow(EntityNotFoundException::new);
+ final ArticleRevision articleRevision2 = articleRevisionRepository.findByRevisionId(nodeId, revision2).orElseThrow(EntityNotFoundException::new);
+
+ final Article article = articleRepository.findByNodeId(nodeId).orElseThrow(EntityNotFoundException::new);
+
+ final ArticleView view1 = articleService.buildArticleView(article, articleRevision1);
+ final ArticleView view2 = articleService.buildArticleView(article, articleRevision2);
+
+ final DiffRowGenerator diffGenerator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true).inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**").build();
+ final List diffRows = diffGenerator.generateDiffRows(Arrays.asList(view1.getContent().split("\n")), Arrays.asList(view2.getContent().split("\n")));
+
+ final StringBuilder buffer = new StringBuilder(4096);
+ for (final DiffRow diffRow : diffRows) {
+ buffer.append(diffRow.getOldLine()).append("\n");
+ buffer.append(diffRow.getNewLine()).append("\n");
+ }
+
+ mv.addObject("nodeid", article.getNodeId());
+ mv.addObject("view1", view1);
+ mv.addObject("view2", view2);
+ mv.addObject("diff", buffer.toString());
+
+ return mv;
+ }
}
diff --git a/src/main/java/de/holarse/web/controller/WikiController.java b/src/main/java/de/holarse/web/controller/WikiController.java
index 8fb5d4b2..5e4ee718 100644
--- a/src/main/java/de/holarse/web/controller/WikiController.java
+++ b/src/main/java/de/holarse/web/controller/WikiController.java
@@ -90,6 +90,9 @@ public class WikiController {
@Autowired
private EntityLockService entityLockService;
+ @Autowired
+ private ArticleService articleService;
+
@Autowired
private Renderer renderer;
@@ -151,41 +154,10 @@ public ModelAndView show(@PathVariable("slug") final String slug, final ModelAnd
final Article article = articleRepository.findBySlug(slug).orElseThrow(EntityNotFoundException::new);
final ArticleRevision articleRevision = article.getNodeRevision();
- final Set tags = article.getTags();
- final List relevantTagGroups = tags.stream().map(t -> t.getTagGroup()).toList();
- final NodeSlug mainSlug = nodeSlugRepository.findMainSlug(articleRevision.getNodeId(), NodeType.article).orElseThrow(EntityNotFoundException::new);
-
- mv.addObject("nodeid", article.getNodeId());
-
- // TODO
-// if (article.getNodeStatus().isDeleted() || !article.getNodeStatus().isPublished()) {
-// logger.debug("Principal: {}", principal);
-// if (principal instanceof HolarsePrincipal holarsePrincipal) {
-// if (holarsePrincipal.getAuthorities().stream().filter(a -> a.getAuthority().equalsIgnoreCase(RoleUserTypes.ROLE_USER_ADMIN)).count() > 0) {
-// adminOverride = true;
-// }
-// }
-//
-// if (!adminOverride) {
-// mv.addObject(WebDefines.DEFAULT_VIEW_ATTRIBUTE_NAME, "sites/wiki/blocked");
-// return mv;
-// }
-// }
-
- final List websiteLinks = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.website.name()));
- final List videos = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.video.name()));
- final List screenshots = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.image.name()));
- // View zusammenstellen
- final ArticleView view = ArticleView.of(articleRevision, mainSlug);
- view.setNodeId(article.getNodeId());
- view.setTagList(tags.stream().map(TagView::of).sorted(Comparator.comparingInt(TagView::getWeight).reversed().thenComparing(TagView::getUseCount).reversed().thenComparing(TagView::getName)).toList());
- view.setContent(renderer.render(view.getContent(), null));
- //view.setSlug(mainSlug.getName());
- view.setWebsiteLinks(websiteLinks.stream().map(AttachmentView::of).toList());
- view.setYoutubeVideos(videos.stream().map(YoutubeView::of).toList());
- view.setScreenshots(screenshots.stream().map(ScreenshotView::of).map(ssv -> objectStorageService.patchUrl(ssv)).toList());
+ final ArticleView view = articleService.buildArticleView(article, articleRevision);
+ mv.addObject("nodeid", articleRevision.getNodeId());
mv.addObject("view", view);
return mv;
@@ -244,7 +216,7 @@ public ModelAndView edit(@PathVariable final Integer nodeId, final ModelAndView
return mv.addObject("form", form);
}
-
+
@Transactional
@PostMapping("{nodeId}")
public ModelAndView update(@PathVariable final int nodeId, @ModelAttribute("form") final ArticleForm form, final ModelAndView mv, final Authentication authentication) throws JsonProcessingException {
diff --git a/src/main/java/de/holarse/web/services/ArticleService.java b/src/main/java/de/holarse/web/services/ArticleService.java
new file mode 100644
index 00000000..8ffcf370
--- /dev/null
+++ b/src/main/java/de/holarse/web/services/ArticleService.java
@@ -0,0 +1,73 @@
+package de.holarse.web.services;
+
+import de.holarse.backend.db.*;
+import de.holarse.backend.db.repositories.AttachmentGroupRepository;
+import de.holarse.backend.db.repositories.NodeSlugRepository;
+import de.holarse.backend.types.AttachmentGroupType;
+import de.holarse.backend.types.NodeType;
+import de.holarse.backend.view.*;
+import de.holarse.web.renderer.Renderer;
+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+@Service
+public class ArticleService {
+
+ @Autowired
+ private AttachmentService attachmentService;
+
+ @Autowired
+ private ObjectStorageService objectStorageService;
+
+ @Autowired
+ private AttachmentGroupRepository attachmentGroupRepository;
+
+ @Autowired
+ private NodeSlugRepository nodeSlugRepository;
+
+ @Autowired
+ private Renderer renderer;
+
+ public ArticleView buildArticleView(final Article article, final ArticleRevision articleRevision) {
+ final Set tags = article.getTags();
+ final List relevantTagGroups = tags.stream().map(Tag::getTagGroup).toList();
+ final NodeSlug mainSlug = nodeSlugRepository.findMainSlug(articleRevision.getNodeId(), NodeType.article).orElseThrow(EntityNotFoundException::new);
+
+ // TODO
+// if (article.getNodeStatus().isDeleted() || !article.getNodeStatus().isPublished()) {
+// logger.debug("Principal: {}", principal);
+// if (principal instanceof HolarsePrincipal holarsePrincipal) {
+// if (holarsePrincipal.getAuthorities().stream().filter(a -> a.getAuthority().equalsIgnoreCase(RoleUserTypes.ROLE_USER_ADMIN)).count() > 0) {
+// adminOverride = true;
+// }
+// }
+//
+// if (!adminOverride) {
+// mv.addObject(WebDefines.DEFAULT_VIEW_ATTRIBUTE_NAME, "sites/wiki/blocked");
+// return mv;
+// }
+// }
+
+ final List websiteLinks = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.website.name()));
+ final List videos = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.video.name()));
+ final List screenshots = attachmentService.getAttachments(article, attachmentGroupRepository.findByCode(AttachmentGroupType.image.name()));
+
+ // View zusammenstellen
+ final ArticleView view = ArticleView.of(articleRevision, mainSlug);
+ view.setNodeId(article.getNodeId());
+ view.setTagList(tags.stream().map(TagView::of).sorted(Comparator.comparingInt(TagView::getWeight).reversed().thenComparing(TagView::getUseCount).reversed().thenComparing(TagView::getName)).toList());
+ view.setContent(renderer.render(view.getContent(), null));
+ //view.setSlug(mainSlug.getName());
+ view.setWebsiteLinks(websiteLinks.stream().map(AttachmentView::of).toList());
+ view.setYoutubeVideos(videos.stream().map(YoutubeView::of).toList());
+ view.setScreenshots(screenshots.stream().map(ScreenshotView::of).map(ssv -> objectStorageService.patchUrl(ssv)).toList());
+
+ return view;
+ }
+
+}
diff --git a/src/main/webapp/WEB-INF/templates/sites/revisions/diff.html b/src/main/webapp/WEB-INF/templates/sites/revisions/diff.html
new file mode 100644
index 00000000..d6da8cfc
--- /dev/null
+++ b/src/main/webapp/WEB-INF/templates/sites/revisions/diff.html
@@ -0,0 +1,8 @@
+
+
+ Diff zwischen Revision
und
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/templates/sites/revisions/show.html b/src/main/webapp/WEB-INF/templates/sites/revisions/show.html
index b9f1e749..56569fd6 100644
--- a/src/main/webapp/WEB-INF/templates/sites/revisions/show.html
+++ b/src/main/webapp/WEB-INF/templates/sites/revisions/show.html
@@ -1,5 +1,8 @@
- Revision
+ Revision
von
+
+
+
diff --git a/src/main/webapp/WEB-INF/templates/sites/wiki/show.html b/src/main/webapp/WEB-INF/templates/sites/wiki/show.html
index fde44464..7d44ad37 100644
--- a/src/main/webapp/WEB-INF/templates/sites/wiki/show.html
+++ b/src/main/webapp/WEB-INF/templates/sites/wiki/show.html
@@ -25,6 +25,10 @@
Statistik
+
+
Tags