Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b66c999
groups: add model classes: AutomaticDateGroup + DateGroup + some basi…
Oct 14, 2025
24e2ce4
Merge branch 'main' of https://github.com/JabRef/jabref into model-au…
Oct 16, 2025
470cb2e
model(groups): adding granularity to to model so it can now group by …
Oct 16, 2025
c80b3af
Fix formatting/checkstyle, apply OpenRewrite, add CHANGELOG
Oct 16, 2025
968232f
Match OpenRewrite/formatting expected by CI
Oct 16, 2025
28e7783
Merge pull request #1 from elliotgnn/model-auto-date-groups
elliotgnn Oct 17, 2025
b1d2d7d
Add serialization support for AutomaticDateGroup
XMJ3083 Oct 17, 2025
e428640
Add deserialization support for AutomaticDateGroup
XMJ3083 Oct 17, 2025
0d48242
Add comprehensive tests for AutomaticDateGroup serialization
XMJ3083 Oct 17, 2025
3e211e8
Fix: Add missing DateGranularity import to GroupSerializer
XMJ3083 Oct 17, 2025
e44970d
Fix: Remove unused DateGranularity import from GroupSerializer
XMJ3083 Oct 17, 2025
40aafb6
Add JavaDoc comments to DateGranularity and DateGroup
XMJ3083 Oct 18, 2025
e08deab
Merge pull request #2 from elliotgnn/logic-serialization
XMJ3083 Oct 18, 2025
5d18207
Add UI components for automatic year groups
u7713884 Oct 18, 2025
98e80f1
Add Date group functionality for automatic grouping
u7713884 Oct 19, 2025
3d8af40
Fix the error popping up when right-clicking the group.
u7713884 Oct 21, 2025
d4ea458
Remove obsolete localization keys from JabRef_en.properties
u7713884 Oct 22, 2025
a054388
Clean up redundant comments in GroupDialogViewModel and GroupNodeView…
u7713884 Oct 22, 2025
fafea35
Merge pull request #3 from elliotgnn/gui
XingyuDu2025 Oct 23, 2025
050ad03
resolving conflicts for csl-locals and csl-styles
Oct 25, 2025
e40815d
test bug fix BibtexParserTest.java:1453
Oct 25, 2025
227c45d
fix jbang
Oct 25, 2025
b4ca15f
turn it back to original case
Oct 25, 2025
9b62d0c
Merge pull request #5 from elliotgnn/merge_sync
elliotgnn Oct 25, 2025
4857040
Fix submodules
Oct 25, 2025
b1193c6
Merge branch 'main' into implmentation-issue-10822
elliotgnn Oct 25, 2025
015171d
Merge branch 'main' of https://github.com/JabRef/jabref into implment…
Oct 25, 2025
bd49890
Merge: keep upstream/main submodule pointers
Oct 25, 2025
970017d
Fix submodules
Oct 25, 2025
12fc167
Merge branch 'main' into implmentation-issue-10822
Siedlerchr Nov 3, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- We added automatic date-based groups that create year/month/day subgroups from an entry’s date fields. [#10822](https://github.com/JabRef/jabref/issues/10822)

### Changed

### Fixed
Expand Down
18 changes: 18 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.DateGranularity;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.search.SearchFlags;
Expand Down Expand Up @@ -101,6 +104,11 @@ public class GroupDialogView extends BaseDialog<AbstractGroup> {

@FXML private TextField texGroupFilePath;

@FXML private RadioButton dateRadioButton;
@FXML private ComboBox<Field> dateGroupFieldCombo;
@FXML private ComboBox<DateGranularity> dateGroupOptionCombo;
@FXML private CheckBox dateGroupIncludeEmpty;

private final EnumMap<GroupHierarchyType, String> hierarchyText = new EnumMap<>(GroupHierarchyType.class);
private final EnumMap<GroupHierarchyType, String> hierarchyToolTip = new EnumMap<>(GroupHierarchyType.class);

Expand Down Expand Up @@ -222,6 +230,16 @@ public void initialize() {

texGroupFilePath.textProperty().bindBidirectional(viewModel.texGroupFilePathProperty());

// Date Group bindings
dateRadioButton.selectedProperty().bindBidirectional(viewModel.dateRadioButtonSelectedProperty());
dateGroupFieldCombo.valueProperty().bindBidirectional(viewModel.dateGroupFieldProperty());
dateGroupOptionCombo.valueProperty().bindBidirectional(viewModel.dateGroupOptionProperty());
dateGroupIncludeEmpty.selectedProperty().bindBidirectional(viewModel.dateGroupIncludeEmptyProperty());

// Initialize Date Group ComboBoxes
dateGroupFieldCombo.setItems(FXCollections.observableArrayList(FieldFactory.getDateFields()));
dateGroupOptionCombo.setItems(FXCollections.observableArrayList(DateGranularity.values()));

validationVisualizer.setDecoration(new IconValidationDecorator());
Platform.runLater(() -> {
validationVisualizer.initVisualization(viewModel.nameValidationStatus(), nameField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.Keyword;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.AutomaticDateGroup;
import org.jabref.model.groups.AutomaticGroup;
import org.jabref.model.groups.AutomaticKeywordGroup;
import org.jabref.model.groups.AutomaticPersonsGroup;
import org.jabref.model.groups.DateGranularity;
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.GroupTreeNode;
Expand Down Expand Up @@ -98,6 +102,12 @@ public class GroupDialogViewModel {

private final StringProperty texGroupFilePathProperty = new SimpleStringProperty("");

// Date Group Properties
private final BooleanProperty dateRadioButtonSelectedProperty = new SimpleBooleanProperty();
private final ObjectProperty<Field> dateGroupFieldProperty = new SimpleObjectProperty<>();
private final ObjectProperty<DateGranularity> dateGroupOptionProperty = new SimpleObjectProperty<>();
private final BooleanProperty dateGroupIncludeEmptyProperty = new SimpleBooleanProperty();

private Validator nameValidator;
private Validator nameContainsDelimiterValidator;
private Validator sameNameValidator;
Expand Down Expand Up @@ -376,6 +386,13 @@ public AbstractGroup resultConverter(ButtonType button) {
currentDatabase.getMetaData(),
preferences.getFilePreferences().getUserAndHost()
);
} else if (Boolean.TRUE.equals(dateRadioButtonSelectedProperty.getValue())) {
resultingGroup = new AutomaticDateGroup(
groupName,
groupHierarchySelectedProperty.getValue(),
dateGroupFieldProperty.getValue(),
dateGroupOptionProperty.getValue()
);
}

if (resultingGroup != null) {
Expand Down Expand Up @@ -413,6 +430,11 @@ public void setValues() {
typeExplicitProperty.setValue(true);
groupHierarchySelectedProperty.setValue(preferences.getGroupsPreferences().getDefaultHierarchicalContext());
autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE);

// Initialize Date Group defaults
dateGroupFieldProperty.setValue(StandardField.DATE);
dateGroupOptionProperty.setValue(DateGranularity.YEAR);
dateGroupIncludeEmptyProperty.setValue(false);
} else {
nameProperty.setValue(editedGroup.getName());
colorUseProperty.setValue(editedGroup.getColor().isPresent());
Expand Down Expand Up @@ -458,6 +480,12 @@ public void setValues() {
AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup;
autoGroupPersonsOptionProperty.setValue(Boolean.TRUE);
autoGroupPersonsFieldProperty.setValue(group.getField().getName());
} else if (editedGroup.getClass() == AutomaticDateGroup.class) {
AutomaticDateGroup group = (AutomaticDateGroup) editedGroup;
dateRadioButtonSelectedProperty.setValue(Boolean.TRUE);
dateGroupFieldProperty.setValue(group.getField());
dateGroupOptionProperty.setValue(group.getGranularity());
dateGroupIncludeEmptyProperty.setValue(false);
}
} else if (editedGroup.getClass() == TexGroup.class) {
typeTexProperty.setValue(true);
Expand Down Expand Up @@ -651,6 +679,23 @@ public StringProperty texGroupFilePathProperty() {
return texGroupFilePathProperty;
}

// Date Group Property Getters
public BooleanProperty dateRadioButtonSelectedProperty() {
return dateRadioButtonSelectedProperty;
}

public ObjectProperty<Field> dateGroupFieldProperty() {
return dateGroupFieldProperty;
}

public ObjectProperty<DateGranularity> dateGroupOptionProperty() {
return dateGroupOptionProperty;
}

public BooleanProperty dateGroupIncludeEmptyProperty() {
return dateGroupIncludeEmptyProperty;
}

private boolean groupOrSubgroupIsSearchGroup(GroupTreeNode groupTreeNode) {
if (groupTreeNode.getGroup() instanceof SearchGroup) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.jabref.model.entry.BibEntry;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.AllEntriesGroup;
import org.jabref.model.groups.AutomaticDateGroup;
import org.jabref.model.groups.AutomaticGroup;
import org.jabref.model.groups.AutomaticKeywordGroup;
import org.jabref.model.groups.AutomaticPersonsGroup;
Expand Down Expand Up @@ -461,6 +462,8 @@ public boolean canAddEntriesIn() {
return false;
} else if (group instanceof TexGroup) {
return false;
} else if (group instanceof AutomaticDateGroup) {
return false;
} else {
throw new UnsupportedOperationException("canAddEntriesIn method not yet implemented in group: " + group.getClass().getName());
}
Expand All @@ -476,6 +479,7 @@ public boolean canBeDragged() {
SearchGroup _,
AutomaticKeywordGroup _,
AutomaticPersonsGroup _,
AutomaticDateGroup _,
TexGroup _ ->
true;
case KeywordGroup _ ->
Expand Down Expand Up @@ -503,6 +507,7 @@ public boolean canAddGroupsIn() {
true;
case AutomaticKeywordGroup _,
AutomaticPersonsGroup _,
AutomaticDateGroup _,
SmartGroup _ ->
false;
case KeywordGroup _ ->
Expand All @@ -528,6 +533,7 @@ public boolean canRemove() {
SearchGroup _,
AutomaticKeywordGroup _,
AutomaticPersonsGroup _,
AutomaticDateGroup _,
TexGroup _ ->
true;
case KeywordGroup _ ->
Expand All @@ -553,6 +559,7 @@ public boolean isEditable() {
SearchGroup _,
AutomaticKeywordGroup _,
AutomaticPersonsGroup _,
AutomaticDateGroup _,
TexGroup _ ->
true;
case KeywordGroup _ ->
Expand Down
18 changes: 18 additions & 0 deletions jabgui/src/main/resources/org/jabref/gui/groups/GroupDialog.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@
<Tooltip text="%Group containing entries cited in a given TeX file"/>
</tooltip>
</RadioButton>
<RadioButton fx:id="dateRadioButton" toggleGroup="$type" wrapText="true"
text="%Date">
<tooltip>
<Tooltip text="%Automatically create groups by date"/>
</tooltip>
</RadioButton>
</VBox>
<Separator orientation="VERTICAL"/>
<StackPane HBox.hgrow="ALWAYS">
Expand Down Expand Up @@ -183,6 +189,18 @@
<Button onAction="#texGroupBrowse" text="%Browse" prefHeight="30.0"/>
</HBox>
</VBox>
<VBox visible="${dateRadioButton.selected}" spacing="10.0">
<VBox>
<Label text="%Field to extract date from"/>
<ComboBox fx:id="dateGroupFieldCombo"/>
</VBox>
<VBox>
<Label text="%Date grouping option"/>
<ComboBox fx:id="dateGroupOptionCombo"/>
</VBox>
<CheckBox fx:id="dateGroupIncludeEmpty"
text="%Include entries without date"/>
</VBox>
</StackPane>
</HBox>
</VBox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.jabref.logic.util.strings.StringUtil;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.AllEntriesGroup;
import org.jabref.model.groups.AutomaticDateGroup;
import org.jabref.model.groups.AutomaticGroup;
import org.jabref.model.groups.AutomaticKeywordGroup;
import org.jabref.model.groups.AutomaticPersonsGroup;
Expand Down Expand Up @@ -141,6 +142,8 @@ private String serializeGroup(AbstractGroup group) {
serializeAutomaticKeywordGroup(keywordGroup);
case AutomaticPersonsGroup personsGroup ->
serializeAutomaticPersonsGroup(personsGroup);
case AutomaticDateGroup dateGroup ->
serializeAutomaticDateGroup(dateGroup);
case TexGroup texGroup ->
serializeTexGroup(texGroup);
case null ->
Expand Down Expand Up @@ -175,6 +178,18 @@ private String serializeAutomaticPersonsGroup(AutomaticPersonsGroup group) {
return sb.toString();
}

private String serializeAutomaticDateGroup(AutomaticDateGroup group) {
StringBuilder sb = new StringBuilder();
sb.append(MetadataSerializationConfiguration.AUTOMATIC_DATE_GROUP_ID);
appendAutomaticGroupDetails(sb, group);
sb.append(StringUtil.quote(group.getField().getName(), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR));
sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR);
sb.append(StringUtil.quote(group.getGranularity().name(), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR));
sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR);
appendGroupDetails(sb, group);
return sb.toString();
}

private void appendAutomaticGroupDetails(StringBuilder builder, AutomaticGroup group) {
builder.append(StringUtil.quote(group.getName(), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR));
builder.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.AutomaticDateGroup;
import org.jabref.model.groups.AutomaticKeywordGroup;
import org.jabref.model.groups.AutomaticPersonsGroup;
import org.jabref.model.groups.DateGranularity;
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.GroupTreeNode;
Expand Down Expand Up @@ -118,6 +120,9 @@ public static AbstractGroup fromString(String s, Character keywordSeparator, Fil
if (s.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID)) {
return automaticKeywordGroupFromString(s);
}
if (s.startsWith(MetadataSerializationConfiguration.AUTOMATIC_DATE_GROUP_ID)) {
return automaticDateGroupFromString(s);
}
if (s.startsWith(MetadataSerializationConfiguration.TEX_GROUP_ID)) {
return texGroupFromString(s, fileMonitor, metaData, userAndHost);
}
Expand Down Expand Up @@ -165,6 +170,23 @@ private static AbstractGroup automaticPersonsGroupFromString(String string) {
return newGroup;
}

private static AbstractGroup automaticDateGroupFromString(String string) {
if (!string.startsWith(MetadataSerializationConfiguration.AUTOMATIC_DATE_GROUP_ID)) {
throw new IllegalArgumentException("AutomaticDateGroup cannot be created from \"" + string + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(string.substring(MetadataSerializationConfiguration.AUTOMATIC_DATE_GROUP_ID
.length()), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);

String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(tok.nextToken()));
Field field = FieldFactory.parseField(StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR));
String granularityString = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
DateGranularity granularity = DateGranularity.valueOf(granularityString);
AutomaticDateGroup newGroup = new AutomaticDateGroup(name, context, field, granularity);
addGroupDetails(tok, newGroup);
return newGroup;
}

private static AbstractGroup automaticKeywordGroupFromString(String string) {
if (!string.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID)) {
throw new IllegalArgumentException("KeywordGroup cannot be created from \"" + string + "\".");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public class MetadataSerializationConfiguration {
*/
public static final String TEX_GROUP_ID = "TexGroup:";

/**
* Identifier for AutomaticDateGroup.
*/
public static final String AUTOMATIC_DATE_GROUP_ID = "AutomaticDateGroup:";

private MetadataSerializationConfiguration() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ public static Set<Field> getPersonNameFields() {
return getFieldsFiltered(field -> field.getProperties().contains(FieldProperty.PERSON_NAMES));
}

public static Set<Field> getDateFields() {
return getFieldsFiltered(field -> field.getProperties().contains(FieldProperty.DATE));
}

private static Set<Field> getFieldsFiltered(Predicate<Field> selector) {
return getAllFields().stream()
.filter(selector)
Expand Down
Loading
Loading