From fe5812fbd514032064fbff02d28e02deaae85eeb Mon Sep 17 00:00:00 2001 From: jhz <070jhz@proton.me> Date: Wed, 4 Jun 2025 02:54:47 +0200 Subject: [PATCH 01/11] fix race condition in openDatabaseList binding by avoiding early FilteredList usage --- .../java/org/jabref/gui/frame/JabRefFrame.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 52ba69e2ca7..46a46f50492 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -374,13 +374,15 @@ private void initKeyBindings() { } private void initBindings() { - // Bind global state - FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); - filteredTabs.setPredicate(LibraryTab.class::isInstance); - // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected - openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); - EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); + openDatabaseList = EasyBind.map(tabbedPane.getTabs(), tab -> { + if (tab instanceof LibraryTab) { + return ((LibraryTab) tab).getBibDatabaseContext(); + } else { + return null; + } + }); + EasyBind.bindContent(stateManager.getOpenDatabases(), new FilteredList<>(openDatabaseList, Objects::nonNull)); // the binding for stateManager.activeDatabaseProperty() is at org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed From c4d94bbe7f5eb1a53e4ecb3f9bb81e1b2e04163b Mon Sep 17 00:00:00 2001 From: 070jhz <070jhz@proton.me> Date: Wed, 4 Jun 2025 08:49:38 +0200 Subject: [PATCH 02/11] use pattern matching for instanceof in initBindings() Co-authored-by: Subhramit Basu --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 46a46f50492..390de54a126 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -376,8 +376,8 @@ private void initKeyBindings() { private void initBindings() { // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected openDatabaseList = EasyBind.map(tabbedPane.getTabs(), tab -> { - if (tab instanceof LibraryTab) { - return ((LibraryTab) tab).getBibDatabaseContext(); + if (tab instanceof LibraryTab libraryTab) { + return libraryTab.getBibDatabaseContext(); } else { return null; } From e7af4a4ea06516cf27fc23ffaad4e19018f4723c Mon Sep 17 00:00:00 2001 From: jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 14:38:13 +0200 Subject: [PATCH 03/11] replaced FilteredList usage with manual reactive filtering --- .../org/jabref/gui/frame/JabRefFrame.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 390de54a126..c0d6bb67e24 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -15,7 +15,6 @@ import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; import javafx.event.Event; import javafx.geometry.Orientation; import javafx.scene.control.ContextMenu; @@ -382,7 +381,10 @@ private void initBindings() { return null; } }); - EasyBind.bindContent(stateManager.getOpenDatabases(), new FilteredList<>(openDatabaseList, Objects::nonNull)); + + // call compromised until further notice + // EasyBind.bindContent(stateManager.getOpenDatabases(), new FilteredList<>(openDatabaseList)); + bindContentFiltered(openDatabaseList, stateManager.getOpenDatabases(), Objects::nonNull); // the binding for stateManager.activeDatabaseProperty() is at org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed @@ -456,6 +458,45 @@ private void initBindings() { EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible()); } + private static void bindContentFiltered(ObservableList source, ObservableList target, java.util.function.Predicate filter) { + // Initial sync + target.setAll(source.stream().filter(Objects::nonNull).toList()); + + source.addListener((ListChangeListener) c -> { + while (c.next()) { + if (c.wasPermutated()) { + // Full reorder pass + List reordered = source.stream().filter(filter).toList(); + target.setAll(reordered); + } + + if (c.wasRemoved()) { + for (T removed : c.getRemoved()) { + target.remove(removed); + } + } + + if (c.wasAdded()) { + int sourceIndex = c.getFrom(); + int targetIndex = 0; + + for (int i = 0; i < sourceIndex; i++) { + T element = source.get(i); + if (filter.test(element)) { + targetIndex++; + } + } + + for (T added : c.getAddedSubList()) { + if (filter.test(added)) { + target.add(targetIndex++, added); + } + } + } + } + }); + } + private void updateTabBarVisible() { if (preferences.getWorkspacePreferences().shouldHideTabBar() && stateManager.getOpenDatabases().size() <= 1) { if (!tabbedPane.getStyleClass().contains("hide-tab-bar")) { From 02204dc45061df91096a9df4c3fa551c33c57b5e Mon Sep 17 00:00:00 2001 From: jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 15:37:37 +0200 Subject: [PATCH 04/11] fix argument --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index c0d6bb67e24..7516d1d5743 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -460,7 +460,7 @@ private void initBindings() { private static void bindContentFiltered(ObservableList source, ObservableList target, java.util.function.Predicate filter) { // Initial sync - target.setAll(source.stream().filter(Objects::nonNull).toList()); + target.setAll(source.stream().filter(filter).toList()); source.addListener((ListChangeListener) c -> { while (c.next()) { From 294d25d6e602d51ead13c267e967f9c49d0b4680 Mon Sep 17 00:00:00 2001 From: 070jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 17:06:59 +0200 Subject: [PATCH 05/11] Update jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java Co-authored-by: Oliver Kopp --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 7516d1d5743..4c8cc0c7b86 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -382,7 +382,7 @@ private void initBindings() { } }); - // call compromised until further notice + // call compromised until further notice, See https://github.com/JabRef/jabref-koppor/pull/713 for details. // EasyBind.bindContent(stateManager.getOpenDatabases(), new FilteredList<>(openDatabaseList)); bindContentFiltered(openDatabaseList, stateManager.getOpenDatabases(), Objects::nonNull); From a415a157252649025531f3ad77be038da93bfaea Mon Sep 17 00:00:00 2001 From: 070jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 17:07:40 +0200 Subject: [PATCH 06/11] Update jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java Co-authored-by: Oliver Kopp --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 4c8cc0c7b86..35ea279c56d 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -465,7 +465,7 @@ private static void bindContentFiltered(ObservableList source, Observable source.addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasPermutated()) { - // Full reorder pass + // We need a fresh copy as permutation is much harder to mirror List reordered = source.stream().filter(filter).toList(); target.setAll(reordered); } From 21e82e40b5415e8ac964d40ff065626de91c8e77 Mon Sep 17 00:00:00 2001 From: 070jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 17:08:48 +0200 Subject: [PATCH 07/11] Update jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java Co-authored-by: Oliver Kopp --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 35ea279c56d..0351e8e7334 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -480,6 +480,7 @@ private static void bindContentFiltered(ObservableList source, Observable int sourceIndex = c.getFrom(); int targetIndex = 0; + // We need to add at the correct place - therefore, we need to find out the correct position for (int i = 0; i < sourceIndex; i++) { T element = source.get(i); if (filter.test(element)) { From 4c7dc1309af9e57a58c84047f6ed51414e2ae162 Mon Sep 17 00:00:00 2001 From: jhz <070jhz@proton.me> Date: Thu, 5 Jun 2025 18:12:18 +0200 Subject: [PATCH 08/11] updated bindContentFiltered to filter on Tab using passed predicate --- .../org/jabref/gui/frame/JabRefFrame.java | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 0351e8e7334..21850d8ff5b 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -373,18 +373,15 @@ private void initKeyBindings() { } private void initBindings() { - // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected - openDatabaseList = EasyBind.map(tabbedPane.getTabs(), tab -> { - if (tab instanceof LibraryTab libraryTab) { - return libraryTab.getBibDatabaseContext(); - } else { - return null; - } - }); + // FIXME: See https://github.com/JabRef/jabref-koppor/pull/713 - workaround in place until issue is resolved. + // Original code used FilteredList and EasyBind to filter and map tabs directly: + // FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); + // filteredTabs.setPredicate(LibraryTab.class::isInstance); + // openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); + // EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); + // Once JabRef#713 is fixed, remove this comment and the bindContentFiltered() method, and restore the original code - // call compromised until further notice, See https://github.com/JabRef/jabref-koppor/pull/713 for details. - // EasyBind.bindContent(stateManager.getOpenDatabases(), new FilteredList<>(openDatabaseList)); - bindContentFiltered(openDatabaseList, stateManager.getOpenDatabases(), Objects::nonNull); + bindContentFiltered(tabbedPane.getTabs(), stateManager.getOpenDatabases(), LibraryTab.class::isInstance); // the binding for stateManager.activeDatabaseProperty() is at org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed @@ -458,21 +455,30 @@ private void initBindings() { EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible()); } - private static void bindContentFiltered(ObservableList source, ObservableList target, java.util.function.Predicate filter) { + private static void bindContentFiltered(ObservableList source, ObservableList target, java.util.function.Predicate filter) { + java.util.function.Function tabToContext = tab -> ((LibraryTab) tab).getBibDatabaseContext(); // Initial sync - target.setAll(source.stream().filter(filter).toList()); + target.setAll(source.stream() + .filter(filter) + .map(tabToContext) + .toList()); - source.addListener((ListChangeListener) c -> { + source.addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasPermutated()) { // We need a fresh copy as permutation is much harder to mirror - List reordered = source.stream().filter(filter).toList(); + List reordered = source.stream() + .filter(filter) + .map(tabToContext) + .toList(); target.setAll(reordered); } if (c.wasRemoved()) { - for (T removed : c.getRemoved()) { - target.remove(removed); + for (Tab removed : c.getRemoved()) { + if (filter.test(removed)) { + target.remove(tabToContext.apply(removed)); + } } } @@ -482,15 +488,15 @@ private static void bindContentFiltered(ObservableList source, Observable // We need to add at the correct place - therefore, we need to find out the correct position for (int i = 0; i < sourceIndex; i++) { - T element = source.get(i); - if (filter.test(element)) { + Tab tab = source.get(i); + if (filter.test(tab)) { targetIndex++; } } - for (T added : c.getAddedSubList()) { + for (Tab added : c.getAddedSubList()) { if (filter.test(added)) { - target.add(targetIndex++, added); + target.add(targetIndex++, tabToContext.apply(added)); } } } From d732f9ed7358de0a78d7b9a9cec8f2f445ab2d6d Mon Sep 17 00:00:00 2001 From: jhz <070jhz@proton.me> Date: Fri, 6 Jun 2025 20:26:55 +0200 Subject: [PATCH 09/11] refactor: import Predicate and Function from java.util.function --- jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 21850d8ff5b..aedb8901feb 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import javafx.application.Platform; @@ -455,8 +457,8 @@ private void initBindings() { EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible()); } - private static void bindContentFiltered(ObservableList source, ObservableList target, java.util.function.Predicate filter) { - java.util.function.Function tabToContext = tab -> ((LibraryTab) tab).getBibDatabaseContext(); + private static void bindContentFiltered(ObservableList source, ObservableList target, Predicate filter) { + Function tabToContext = tab -> ((LibraryTab) tab).getBibDatabaseContext(); // Initial sync target.setAll(source.stream() .filter(filter) From ad92efa53bf03c0137d8c9d87e00bc1e97eac6fd Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage Date: Sat, 7 Jun 2025 19:40:35 +0200 Subject: [PATCH 10/11] Move helper method to helper class Co-authored-by: Oliver Kopp --- .../org/jabref/gui/frame/JabRefFrame.java | 61 +----------------- .../org/jabref/gui/util/BindingsHelper.java | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java index aedb8901feb..4280e9b5ff4 100644 --- a/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/jabgui/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.function.Supplier; import javafx.application.Platform; @@ -375,15 +373,7 @@ private void initKeyBindings() { } private void initBindings() { - // FIXME: See https://github.com/JabRef/jabref-koppor/pull/713 - workaround in place until issue is resolved. - // Original code used FilteredList and EasyBind to filter and map tabs directly: - // FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); - // filteredTabs.setPredicate(LibraryTab.class::isInstance); - // openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); - // EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); - // Once JabRef#713 is fixed, remove this comment and the bindContentFiltered() method, and restore the original code - - bindContentFiltered(tabbedPane.getTabs(), stateManager.getOpenDatabases(), LibraryTab.class::isInstance); + BindingsHelper.bindContentFiltered(tabbedPane.getTabs(), stateManager.getOpenDatabases(), LibraryTab.class::isInstance); // the binding for stateManager.activeDatabaseProperty() is at org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed @@ -457,55 +447,6 @@ private void initBindings() { EasyBind.subscribe(preferences.getWorkspacePreferences().hideTabBarProperty(), _ -> updateTabBarVisible()); } - private static void bindContentFiltered(ObservableList source, ObservableList target, Predicate filter) { - Function tabToContext = tab -> ((LibraryTab) tab).getBibDatabaseContext(); - // Initial sync - target.setAll(source.stream() - .filter(filter) - .map(tabToContext) - .toList()); - - source.addListener((ListChangeListener) c -> { - while (c.next()) { - if (c.wasPermutated()) { - // We need a fresh copy as permutation is much harder to mirror - List reordered = source.stream() - .filter(filter) - .map(tabToContext) - .toList(); - target.setAll(reordered); - } - - if (c.wasRemoved()) { - for (Tab removed : c.getRemoved()) { - if (filter.test(removed)) { - target.remove(tabToContext.apply(removed)); - } - } - } - - if (c.wasAdded()) { - int sourceIndex = c.getFrom(); - int targetIndex = 0; - - // We need to add at the correct place - therefore, we need to find out the correct position - for (int i = 0; i < sourceIndex; i++) { - Tab tab = source.get(i); - if (filter.test(tab)) { - targetIndex++; - } - } - - for (Tab added : c.getAddedSubList()) { - if (filter.test(added)) { - target.add(targetIndex++, tabToContext.apply(added)); - } - } - } - } - }); - } - private void updateTabBarVisible() { if (preferences.getWorkspacePreferences().shouldHideTabBar() && stateManager.getOpenDatabases().size() <= 1) { if (!tabbedPane.getStyleClass().contains("hide-tab-bar")) { diff --git a/jabgui/src/main/java/org/jabref/gui/util/BindingsHelper.java b/jabgui/src/main/java/org/jabref/gui/util/BindingsHelper.java index 48cef619852..487111614c3 100644 --- a/jabgui/src/main/java/org/jabref/gui/util/BindingsHelper.java +++ b/jabgui/src/main/java/org/jabref/gui/util/BindingsHelper.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; @@ -19,6 +20,10 @@ import javafx.collections.ObservableMap; import javafx.css.PseudoClass; import javafx.scene.Node; +import javafx.scene.control.Tab; + +import org.jabref.gui.LibraryTab; +import org.jabref.model.database.BibDatabaseContext; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.PreboundBinding; @@ -189,6 +194,63 @@ public static Subscription subscribeFuture(ObservableValue observable, Co return () -> observable.removeListener(listener); } + public static void bindContentFiltered(ObservableList source, ObservableList target, Predicate filter) { + // FIXME: See https://github.com/JabRef/jabref-koppor/pull/713 - workaround in place until issue is resolved. + // Original code used FilteredList and EasyBind to filter and map tabs directly: + // FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); + // filteredTabs.setPredicate(LibraryTab.class::isInstance); + // openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); + // EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); + // Once JabRef#713 is fixed, remove this comment and the bindContentFiltered() method, and restore the original code + + Function tabToContext = tab -> ((LibraryTab) tab).getBibDatabaseContext(); + // Initial sync + target.setAll(source.stream() + .filter(filter) + .map(tabToContext) + .toList()); + + source.addListener((ListChangeListener) change -> { + while (change.next()) { + if (change.wasPermutated()) { + // We need a fresh copy as permutation is much harder to mirror + List reordered = source.stream() + .filter(filter) + .map(tabToContext) + .toList(); + target.setAll(reordered); + } + + if (change.wasRemoved()) { + for (Tab removed : change.getRemoved()) { + if (filter.test(removed)) { + target.remove(tabToContext.apply(removed)); + } + } + } + + if (change.wasAdded()) { + int sourceIndex = change.getFrom(); + int targetIndex = 0; + + // We need to add at the correct place - therefore, we need to find out the correct position + for (int i = 0; i < sourceIndex; i++) { + Tab tab = source.get(i); + if (filter.test(tab)) { + targetIndex++; + } + } + + for (Tab added : change.getAddedSubList()) { + if (filter.test(added)) { + target.add(targetIndex++, tabToContext.apply(added)); + } + } + } + } + }); + } + private static class BidirectionalBinding { private final ObservableValue propertyA; From 34644708775aaf7f44601350774d745747961887 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage Date: Sat, 7 Jun 2025 19:42:06 +0200 Subject: [PATCH 11/11] CHANGELOG.md Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30029a698af..2dd90147968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a fallback for the "Convert to biblatex" cleanup when it failed to populate the `date` field if `year` contained a full date in ISO format (e.g., `2011-11-11`). [#11868](https://github.com/JabRef/jabref/issues/11868) - We fixed an issue where directory check for relative path was not handled properly under library properties. [#13017](https://github.com/JabRef/jabref/issues/13017) +- We fixed an exception on tab dragging. [#12921](https://github.com/JabRef/jabref/issues/12921) - We fixed an issue where the option for which method to use when parsing plaintext citations was unavailable in the 'Create New Entry' tool. [#8808](https://github.com/JabRef/jabref/issues/8808) - We fixed an issue where the "Make/Sync bibliography" button in the OpenOffice/LibreOffice sidebar was not enabled when a jstyle was selected. [#13055](https://github.com/JabRef/jabref/pull/13055) - We fixed an issue where CSL bibliography title properties would be saved even if the "Modify bibliography title" dialog was closed without pressing the "OK" button. [#13074](https://github.com/JabRef/jabref/pull/13074)