Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
1407b95
feat:Side navigation treeview in note application - MEED-10100 - Meed…
AzmiTouil Jan 19, 2026
74262f8
feat: Filter by name and note type - MEED-10104 - Meeds-io/MIPs#235 (…
AzmiTouil Jan 20, 2026
05ef78c
feat: Hide/display side navigation treeview - MEED-10101 - Meeds-io/M…
AzmiTouil Jan 21, 2026
eaf5691
feat: Resize side navigation treeview - MEED-10102 - Meeds-io/MIPs#23…
AzmiTouil Jan 21, 2026
658bbdb
feat: Move/sort manually notes from side treeview - MEED-10103 - Meed…
AzmiTouil Jan 29, 2026
405770b
feat:Side navigation treeview in note application - MEED-10100 - Meed…
AzmiTouil Jan 29, 2026
591cd0e
feat: Fix note treeview display - MEED-10179 - Meeds-io/MIPs#235 (#1609)
AzmiTouil Feb 5, 2026
3e44672
fix: Fix Treeview position with a wide featured image - MEED-10180 - …
AzmiTouil Feb 5, 2026
2432f0b
feat: Update display for no results found for note - MEED-10191 - Mee…
AzmiTouil Feb 5, 2026
d917172
feat: Move/sort manually notes from side treeview - MEED-10103 - Meed…
AzmiTouil Feb 10, 2026
9242343
fix: Open treeview to the current position when refresh - MEED-10239 …
AzmiTouil Feb 10, 2026
825b235
fix: Fix note display when increasing the treeview size - MEED-10235 …
AzmiTouil Feb 11, 2026
fbb46ff
fix: Fix note breadcrumb display - MEED-10234 - Meeds-io/MIPs#235 (#1…
AzmiTouil Feb 11, 2026
7c97115
fix: Update UX when clicking on the avatar in "My Notes" to open the …
AzmiTouil Feb 12, 2026
b3f2a46
fix: Fix position of notes in the treeview - MEED-10183 - Meeds-io/MI…
AzmiTouil Feb 13, 2026
623cedc
fix: Fix the display of the treeview after expanding sidebar - MEED-1…
AzmiTouil Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* This file is part of the Meeds project (https://meeds.io/).
*
* Copyright (C) 2020 - 2026 Meeds Association contact@meeds.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package io.meeds.notes.rest.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class NoteReorder {

private String pageId;

private String wikiType;

private String wikiOwner;

private String sourceParentId;

private String targetParentId;

private List<String> sourceOrderedIds;

private List<String> targetOrderedIds;

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,7 @@
import java.util.List;
import java.util.Set;

import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;

Expand All @@ -49,8 +34,8 @@
@NamedQuery(name = "wikiPage.getPageOfWikiByName", query = "SELECT p FROM WikiPageEntity p JOIN p.wiki w WHERE p.name = :name AND w.type = :type AND w.owner = :owner AND p.deleted = false")
@NamedQuery(name = "wikiPage.getAllPagesOfWiki", query = "SELECT p FROM WikiPageEntity p JOIN p.wiki w WHERE w.type = :type AND w.owner = :owner")
@NamedQuery(name = "wikiPage.getPagesOfWiki", query = "SELECT p FROM WikiPageEntity p JOIN p.wiki w WHERE w.type = :type AND w.owner = :owner AND p.deleted = :deleted")
@NamedQuery(name = "wikiPage.getChildrenPages", query = "SELECT p FROM WikiPageEntity p WHERE p.parentPage.id = :id AND p.deleted = false ORDER BY p.name")
@NamedQuery(name = "wikiPage.getAllChildrenPages", query = "SELECT p FROM WikiPageEntity p WHERE p.parentPage.id = :id ORDER BY p.name")
@NamedQuery(name = "wikiPage.getChildrenPages", query = "SELECT p FROM WikiPageEntity p WHERE p.parentPage.id = :id AND p.deleted = false ORDER BY p.position ASC, p.name ASC")
@NamedQuery(name = "wikiPage.getAllChildrenPages", query = "SELECT p FROM WikiPageEntity p WHERE p.parentPage.id = :id ORDER BY p.position ASC, p.name ASC")
@NamedQuery(name = "wikiPage.getRelatedPages", query = "SELECT p FROM WikiPageEntity p INNER JOIN p.relatedPages r where r.id = :pageId")
@NamedQuery(name = "wikiPage.getAllPagesBySyntax", query = "SELECT p FROM WikiPageEntity p WHERE p.syntax = :syntax OR p.syntax IS NULL ORDER BY p.updatedDate DESC")
@NamedQuery(name = "wikiPage.countPageChildrenById", query = "SELECT COUNT(*) FROM WikiPageEntity p WHERE p.parentPage.id = :id AND p.deleted = false")
Expand All @@ -75,6 +60,7 @@ public class PageEntity extends BasePageEntity {
private PageEntity parentPage;

@OneToMany(mappedBy = "parentPage")
@OrderBy("position ASC, name ASC")
@EqualsAndHashCode.Exclude
private List<PageEntity> children;

Expand Down Expand Up @@ -126,4 +112,7 @@ public class PageEntity extends BasePageEntity {
@Column(name = "DELETED")
private boolean deleted;

@Column(name = "POSITION")
private Integer position;

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.List;

import io.meeds.notes.rest.model.NoteReorder;
import org.gatein.api.EntityNotFoundException;

import org.exoplatform.commons.utils.PageList;
Expand Down Expand Up @@ -997,4 +998,11 @@ default boolean canViewNote(String pageId, String username) {
*/
boolean canViewNote(Page page, String username);

/**
* @param noteReorder the payload object encapsulating the reordering details.
* @param userIdentity user Identity
*/
default void updateNotesPosition(NoteReorder noteReorder, Identity userIdentity) throws IllegalAccessException {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.meeds.notes.rest.model.NoteReorder;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -1893,6 +1894,16 @@ public void markNoteAsViewed(Page note, Identity userIdentity) {
}
}

public void updateNotesPosition(NoteReorder noteReorder, Identity userIdentity) throws IllegalAccessException {
Page existingNote = getNoteById(noteReorder.getPageId());
if (existingNote == null) {
throw new EntityNotFoundException("Note to update not found");
} else if (userIdentity == null || !canEditNote(existingNote, userIdentity.getUserId())) {
throw new IllegalAccessException("User does not have edit the note.");
}
dataStorage.updateNotesPosition(noteReorder);
}

/******* Private methods *******/

private void deleteNoteMetadataProperties(Page note, String lang, String objectType) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import io.meeds.notes.rest.model.NoteReorder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.LocaleUtils;
Expand Down Expand Up @@ -1474,6 +1475,24 @@ public Response markNoteAsViewed(@Parameter(description = "News id") @PathParam(
}
}


@POST
@Path("/reorder")
@Operation(summary = "Reorder a note", method = "POST", description = "This reorder a note.")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"),
@ApiResponse(responseCode = "401", description = "User not authorized to get the note"),
@ApiResponse(responseCode = "500", description = "Internal server error") })
public Response reorderNotes(@RequestBody NoteReorder request) {
try {
noteService.updateNotesPosition(request, ConversationState.getCurrent().getIdentity());
return Response.ok().build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
} catch (IllegalAccessException e) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}

private List<JsonNodeData> getJsonTree(WikiPageParams params, Map<String, Object> context, Identity identity, Locale locale) throws Exception {
Wiki noteBook = noteBookService.getWikiByTypeAndOwner(params.getType(), params.getOwner());
WikiTreeNode noteBookNode = new WikiTreeNode(noteBook);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.List;
import java.util.Set;

import io.meeds.notes.rest.model.NoteReorder;
import org.apache.commons.lang3.StringUtils;

import org.exoplatform.commons.api.persistence.ExoTransactional;
Expand Down Expand Up @@ -991,6 +992,65 @@ public List<DraftPage> getDraftsOfWiki(String wikiOwner, String wikiType, String
return convertDraftPageEntitiesToDraftPages(draftPageEntities);
}

@ExoTransactional
public void updateNotesPosition(NoteReorder noteReorder) throws WikiException {
PageEntity pageToMove = pageDAO.find(Long.valueOf(noteReorder.getPageId()));

if (pageToMove == null) {
throw new WikiException("Cannot update note position: page does not exist.");
}
boolean isCrossFolderMove = noteReorder.getSourceParentId() != null
&& !noteReorder.getSourceParentId().equals(noteReorder.getTargetParentId());

if (isCrossFolderMove) {
PageEntity destinationParentEntity = null;
if (noteReorder.getTargetParentId() != null && !noteReorder.getTargetParentId().isEmpty()) {
destinationParentEntity = pageDAO.find(Long.valueOf(noteReorder.getTargetParentId()));
if (destinationParentEntity == null) {
throw new WikiException("Destination parent page does not exist.");
}
}
List<PageMoveEntity> pageMoves = pageToMove.getMoves();
if (pageMoves == null) {
pageMoves = new ArrayList<>();
}
PageMoveEntity move = new PageMoveEntity(noteReorder.getWikiType(),
noteReorder.getWikiOwner(),
pageToMove.getName(),
Calendar.getInstance().getTime());
move.setPage(pageToMove);
pageMoveDAO.create(move);
pageToMove.setParentPage(destinationParentEntity);
if (destinationParentEntity != null) {
updateWikiOfPageTree(destinationParentEntity.getWiki(), pageToMove);
}

pageMoves.add(move);
pageToMove.setMoves(pageMoves);
pageDAO.update(pageToMove);
}
if (noteReorder.getTargetOrderedIds() != null && !noteReorder.getTargetOrderedIds().isEmpty()) {
int position = 1;
for (String id : noteReorder.getTargetOrderedIds()) {
PageEntity pageToUpdate = pageDAO.find(Long.valueOf(id));
if (pageToUpdate != null) {
pageToUpdate.setPosition(position++);
pageDAO.update(pageToUpdate);
}
}
}
if (isCrossFolderMove && noteReorder.getSourceOrderedIds() != null && !noteReorder.getSourceOrderedIds().isEmpty()) {
int position = 1;
for (String id : noteReorder.getSourceOrderedIds()) {
PageEntity pageToUpdate = pageDAO.find(Long.valueOf(id));
if (pageToUpdate != null) {
pageToUpdate.setPosition(position++);
pageDAO.update(pageToUpdate);
}
}
}
}

private void getDraftsOfPage(PageEntity pageEntity, List<DraftPageEntity> drafts) {
drafts.addAll(draftPageDAO.findDraftPagesByParentPage(pageEntity.getId()));
List<PageEntity> childrenPages = pageDAO.getChildrenPages(pageEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ public boolean equals(Object obj) {
public void pushDescendants(Map<String, Object> context, String userId) throws Exception {
addChildren(context, userId);
pushChildren(context, userId);
this.children = this.children.stream().sorted((childItem1, childItem2) -> new NaturalComparator().compare(childItem1.getName(), childItem2.getName())).toList();
}

protected void addChildren(Map<String, Object> context, String userId) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,4 +623,10 @@
<dropTable tableName="WIKI_DRAFT_ATTACHMENTS"/>
</changeSet>

<changeSet author="notes" id="1.0.0-mip-235-01">
<addColumn tableName="WIKI_PAGES">
<column name="POSITION" type="int" defaultValueNumeric="0"/>
</addColumn>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.Date;
import java.util.List;

import io.meeds.notes.rest.model.NoteReorder;
import org.apache.commons.io.FileUtils;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
Expand Down Expand Up @@ -1212,6 +1213,48 @@ public void testProcessingNoteImportedContentImages() throws Exception {
file1.delete();
}

@SneakyThrows
public void testUpdateNotesPosition() {
Identity root = ROOT_IDENTITY;
Identity mary = USER_IDENTITY;

Wiki portalWiki = getOrCreateWiki(wikiService, PortalConfig.PORTAL_TYPE, PORTAL_NAME);

Page note1 = new Page("dragged_note", "dragged_note");
note1 = noteService.createNote(portalWiki, "Home", note1, root);

Page note2 = new Page("other_note", "other_note");
note2 = noteService.createNote(portalWiki, "Home", note2, root);

assertNotNull(noteService.getNoteById(note1.getId()));

NoteReorder validReorder = new NoteReorder();
validReorder.setPageId(note1.getId());
validReorder.setWikiType(PortalConfig.PORTAL_TYPE);
validReorder.setWikiOwner(PORTAL_NAME);
validReorder.setSourceParentId("1");
validReorder.setTargetParentId("1");
validReorder.setTargetOrderedIds(Arrays.asList(note2.getId(), note1.getId()));

noteService.updateNotesPosition(validReorder, root);

NoteReorder reorder1 = new NoteReorder();
reorder1.setPageId("10");

Exception notFoundException = assertThrows(Exception.class, () -> {
noteService.updateNotesPosition(reorder1, root);
});
assertEquals("Note to update not found", notFoundException.getMessage());

NoteReorder reorder2 = new NoteReorder();
reorder2.setPageId(note1.getId());

IllegalAccessException accessException = assertThrows(IllegalAccessException.class, () -> {
noteService.updateNotesPosition(reorder2, mary);
});
assertEquals("User does not have edit the note.", accessException.getMessage());
}

private Long getIdentityId(String username) {
return Long.parseLong(identityManager.getOrCreateUserIdentity(username).getId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,6 @@ public void testGetFullTreeData() throws Exception {
assertEquals(5, ((JsonNodeData)((BeanToJsons) response3.getEntity()).getJsonList().getFirst()).getChildren().size());
JsonNodeData jsonNodeData = ((JsonNodeData)((BeanToJsons) response3.getEntity()).getJsonList().getFirst());
assertEquals(5, jsonNodeData.getChildren().size());
assertEquals("testPage", jsonNodeData.getChildren().get(0).getName());
assertEquals("testPage 1", jsonNodeData.getChildren().get(1).getName());
assertEquals("testPage 2", jsonNodeData.getChildren().get(2).getName());
assertEquals("testPage 10", jsonNodeData.getChildren().get(3).getName());
assertEquals("testPage 22", jsonNodeData.getChildren().get(4).getName());

doThrow(new IllegalAccessException()).when(noteService)
.getNoteOfNoteBookByName(pageParams.getType(),
Expand Down
Loading