Skip to content

Commit 0238ef3

Browse files
authored
Merge branch 'main' into fix-issue-12289
2 parents a75a6fb + 8aacc0e commit 0238ef3

File tree

59 files changed

+559
-381
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+559
-381
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2525
- We added "All" option to the citation fetcher combo box, which queries all providers (CrossRef, OpenAlex, OpenCitations, SemanticScholar) and merges the results into a single deduplicated list.
2626
- We added a quick setting toggle to enable cover images download. [#15322](https://github.com/JabRef/jabref/pull/15322)
2727
- We now support refreshing existing CSL citations with respect to their in-text nature in the LibreOffice integration. [#15369](https://github.com/JabRef/jabref/pull/15369)
28+
- Added context menu entry "Sort tabs alphabetically" to the library tabs. [#15425](https://github.com/JabRef/jabref/pull/15425)
29+
- We added a "Merge" action in the File menu to compare the current library with a selected BibTeX file and review changes. [#15401](https://github.com/JabRef/jabref/issues/15401)
2830

2931
### Changed
3032

@@ -47,6 +49,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
4749

4850
### Fixed
4951

52+
- We fixed an issue where removed CSL files were not immediately cleared from the UI upon style removal. [#15438](https://github.com/JabRef/jabref/issues/15438)
5053
- We improved the group filter to support full boolean search syntax. [#12721](https://github.com/JabRef/jabref/issues/12721)
5154
- We fixed the column chooser context menu appearing when right-clicking the empty library table body. [#15384](https://github.com/JabRef/jabref/issues/15384)
5255
- We fixed web search rejecting queries with non-standard syntax. [#12637](https://github.com/JabRef/jabref/issues/12637)
@@ -92,6 +95,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
9295

9396
### Removed
9497

98+
- We removed GPT4All as AI-provider as this project is currently unmaintained and does not receive any security updates. [#15439](https://github.com/JabRef/jabref/pull/15439)
99+
95100
## [6.0-alpha.5] – 2026-02-20
96101

97102
### Added

build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
22
id("org.jabref.gradle.base.repositories")
33
id("org.jabref.gradle.feature.compile") // for openrewrite
4-
id("org.openrewrite.rewrite") version "7.29.0"
4+
id("org.openrewrite.rewrite") version "7.29.1"
55
id("org.itsallcode.openfasttrace") version "3.1.1"
66
id("org.cyclonedx.bom") version "3.2.3"
77
}
@@ -10,7 +10,7 @@ plugins {
1010
// This is the behavior when applied in the root project (https://docs.openrewrite.org/reference/gradle-plugin-configuration#multi-module-gradle-projects)
1111

1212
dependencies {
13-
rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:3.27.0"))
13+
rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:3.27.1"))
1414
rewrite("org.openrewrite.recipe:rewrite-static-analysis")
1515
rewrite("org.openrewrite.recipe:rewrite-logging-frameworks")
1616
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks")

jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public enum StandardActions implements Action {
7777
RELEVANT(Localization.lang("Toggle relevance"), IconTheme.JabRefIcons.RELEVANCE),
7878
NEW_LIBRARY(Localization.lang("New empty library"), IconTheme.JabRefIcons.NEW),
7979
OPEN_LIBRARY(Localization.lang("Open library..."), IconTheme.JabRefIcons.OPEN, KeyBinding.OPEN_LIBRARY),
80+
MERGE_LIBRARY(Localization.lang("Merge..."), IconTheme.JabRefIcons.MERGE_ENTRIES),
8081
IMPORT(Localization.lang("Import"), IconTheme.JabRefIcons.IMPORT),
8182
EXPORT(Localization.lang("Export"), IconTheme.JabRefIcons.EXPORT, KeyBinding.EXPORT),
8283
SAVE_LIBRARY(Localization.lang("Save library"), IconTheme.JabRefIcons.SAVE, KeyBinding.SAVE_LIBRARY),
@@ -124,6 +125,7 @@ public enum StandardActions implements Action {
124125
OPEN_FOLDER(Localization.lang("Open folder(s)"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER),
125126
OPEN_FILE(Localization.lang("Open file(s)"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
126127
OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE),
128+
SORT_TABS_ALPHABETICALLY(Localization.lang("Sort tabs alphabetically")),
127129
COPY_LINKED_FILES(Localization.lang("Copy linked file(s) to folder...")),
128130
COPY_DOI(Localization.lang("Copy DOI")),
129131
COPY_DOI_URL(Localization.lang("Copy DOI url")),

jabgui/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,7 @@ private Observable[] noticeDependencies() {
162162
aiPreferences.openAiChatModelProperty(),
163163
aiPreferences.mistralAiChatModelProperty(),
164164
aiPreferences.geminiChatModelProperty(),
165-
aiPreferences.huggingFaceChatModelProperty(),
166-
aiPreferences.gpt4AllChatModelProperty()
165+
aiPreferences.huggingFaceChatModelProperty()
167166
};
168167
}
169168

jabgui/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ private void initialize() {
5959
addPrivacyHyperlink(aiPolicies, AiProvider.MISTRAL_AI);
6060
addPrivacyHyperlink(aiPolicies, AiProvider.GEMINI);
6161
addPrivacyHyperlink(aiPolicies, AiProvider.HUGGING_FACE);
62-
addPrivacyHyperlink(aiPolicies, AiProvider.GPT4ALL);
6362

6463
String newEmbeddingModelText = embeddingModelText.getText().replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo());
6564
embeddingModelText.setText(newEmbeddingModelText);

jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.jabref.gui.collab;
22

33
import java.io.IOException;
4+
import java.nio.file.Path;
45
import java.util.List;
56

67
import org.jabref.gui.DialogService;
@@ -38,16 +39,22 @@ public List<DatabaseChange> scanForChanges() {
3839
}
3940

4041
try {
41-
// Parse the modified file
42-
// Important: apply all post-load actions
43-
ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences();
44-
ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, new DummyFileUpdateMonitor());
45-
BibDatabaseContext databaseOnDisk = result.getDatabaseContext();
46-
47-
return DatabaseChangeList.compareAndGetChanges(database, databaseOnDisk, databaseChangeResolverFactory);
42+
return getDatabaseChanges(database.getDatabasePath().get());
4843
} catch (IOException e) {
4944
LOGGER.warn("Error while parsing changed file.", e);
5045
return List.of();
5146
}
5247
}
48+
49+
public List<DatabaseChange> getDatabaseChanges(Path fileToCompare) throws IOException {
50+
ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences();
51+
ParserResult result = OpenDatabase.loadDatabase(fileToCompare, importFormatPreferences, new DummyFileUpdateMonitor());
52+
53+
if (result.isInvalid() || result.isEmpty()) {
54+
return List.of();
55+
}
56+
57+
BibDatabaseContext databaseOnDisk = result.getDatabaseContext();
58+
return DatabaseChangeList.compareAndGetChanges(database, databaseOnDisk, databaseChangeResolverFactory);
59+
}
5360
}

jabgui/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,18 @@ public class ExternalChangesResolverViewModel extends AbstractViewModel {
2828
private final BooleanBinding canAskUserToResolveChange;
2929

3030
public ExternalChangesResolverViewModel(@NonNull List<DatabaseChange> externalChanges) {
31-
assert !externalChanges.isEmpty();
32-
3331
this.visibleChanges.addAll(externalChanges);
3432
this.changes.addAll(externalChanges);
3533

36-
areAllChangesResolved = Bindings.createBooleanBinding(visibleChanges::isEmpty, visibleChanges);
37-
areAllChangesAccepted = Bindings.createBooleanBinding(() -> changes.stream().allMatch(DatabaseChange::isAccepted));
38-
areAllChangesDenied = Bindings.createBooleanBinding(() -> changes.stream().noneMatch(DatabaseChange::isAccepted));
34+
if (externalChanges.isEmpty()) {
35+
areAllChangesResolved = Bindings.createBooleanBinding(() -> false);
36+
areAllChangesAccepted = Bindings.createBooleanBinding(() -> false);
37+
areAllChangesDenied = Bindings.createBooleanBinding(() -> false);
38+
} else {
39+
areAllChangesResolved = Bindings.createBooleanBinding(visibleChanges::isEmpty, visibleChanges);
40+
areAllChangesAccepted = Bindings.createBooleanBinding(() -> changes.stream().allMatch(DatabaseChange::isAccepted));
41+
areAllChangesDenied = Bindings.createBooleanBinding(() -> changes.stream().noneMatch(DatabaseChange::isAccepted));
42+
}
3943
canAskUserToResolveChange = Bindings.createBooleanBinding(() -> selectedChange.isNotNull().get() && selectedChange.get().getExternalChangeResolver().isPresent(), selectedChange);
4044
}
4145

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.jabref.gui.collab;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.util.List;
6+
7+
import org.jabref.gui.DialogService;
8+
import org.jabref.gui.LibraryTab;
9+
import org.jabref.gui.LibraryTabContainer;
10+
import org.jabref.gui.StateManager;
11+
import org.jabref.gui.actions.ActionHelper;
12+
import org.jabref.gui.actions.SimpleCommand;
13+
import org.jabref.gui.preferences.GuiPreferences;
14+
import org.jabref.gui.undo.CountingUndoManager;
15+
import org.jabref.gui.undo.NamedCompoundEdit;
16+
import org.jabref.gui.util.FileDialogConfiguration;
17+
import org.jabref.gui.util.FileFilterConverter;
18+
import org.jabref.logic.l10n.Localization;
19+
import org.jabref.logic.util.BackgroundTask;
20+
import org.jabref.logic.util.StandardFileType;
21+
import org.jabref.logic.util.TaskExecutor;
22+
import org.jabref.model.database.BibDatabaseContext;
23+
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
public class MergeLibraryAction extends SimpleCommand {
28+
private static final Logger LOGGER = LoggerFactory.getLogger(MergeLibraryAction.class);
29+
30+
private final DialogService dialogService;
31+
private final StateManager stateManager;
32+
private final GuiPreferences preferences;
33+
private final TaskExecutor taskExecutor;
34+
private final CountingUndoManager undoManager;
35+
private final LibraryTabContainer libraryTabContainer;
36+
37+
public MergeLibraryAction(DialogService dialogService,
38+
StateManager stateManager,
39+
GuiPreferences preferences,
40+
TaskExecutor taskExecutor,
41+
CountingUndoManager undoManager,
42+
LibraryTabContainer libraryTabContainer) {
43+
this.dialogService = dialogService;
44+
this.stateManager = stateManager;
45+
this.preferences = preferences;
46+
this.taskExecutor = taskExecutor;
47+
this.undoManager = undoManager;
48+
this.libraryTabContainer = libraryTabContainer;
49+
50+
this.executable.bind(ActionHelper.needsDatabase(stateManager));
51+
}
52+
53+
@Override
54+
public void execute() {
55+
stateManager.getActiveDatabase().ifPresent(activeDatabase -> {
56+
Path initialDirectory = activeDatabase.getDatabasePath()
57+
.map(Path::getParent)
58+
.orElse(preferences.getImporterPreferences().getImportWorkingDirectory());
59+
60+
FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
61+
.addExtensionFilter(FileFilterConverter.toExtensionFilter(Localization.lang("BibTeX"), StandardFileType.BIBTEX_DB))
62+
.withDefaultExtension(StandardFileType.BIBTEX_DB)
63+
.withInitialDirectory(initialDirectory)
64+
.build();
65+
66+
dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(mergeFile -> {
67+
if (!Files.exists(mergeFile)) {
68+
dialogService.showErrorDialogAndWait(
69+
Localization.lang("Merge"),
70+
Localization.lang("File %0 not found.", mergeFile.getFileName().toString()));
71+
return;
72+
}
73+
74+
ChangeScanner changeScanner = new ChangeScanner(activeDatabase, dialogService, preferences, stateManager);
75+
BackgroundTask.wrap(() -> changeScanner.getDatabaseChanges(mergeFile))
76+
.onSuccess(changes -> showMergeDialog(activeDatabase, changes))
77+
.onFailure(exception -> {
78+
LOGGER.warn("Error while reading merge file {}", mergeFile, exception);
79+
dialogService.showErrorDialogAndWait(
80+
Localization.lang("Merge"),
81+
Localization.lang("Could not read merge file."));
82+
})
83+
.executeWith(taskExecutor);
84+
});
85+
});
86+
}
87+
88+
private void showMergeDialog(BibDatabaseContext activeDatabase, List<DatabaseChange> changes) {
89+
DatabaseChangesResolverDialog databaseChangesResolverDialog = new DatabaseChangesResolverDialog(
90+
changes,
91+
activeDatabase,
92+
Localization.lang("External Changes Resolver"));
93+
dialogService.showCustomDialogAndWait(databaseChangesResolverDialog);
94+
95+
NamedCompoundEdit compoundEdit = new NamedCompoundEdit(Localization.lang("Merged external changes"));
96+
changes.stream()
97+
.filter(DatabaseChange::isAccepted)
98+
.forEach(change -> change.applyChange(compoundEdit));
99+
compoundEdit.end();
100+
101+
if (compoundEdit.hasEdits()) {
102+
undoManager.addEdit(compoundEdit);
103+
104+
libraryTabContainer.getLibraryTabs().stream()
105+
.filter(tab -> tab.getBibDatabaseContext().equals(activeDatabase))
106+
.findFirst()
107+
.ifPresent(LibraryTab::markBaseChanged);
108+
}
109+
}
110+
}

jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,16 @@ private ContextMenu createTabContextMenuFor(LibraryTab tab) {
609609
LibraryTab currentTab = getCurrentLibraryTab();
610610
return (currentTab == null) ? null : currentTab.getBibDatabaseContext();
611611
}, stateManager, preferences, dialogService)),
612+
factory.createMenuItem(StandardActions.SORT_TABS_ALPHABETICALLY, new SimpleCommand() {
613+
@Override
614+
public void execute() {
615+
tabbedPane.getTabs().sort((tab1, tab2) -> {
616+
String text1 = tab1.getText() != null ? tab1.getText() : "";
617+
String text2 = tab2.getText() != null ? tab2.getText() : "";
618+
return text1.compareToIgnoreCase(text2);
619+
});
620+
}
621+
}),
612622
new SeparatorMenuItem(),
613623
factory.createMenuItem(StandardActions.CLOSE_LIBRARY,
614624
new CloseDatabaseAction(this, tab, stateManager)),

jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.jabref.gui.citationkeypattern.GenerateCitationKeyAction;
2020
import org.jabref.gui.cleanup.CleanupAction;
2121
import org.jabref.gui.clipboard.ClipBoardManager;
22+
import org.jabref.gui.collab.MergeLibraryAction;
2223
import org.jabref.gui.consistency.ConsistencyCheckAction;
2324
import org.jabref.gui.copyfiles.CopyFilesAction;
2425
import org.jabref.gui.documentviewer.ShowDocumentViewerAction;
@@ -179,6 +180,8 @@ private void createMenu() {
179180

180181
new SeparatorMenuItem(),
181182

183+
factory.createMenuItem(StandardActions.MERGE_LIBRARY, new MergeLibraryAction(dialogService, stateManager, preferences, taskExecutor, undoManager, frame)),
184+
182185
factory.createSubMenu(StandardActions.IMPORT,
183186
factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(frame, ImportCommand.ImportMethod.TO_EXISTING, preferences, stateManager, fileUpdateMonitor, taskExecutor, dialogService)),
184187
factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(frame, ImportCommand.ImportMethod.AS_NEW, preferences, stateManager, fileUpdateMonitor, taskExecutor, dialogService))),

0 commit comments

Comments
 (0)