Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
26d8fa8
Fix: Auto-detect identifier type when typing in identifier field
atulrcrosslake Dec 19, 2025
113a0f5
Refactor: Extract identifier validation to avoid code duplication
atulrcrosslake Dec 19, 2025
8a0396f
Format code according to JabRef style guidelines
atulrcrosslake Dec 19, 2025
e275b1c
Revert formatting commit - only format changed code, not entire file
atulrcrosslake Dec 19, 2025
03bef93
Fix: Remove unnecessary null checks and unused parameters
atulrcrosslake Dec 24, 2025
5bda82f
Merge remote-tracking branch 'origin/main' into fix/identifier-type-a…
atulrcrosslake Dec 24, 2025
8081353
Merge branch 'main' into fix/identifier-type-auto-detect-14660
atul-raman Dec 24, 2025
3027145
Fix syntax error in fetcherForIdentifier method
atulrcrosslake Dec 24, 2025
e127b87
Merge branch 'fix/identifier-type-auto-detect-14660' of https://githu…
atulrcrosslake Dec 24, 2025
f5a0d76
Format isValidIdentifier switch statement to match project style
atulrcrosslake Dec 24, 2025
924a990
Merge branch 'main' into fix/identifier-type-auto-detect-14660
atul-raman Dec 24, 2025
959fda9
Fix: Handle URL fragments and query parameters in identifier detection
atulrcrosslake Dec 28, 2025
4e33c9f
Merge branch 'fix/identifier-type-auto-detect-14660' of https://githu…
atulrcrosslake Dec 28, 2025
bfe3e3f
Merge branch 'main' of https://github.com/JabRef/jabref into fix/iden…
atulrcrosslake Dec 28, 2025
f0afe7c
Fix CHANGELOG entry format to match origin/main style
atulrcrosslake Dec 28, 2025
4136d62
Fix CHANGELOG entry placement and format to match Unreleased section …
atulrcrosslake Dec 28, 2025
572536b
Merge branch 'main' into fix/identifier-type-auto-detect-14660
atul-raman Dec 28, 2025
ccae7f0
Merge branch 'main' into fix/identifier-type-auto-detect-14660
atul-raman Dec 28, 2025
262d4f6
Refactor: Remove cleanIdentifierText and extract duplicate logic
atulrcrosslake Dec 29, 2025
cea9cf5
Merge branch 'main' into fix/identifier-type-auto-detect-14660
atul-raman Dec 29, 2025
1e6c125
Refine JavaDoc
koppor Dec 29, 2025
cb8f1e5
Fix DOI validation and removes obsolete code
koppor Dec 29, 2025
bcb68f1
Fix CHANGELOG.md
koppor Dec 29, 2025
42bdbbc
More Optional syntax
koppor Dec 29, 2025
083b82e
WIP
koppor Dec 29, 2025
d4f3c71
Fix: Remove incomplete switch statement causing compilation error
atulrcrosslake Dec 29, 2025
e60a295
Fix: Correct JavaDoc tag from @returns to @return
atulrcrosslake Dec 29, 2025
ee80f22
Revert "Fix: Remove incomplete switch statement causing compilation e…
koppor Dec 29, 2025
709b914
Restore reviewer's TODO comment that was accidentally removed
atulrcrosslake Dec 29, 2025
a898279
Cache searchBasedFetchers
koppor Dec 30, 2025
ad8f175
WIP
koppor Dec 30, 2025
e0b70c9
Merge branch 'fix/identifier-type-auto-detect-14660' of github.com:at…
koppor Dec 30, 2025
12c37d7
Merge branch 'main' into fix/identifier-type-auto-detect-14660
koppor Dec 30, 2025
d90cc41
Merge branch 'fix/identifier-type-auto-detect-14660' into improve-new…
koppor Dec 30, 2025
5bded80
Add simple route
koppor Dec 30, 2025
684fa4c
Wire SsrnFetcher
koppor Dec 30, 2025
07fb2cc
Streamline code in composite id fetcher
koppor Dec 30, 2025
a517de8
Magic merge commit for PR #14662
koppor Dec 30, 2025
d136afc
Merge into improve-newentry-dialog
koppor Dec 30, 2025
dce7c98
Fix typo
koppor Jan 12, 2026
3b3b49e
Merge branch 'main' into improve-newentry-dialog
koppor Jan 12, 2026
9f4dca3
Refine variables
koppor Jan 12, 2026
f52d3f7
Sort alphabetically
koppor Jan 12, 2026
3ba1504
Magic merge commit for PR #14662
koppor Jan 12, 2026
4159b42
Merge into improve-newentry-dialog
koppor Jan 12, 2026
2fcd31a
Fix formatting
koppor Jan 12, 2026
15f33dd
Fix indent
koppor Jan 12, 2026
a61edcb
Fix unused imports
koppor Jan 12, 2026
ab7daec
Refine returned fetchers
koppor Jan 12, 2026
de0852b
Merge branch 'main' into improve-newentry-dialog
Siedlerchr Jan 13, 2026
ea9ee2e
fix faliing fetcher test
Siedlerchr Jan 13, 2026
d40b375
Merge branch 'main' into improve-newentry-dialog
koppor Jan 16, 2026
d87bd03
Merge branch 'main' into improve-newentry-dialog
koppor Jan 18, 2026
d45145a
Fix composite fetcher
koppor Jan 18, 2026
67b64a7
Fix checkstyle location
koppor Jan 18, 2026
74cde84
Add static
koppor Jan 18, 2026
8d99816
Use .toList()
koppor Jan 18, 2026
be4ebff
More alphabet...
koppor Jan 18, 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
8 changes: 4 additions & 4 deletions .idea/checkstyle-idea.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We fixed the Quality > Automatically set file links button. Now if a file is moved, the button can relink the moved file to the broken linked file. [#9798](https://github.com/JabRef/jabref/issues/9798)
- We fixed an issue where JabRef would not start on Linux ARM due to missing binaries for postgres-embedded [#14783](https://github.com/JabRef/jabref/issues/14783)
- We fixed an issue where JaRef would not correctly remember the opened side panels in the preferences [#14818](https://github.com/JabRef/jabref/issues/14818)
- Updates of the pre-selected fetchers are now followed at the Web fetchers. [#14768](https://github.com/JabRef/jabref/pull/14768)

### Removed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.jabref.gui.mergeentries;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -42,10 +41,17 @@
*/
public class FetchAndMergeEntry {

// All identifiers listed here should also appear at {@link org.jabref.logic.importer.CompositeIdFetcher#performSearchById}
public static List<Field> SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN);
// All identifiers listed here should also appear at {@link org.jabref.logic.importer.WebFetchers#getIdBasedFetcherFoIdentifier}
public static List<Field> SUPPORTED_FIELDS =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be done with instanceof ssrnFetcher?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

Example field definition:

ISBN("isbn", FieldProperty.VERBATIM),

Alternatively, I could add a FieldProperty, but that feels unclean.

List.of(
StandardField.EPRINT, // arXiv
StandardField.DOI,
StandardField.ISBN,
StandardField.ISSN
);

private static final Logger LOGGER = LoggerFactory.getLogger(FetchAndMergeEntry.class);

private final DialogService dialogService;
private final UndoManager undoManager;
private final StateManager stateManager;
Expand Down
95 changes: 42 additions & 53 deletions jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@
import org.jabref.gui.util.ViewModelListCellFactory;
import org.jabref.logic.ai.AiService;
import org.jabref.logic.importer.IdBasedFetcher;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.WebFetcher;
import org.jabref.logic.importer.fetcher.ArXivFetcher;
import org.jabref.logic.importer.WebFetchers;
import org.jabref.logic.importer.fetcher.DoiFetcher;
import org.jabref.logic.importer.fetcher.RfcFetcher;
import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher;
import org.jabref.logic.importer.plaincitation.PlainCitationParserChoice;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.TaskExecutor;
Expand All @@ -53,12 +52,7 @@
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryType;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.identifier.ArXivIdentifier;
import org.jabref.model.entry.identifier.DOI;
import org.jabref.model.entry.identifier.ISBN;
import org.jabref.model.entry.identifier.Identifier;
import org.jabref.model.entry.identifier.RFC;
import org.jabref.model.entry.identifier.SSRN;
import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions;
import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions;
import org.jabref.model.entry.types.BiblatexNonStandardEntryType;
Expand Down Expand Up @@ -87,8 +81,11 @@ public class NewEntryView extends BaseDialog<BibEntry> {
private final NewEntryDialogTab initialApproach;
private NewEntryDialogTab currentApproach;

private final GuiPreferences guiPreferences;
private final NewEntryPreferences preferences;
private final GuiPreferences preferences;

private final ImportFormatPreferences importFormatPreferences;
private final NewEntryPreferences newEntryPreferences;

private final LibraryTab libraryTab;
private final DialogService dialogService;
@Inject private StateManager stateManager;
Expand Down Expand Up @@ -136,8 +133,13 @@ public NewEntryView(NewEntryDialogTab initialApproach, GuiPreferences preference
this.initialApproach = initialApproach;
this.currentApproach = initialApproach;

this.guiPreferences = preferences;
this.preferences = preferences.getNewEntryPreferences();
// This is required for new NewEntryViewModel(preferences, ...
this.preferences = preferences;
Copy link
Member

@calixtus calixtus Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like two levels of prefs are mixed up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but no time to fix now. --> follow-up


// Required by this class
this.importFormatPreferences = preferences.getImportFormatPreferences();
this.newEntryPreferences = preferences.getNewEntryPreferences();

this.libraryTab = libraryTab;
this.dialogService = dialogService;

Expand Down Expand Up @@ -177,10 +179,10 @@ private void finalizeTabs() {
} else if (clipboardText.split(LINE_BREAK)[0].matches(BIBTEX_REGEX)) {
approach = NewEntryDialogTab.SPECIFY_BIBTEX;
} else {
approach = preferences.getLatestApproach();
approach = newEntryPreferences.getLatestApproach();
}
} else {
approach = preferences.getLatestApproach();
approach = newEntryPreferences.getLatestApproach();
}
}

Expand Down Expand Up @@ -211,7 +213,7 @@ private void finalizeTabs() {

@FXML
public void initialize() {
viewModel = new NewEntryViewModel(guiPreferences, libraryTab, dialogService, stateManager, (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor);
viewModel = new NewEntryViewModel(preferences, libraryTab, dialogService, stateManager, (UiTaskExecutor) taskExecutor, aiService, fileUpdateMonitor);

visualizer.setDecoration(new IconValidationDecorator());

Expand All @@ -231,15 +233,15 @@ public void initialize() {

private void initializeAddEntry() {
entryRecommendedTitle.managedProperty().bind(entryRecommendedTitle.visibleProperty());
entryRecommendedTitle.expandedProperty().bindBidirectional(preferences.typesRecommendedExpandedProperty());
entryRecommendedTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesRecommendedExpandedProperty());
entryRecommended.managedProperty().bind(entryRecommended.visibleProperty());

entryOtherTitle.managedProperty().bind(entryOtherTitle.visibleProperty());
entryOtherTitle.expandedProperty().bindBidirectional(preferences.typesOtherExpandedProperty());
entryOtherTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesOtherExpandedProperty());
entryOther.managedProperty().bind(entryOther.visibleProperty());

entryCustomTitle.managedProperty().bind(entryCustomTitle.visibleProperty());
entryCustomTitle.expandedProperty().bindBidirectional(preferences.typesCustomExpandedProperty());
entryCustomTitle.expandedProperty().bindBidirectional(newEntryPreferences.typesCustomExpandedProperty());
entryCustom.managedProperty().bind(entryCustom.visibleProperty());

entryNonStandardTitle.managedProperty().bind(entryNonStandardTitle.visibleProperty());
Expand Down Expand Up @@ -291,7 +293,7 @@ private void initializeLookupIdentifier() {
idLookupGuess.setToggleGroup(toggleGroup);
idLookupSpecify.setToggleGroup(toggleGroup);

if (preferences.getIdLookupGuessing()) {
if (newEntryPreferences.getIdLookupGuessing()) {
idLookupGuess.selectedProperty().set(true);
} else {
idLookupSpecify.selectedProperty().set(true);
Expand All @@ -313,13 +315,14 @@ private void initializeLookupIdentifier() {
Platform.runLater(() -> {
// [impl->req~newentry.clipboard.autofocus~1]
idLookupSpecify.setSelected(true);
fetcherForIdentifier(identifier).ifPresent(idFetcher::setValue);
WebFetchers.getIdBasedFetcherForIdentifier(identifier, importFormatPreferences)
.ifPresent(idFetcher::setValue);
});
},
() -> Platform.runLater(() -> idLookupGuess.setSelected(true)));

idLookupGuess.selectedProperty().addListener((_, _, newValue) -> {
preferences.setIdLookupGuessing(newValue);
newEntryPreferences.setIdLookupGuessing(newValue);
// When switching to auto-detect mode, detect identifier type from current text
if (newValue) {
updateFetcherFromIdentifierText(idText.getText());
Expand All @@ -330,13 +333,12 @@ private void initializeLookupIdentifier() {
new ViewModelListCellFactory<IdBasedFetcher>().withText(WebFetcher::getName).install(idFetcher);
idFetcher.disableProperty().bind(idLookupSpecify.selectedProperty().not());
idFetcher.valueProperty().bindBidirectional(viewModel.idFetcherProperty());
IdBasedFetcher initialFetcher = fetcherFromName(preferences.getLatestIdFetcher(), idFetcher.getItems());
IdBasedFetcher initialFetcher = fetcherFromName(newEntryPreferences.getLatestIdFetcher());
if (initialFetcher == null) {
final IdBasedFetcher defaultFetcher = new DoiFetcher(guiPreferences.getImportFormatPreferences());
initialFetcher = fetcherFromName(defaultFetcher.getName(), idFetcher.getItems());
initialFetcher = fetcherFromName(DoiFetcher.NAME);
}
idFetcher.setValue(initialFetcher);
idFetcher.setOnAction(_ -> preferences.setLatestIdFetcher(idFetcher.getValue().getName()));
idFetcher.setOnAction(_ -> newEntryPreferences.setLatestIdFetcher(idFetcher.getValue().getName()));

// Auto-detect identifier type when typing in the identifier field
// Only works when "Automatically determine identifier type" is selected
Expand All @@ -354,7 +356,7 @@ private void initializeLookupIdentifier() {
idJumpLink.setOnAction(_ -> libraryTab.showAndEdit(viewModel.getDuplicateEntry()));

TextInputControl textInput = idText;
EditorValidator validator = new EditorValidator(this.guiPreferences);
EditorValidator validator = new EditorValidator(this.preferences);
validator.configureValidation(viewModel.duplicateDoiValidatorStatus(), textInput);
}

Expand All @@ -369,13 +371,12 @@ private void initializeInterpretCitations() {
interpretParser.itemsProperty().bind(viewModel.interpretParsersProperty());
new ViewModelListCellFactory<PlainCitationParserChoice>().withText(PlainCitationParserChoice::getLocalizedName).install(interpretParser);
interpretParser.valueProperty().bindBidirectional(viewModel.interpretParserProperty());
PlainCitationParserChoice initialParser = parserFromName(preferences.getLatestInterpretParser(), interpretParser.getItems());
PlainCitationParserChoice initialParser = parserFromName(newEntryPreferences.getLatestInterpretParser(), interpretParser.getItems());
if (initialParser == null) {
final PlainCitationParserChoice defaultParser = PlainCitationParserChoice.RULE_BASED_GENERAL;
initialParser = parserFromName(defaultParser.getLocalizedName(), interpretParser.getItems());
initialParser = parserFromName(PlainCitationParserChoice.RULE_BASED_GENERAL.getLocalizedName(), interpretParser.getItems());
}
interpretParser.setValue(initialParser);
interpretParser.setOnAction(_ -> preferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName()));
interpretParser.setOnAction(_ -> newEntryPreferences.setLatestInterpretParser(interpretParser.getValue().getLocalizedName()));
}

private void initializeSpecifyBibTeX() {
Expand All @@ -396,7 +397,7 @@ private void switchAddEntry() {
}

currentApproach = NewEntryDialogTab.CHOOSE_ENTRY_TYPE;
preferences.setLatestApproach(NewEntryDialogTab.CHOOSE_ENTRY_TYPE);
newEntryPreferences.setLatestApproach(NewEntryDialogTab.CHOOSE_ENTRY_TYPE);

if (generateButton != null) {
generateButton.disableProperty().unbind();
Expand All @@ -412,7 +413,7 @@ private void switchLookupIdentifier() {
}

currentApproach = NewEntryDialogTab.ENTER_IDENTIFIER;
preferences.setLatestApproach(NewEntryDialogTab.ENTER_IDENTIFIER);
newEntryPreferences.setLatestApproach(NewEntryDialogTab.ENTER_IDENTIFIER);

if (idText != null) {
Platform.runLater(() -> idText.requestFocus());
Expand All @@ -431,7 +432,7 @@ private void switchInterpretCitations() {
}

currentApproach = NewEntryDialogTab.INTERPRET_CITATIONS;
preferences.setLatestApproach(NewEntryDialogTab.INTERPRET_CITATIONS);
newEntryPreferences.setLatestApproach(NewEntryDialogTab.INTERPRET_CITATIONS);

if (interpretText != null) {
Platform.runLater(() -> interpretText.requestFocus());
Expand All @@ -450,7 +451,7 @@ private void switchSpecifyBibtex() {
}

currentApproach = NewEntryDialogTab.SPECIFY_BIBTEX;
preferences.setLatestApproach(NewEntryDialogTab.SPECIFY_BIBTEX);
newEntryPreferences.setLatestApproach(NewEntryDialogTab.SPECIFY_BIBTEX);

if (bibtexText != null) {
Platform.runLater(() -> bibtexText.requestFocus());
Expand All @@ -463,7 +464,7 @@ private void switchSpecifyBibtex() {
}

private void onEntryTypeSelected(EntryType type) {
preferences.setLatestImmediateType(type);
newEntryPreferences.setLatestImmediateType(type);
result = new BibEntry(type);
this.close();
}
Expand Down Expand Up @@ -538,7 +539,7 @@ private static String descriptionOfEntryType(EntryType type) {
return null;
}

private static String descriptionOfStandardEntryType(StandardEntryType type) {
private static @NonNull String descriptionOfStandardEntryType(StandardEntryType type) {
// These descriptions are taken from subsection 2.1 of the biblatex package documentation.
// Biblatex is a superset of bibtex, with more elaborate descriptions, so its documentation is preferred.
// See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf].
Expand Down Expand Up @@ -610,7 +611,7 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) {
};
}

private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntryType type) {
private static @NonNull String descriptionOfNonStandardEntryType(BiblatexNonStandardEntryType type) {
// These descriptions are taken from subsection 2.1.3 of the biblatex package documentation.
// Non-standard Types (BibLaTeX only) - these use the @misc driver in standard bibliography styles.
// See [https://mirrors.ibiblio.org/pub/mirrors/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf].
Expand Down Expand Up @@ -648,8 +649,8 @@ private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntry
};
}

private static IdBasedFetcher fetcherFromName(String fetcherName, List<IdBasedFetcher> fetchers) {
for (IdBasedFetcher fetcher : fetchers) {
private @Nullable IdBasedFetcher fetcherFromName(String fetcherName) {
for (IdBasedFetcher fetcher : idFetcher.getItems()) {
if (fetcher.getName().equals(fetcherName)) {
return fetcher;
}
Expand All @@ -675,7 +676,8 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List<IdBasedFe
*/
private void updateFetcherFromIdentifierText(@Nullable String text) {
Identifier.from(text)
.flatMap(identifier -> fetcherForIdentifier(identifier))
.flatMap(identifier -> WebFetchers.getIdBasedFetcherForIdentifier(identifier, importFormatPreferences))
.map(fetcher -> fetcherFromName(fetcher.getName()))
.ifPresent(idFetcher::setValue);
}

Expand All @@ -686,17 +688,4 @@ private Optional<Identifier> extractIdentifierFromClipboard() {
}
return Optional.empty();
}

private Optional<IdBasedFetcher> fetcherForIdentifier(Identifier id) {
for (IdBasedFetcher fetcher : idFetcher.getItems()) {
if ((id instanceof DOI && fetcher instanceof DoiFetcher) ||
(id instanceof ISBN && fetcher instanceof IsbnFetcher) ||
(id instanceof ArXivIdentifier && fetcher instanceof ArXivFetcher) ||
(id instanceof RFC && fetcher instanceof RfcFetcher) ||
(id instanceof SSRN && fetcher instanceof DoiFetcher)) {
return Optional.of(fetcher);
}
}
return Optional.empty();
}
}
Loading
Loading