feat: Add TagsField for keywords#15126
Conversation
|
Hey @intelliking! 👋 Thank you for contributing to JabRef! We have automated checks in place, based on which you will soon get feedback if any of them are failing. We also use Qodo for review assistance. It will update your pull request description with a review help and offer suggestions to improve the pull request. After all automated checks pass, a maintainer will also review your contribution. Once that happens, you can go through their comments in the "Files changed" tab and act on them, or reply to the conversation if you have further inputs. You can read about the whole pull request process in our contribution guide. Please ensure that your pull request is in line with our AI Usage Policy and make necessary disclosures. |
Review Summary by QodoImplement TagsField for Groups field with shared editor base class
WalkthroughsDescription• Implement TagsField support for Groups field with modern tag-based interface • Create abstract TagsEditor base class for shared tag editor functionality • Refactor KeywordsEditor to inherit from TagsEditor for code reuse • Add GroupsEditor with drag-and-drop, context menus, and tag editing features Diagramflowchart LR
A["GroupEditor<br/>Old Implementation"] -->|Replaced| B["GroupsEditor<br/>New TagsField-based"]
C["KeywordsEditor<br/>Refactored"] -->|Inherits| D["TagsEditor<br/>Abstract Base Class"]
B -->|Inherits| D
D -->|Implements| E["FieldEditorFX<br/>Interface"]
B -->|Features| F["Drag-Drop<br/>Context Menus<br/>Tag Editing"]
File Changes1. jabgui/src/main/java/org/jabref/gui/fieldeditors/TagsEditor.java
|
Code Review by Qodo
1.
|
| List<Keyword> suggestions = suggestionProvider.getPossibleSuggestions().stream() | ||
| .map(String.class::cast) | ||
| .filter(group -> group.toLowerCase().contains(request.toLowerCase())) | ||
| .map(Keyword::new) | ||
| .distinct() | ||
| .collect(Collectors.toList()); |
There was a problem hiding this comment.
1. getsuggestions() uses collectors.tolist() 📘 Rule violation ➹ Performance
New code collects a stream via collect(Collectors.toList()) instead of using the modern Stream.toList() API. This conflicts with the requirement to prefer modern Java (24+) APIs and may trigger modernization quality gates.
Agent Prompt
## Issue description
`GroupsEditorViewModel#getSuggestions` uses `collect(Collectors.toList())` even though `Stream.toList()` is the preferred modern API.
## Issue Context
The compliance checklist requires preferring modern Java (24+) APIs where applicable.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/fieldeditors/GroupsEditorViewModel.java[82-87]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
This comment has been minimized.
This comment has been minimized.
| <?xml version="1.0" encoding="UTF-8"?> | ||
|
|
||
| <?import javafx.scene.layout.HBox?> | ||
| <?import com.dlsc.gemsfx.TagsField?> | ||
| <fx:root xmlns:fx="http://javafx.com/fxml/1" type="HBox" xmlns="http://javafx.com/javafx/8.0.112" | ||
| fx:controller="org.jabref.gui.fieldeditors.GroupsEditor" > | ||
| <TagsField fx:id="groupTagsField" HBox.hgrow="ALWAYS"/> | ||
| </fx:root> |
There was a problem hiding this comment.
Parsing this is a bit silly, isnt it?
… into fix/issue-15108
| if (groupTagsField.getTags().size() < 2) { | ||
| isSortedTagsField = false; | ||
| } else if ((Comparators.isInOrder(groupTagsField.getTags(), Comparator.comparing(Keyword::get))) || isSortedTagsField) { | ||
| isSortedTagsField = true; | ||
| groupTagsField.getTags().sort(Comparator.comparing(Keyword::get)); | ||
| } |
There was a problem hiding this comment.
This definitly needs a comment. This feels totally weird.
Please use your own words, do not use an AI.
There was a problem hiding this comment.
It is for sorting tags. When the tags added it is automatically sorted but if manually sort them(drag-and-drop) it respect the user's behavior
| }); | ||
|
|
||
| groupTagsField.getEditor().setOnKeyPressed(event -> { | ||
| KeyBindingRepository keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); |
There was a problem hiding this comment.
Why dont you inject it with the other Injections above?
| if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { | ||
| Object content = event.getDragboard().getContent(DragAndDropDataFormats.GROUP); | ||
| if (bibEntry.isPresent() && content instanceof List<?> rawList && !rawList.isEmpty()) { | ||
| for (Object item : rawList) { | ||
| if (item instanceof String path && !path.isBlank()) { | ||
| String groupName = path.contains(" > ") ? path.substring(path.lastIndexOf(" > ") + 3) : path; | ||
| Keyword newGroup = new Keyword(groupName); | ||
| if (!groupTagsField.getTags().contains(newGroup)) { | ||
| groupTagsField.addTags(newGroup); | ||
| } | ||
| success = true; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This produces mental overload.
Fail fast principle.
Divide in separate methods.
Clean code!
| if (bibEntry.isPresent() && content instanceof List<?> rawList && !rawList.isEmpty()) { | ||
| for (Object item : rawList) { | ||
| if (item instanceof String path && !path.isBlank()) { | ||
| String groupName = path.contains(" > ") ? path.substring(path.lastIndexOf(" > ") + 3) : path; |
There was a problem hiding this comment.
Are you trying to parse Hierarichal Groups or something?
What does the chevron stand for? Use constants instead of magical strings
|
|
||
| tagLabel.setOnDragDetected(event -> { | ||
| Dragboard db = tagLabel.startDragAndDrop(TransferMode.MOVE); | ||
| ClipboardContent content = new ClipboardContent(); |
There was a problem hiding this comment.
This sets content on the Dragboard for drag-and-drop, not the system clipboard. If we used ClipBoardManager.setContent() here, it would put the text into the user's clipboard instead of attaching it to the drag operation — the drop handler wouldn't be able to read it back from event.getDragboard(). Same approach as KeywordsEditor and GroupTreeView.
| draggedGroup = Optional.of(group); | ||
| event.consume(); | ||
| }); | ||
| tagLabel.setOnDragEntered(_ -> tagLabel.setStyle("-fx-background-color: lightgrey;")); |
There was a problem hiding this comment.
Dont manually add styles. Put them in base.css and use standard colors.
| event.consume(); | ||
| }); | ||
| tagLabel.setOnDragEntered(_ -> tagLabel.setStyle("-fx-background-color: lightgrey;")); | ||
| tagLabel.setOnDragExited(_ -> tagLabel.setStyle("")); |
| public void execute() { | ||
| switch (command) { | ||
| case COPY -> { | ||
| clipBoardManager.setContent(group.get()); |
There was a problem hiding this comment.
So you DO know about ClipBoardManager.setContent
| class GroupsEditorViewModelTest { | ||
| @Test | ||
| void stringConverterWithHierarchicalGroups() { | ||
| String hierarchicalString = "parent > node > child"; | ||
| Keyword group = Keyword.ofHierarchical(hierarchicalString); | ||
|
|
||
| assertEquals(hierarchicalString, GroupsEditorViewModel.getStringConverter().toString(group)); | ||
| } | ||
| } |
|
@koppor what else should I update? Please let me know 🙏 |
* upstream/main: (59 commits) Fix 15000 identifier (JabRef#15286) Chore(deps): Bump dev.langchain4j:langchain4j-bom in /versions (JabRef#15305) Supress JavaFX VirtualFlow Info log noise for large libraries (10k+). (JabRef#15298) Chore(deps): Bump commons-logging:commons-logging in /versions (JabRef#15304) Fix merge dialog closing immediately when only one PDF importer returns metadata (JabRef#15127) (JabRef#15287) Fixed nullable eventhandlers (JabRef#15288) New Crowdin updates (JabRef#15285) Fix the ESC key for GlobalSearchResultDialog (JabRef#15259) Remove jbang plugin banner (JabRef#15282) Chore(deps): Bump org.apache.httpcomponents.core5:httpcore5 in /versions (JabRef#15281) Udpate to latest gradle master (JabRef#15279) Migrate to GemsFX Notifications (JabRef#14762) Chore(deps): Bump JetBrains/junie-github-action from 0 to 1 (JabRef#15272) Chore(deps): Bump docker/setup-qemu-action from 3 to 4 (JabRef#15269) Feature/citation count dropdown (JabRef#15216) Update dependency org.apache.maven.plugins:maven-resources-plugin to v3.5.0 (JabRef#15275) Chore(deps): Bump jablib/src/main/resources/csl-styles (JabRef#15273) Fix more security Fix pr_body leakage Chore: add dependency-management.md (JabRef#15278) ...
This comment has been minimized.
This comment has been minimized.
* upstream/main: fix jbang (JabRef#15311) New Crowdin updates (JabRef#15310) Fix exception in ExtractReferences from pdf (JabRef#15308) feat: add --entry-type-pattern option to citationkeys generate command (JabRef#15072) Fix reflection excpetion, add missing (JabRef#15307)
This comment has been minimized.
This comment has been minimized.
|
Please don't push us. We all have day jobs and a real life. As GSoC proposal period is coming closer we have to deal with more and more AI generated slop. As we noticed that your contribution was at least also co-generated by Claude AI we respectfully ask you to be patient. We prioritze non-AI contributions over AI-supported ones, because the main focus of JabRef is to support student contributors learning about about software engineering and open source. JabRef is a classroom, not a factory. |
✅ All tests passed ✅🏷️ Commit: 0f748d6 Learn more about TestLens at testlens.app. |
|
Dev-Call decision: Merge now and postpone fixes, as current state is already improving things. |
* feat: Add TagsField for keywords * fix for all feedback * Fix comment style in TagsEditor.java to use /// for multi-line comments * Reformat indentation in GroupsEditorViewModel.java to match code style * Convert GroupsEditor and KeywordsEditor from FXML to programmatic * address review feedback * Address feedbacks * remove GroupsEditorWiewModelTest * Resolve merge conflicts in KeywordsEditor and integrate escape handling Resolve merge conflicts from main merge into fix/issue-15108 branch. KeywordsEditor now properly extends TagsEditor and overrides the separator key handler to preserve the escaped keyword support added in main (JabRef#14929). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Injection by constructor * function references --------- Co-authored-by: intelliking <intelliking@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Siedlerchr <siedlerkiller@gmail.com> Co-authored-by: Carl Christian Snethlage <calixtus@users.noreply.github.com>
* feat: Add TagsField for keywords * fix for all feedback * Fix comment style in TagsEditor.java to use /// for multi-line comments * Reformat indentation in GroupsEditorViewModel.java to match code style * Convert GroupsEditor and KeywordsEditor from FXML to programmatic * address review feedback * Address feedbacks * remove GroupsEditorWiewModelTest * Resolve merge conflicts in KeywordsEditor and integrate escape handling Resolve merge conflicts from main merge into fix/issue-15108 branch. KeywordsEditor now properly extends TagsEditor and overrides the separator key handler to preserve the escaped keyword support added in main (JabRef#14929). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Injection by constructor * function references --------- Co-authored-by: intelliking <intelliking@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Siedlerchr <siedlerkiller@gmail.com> Co-authored-by: Carl Christian Snethlage <calixtus@users.noreply.github.com>
Related issues and pull requests
Closes: #15108
PR Description
This PR implements TagsField support for the Groups field by creating an abstract TagsEditor base class and refactoring both KeywordsEditor and GroupsEditor to inherit from it. The Groups field now provides the same modern tag-based interface as the Keywords field, including visual tags, drag-and-drop functionality, double-click editing, and context menus. This change ensures consistent user experience across both fields and eliminates the previous invalidation of user expectations when switching between Keywords and Groups fields, while also preventing code invalidation through proper inheritance hierarchy.
Steps to test
Checklist
CHANGELOG.mdin a way that can be understood by the average user (if change is visible to the user)