Skip to content
Merged
Changes from 3 commits
Commits
Show all changes
28 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
12c37d7
Merge branch 'main' into fix/identifier-type-auto-detect-14660
koppor Dec 30, 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
199 changes: 118 additions & 81 deletions jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,44 +89,73 @@ public class NewEntryView extends BaseDialog<BibEntry> {
private final NewEntryPreferences preferences;
private final LibraryTab libraryTab;
private final DialogService dialogService;
@Inject private StateManager stateManager;
@Inject private TaskExecutor taskExecutor;
@Inject private AiService aiService;
@Inject private FileUpdateMonitor fileUpdateMonitor;
@Inject
private StateManager stateManager;
@Inject
private TaskExecutor taskExecutor;
@Inject
private AiService aiService;
@Inject
private FileUpdateMonitor fileUpdateMonitor;

private final ControlsFxVisualizer visualizer;

@FXML private ButtonType generateButtonType;
@FXML
private ButtonType generateButtonType;
private Button generateButton;

@FXML private TabPane tabs;
@FXML private Tab tabAddEntry;
@FXML private Tab tabLookupIdentifier;
@FXML private Tab tabInterpretCitations;
@FXML private Tab tabSpecifyBibtex;

@FXML private TitledPane entryRecommendedTitle;
@FXML private TilePane entryRecommended;
@FXML private TitledPane entryOtherTitle;
@FXML private TilePane entryOther;
@FXML private TitledPane entryCustomTitle;
@FXML private TilePane entryCustom;
@FXML private TitledPane entryNonStandardTitle;
@FXML private TilePane entryNonStandard;

@FXML private TextField idText;
@FXML private Tooltip idTextTooltip;
@FXML private Hyperlink idJumpLink;
@FXML private RadioButton idLookupGuess;
@FXML private RadioButton idLookupSpecify;
@FXML private ComboBox<IdBasedFetcher> idFetcher;
@FXML private Label idErrorInvalidText;
@FXML private Label idErrorInvalidFetcher;

@FXML private TextArea interpretText;
@FXML private ComboBox<PlainCitationParserChoice> interpretParser;

@FXML private TextArea bibtexText;
@FXML
private TabPane tabs;
@FXML
private Tab tabAddEntry;
@FXML
private Tab tabLookupIdentifier;
@FXML
private Tab tabInterpretCitations;
@FXML
private Tab tabSpecifyBibtex;

@FXML
private TitledPane entryRecommendedTitle;
@FXML
private TilePane entryRecommended;
@FXML
private TitledPane entryOtherTitle;
@FXML
private TilePane entryOther;
@FXML
private TitledPane entryCustomTitle;
@FXML
private TilePane entryCustom;
@FXML
private TitledPane entryNonStandardTitle;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

NO, PLEASE NOT

DO NOT REFORMAT. TELL YOUR AI!!!!!!

@FXML
private TilePane entryNonStandard;

@FXML
private TextField idText;
@FXML
private Tooltip idTextTooltip;
@FXML
private Hyperlink idJumpLink;
@FXML
private RadioButton idLookupGuess;
@FXML
private RadioButton idLookupSpecify;
@FXML
private ComboBox<IdBasedFetcher> idFetcher;
@FXML
private Label idErrorInvalidText;
@FXML
private Label idErrorInvalidFetcher;

@FXML
private TextArea interpretText;
@FXML
private ComboBox<PlainCitationParserChoice> interpretParser;

@FXML
private TextArea bibtexText;

private BibEntry result;

Expand Down Expand Up @@ -320,7 +349,16 @@ private void initializeLookupIdentifier() {
Platform.runLater(() -> idLookupGuess.setSelected(true));
}

idLookupGuess.selectedProperty().addListener((_, _, newValue) -> preferences.setIdLookupGuessing(newValue));
idLookupGuess.selectedProperty().addListener((_, _, newValue) -> {
preferences.setIdLookupGuessing(newValue);
// When switching to auto-detect mode, detect identifier type from current text
if (newValue && idText.getText() != null && !idText.getText().trim().isEmpty()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

null checks are from AI

did you check if this really happens????

You can use LOGGER for that

Optional<Identifier> identifier = Identifier.from(idText.getText().trim());
if (identifier.isPresent() && isValidIdentifier(identifier.get())) {
fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue);
}
}
});

idFetcher.itemsProperty().bind(viewModel.idFetchersProperty());
new ViewModelListCellFactory<IdBasedFetcher>().withText(WebFetcher::getName).install(idFetcher);
Expand All @@ -334,6 +372,17 @@ private void initializeLookupIdentifier() {
idFetcher.setValue(initialFetcher);
idFetcher.setOnAction(_ -> preferences.setLatestIdFetcher(idFetcher.getValue().getName()));

// Auto-detect identifier type when typing in the identifier field
// Only works when "Automatically determine identifier type" is selected
idText.textProperty().addListener((observable, oldValue, newValue) -> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ai, ai, ai

Open in IntelliJ and see that observable is never used.

if (idLookupGuess.isSelected() && newValue != null && !newValue.trim().isEmpty()) {
Optional<Identifier> identifier = Identifier.from(newValue.trim());
if (identifier.isPresent() && isValidIdentifier(identifier.get())) {
fetcherForIdentifier(identifier.get()).ifPresent(idFetcher::setValue);
}
}
});

idJumpLink.visibleProperty().bind(viewModel.duplicateDoiValidatorStatus().validProperty().not());
idErrorInvalidText.visibleProperty().bind(viewModel.idTextValidatorProperty().not());
idErrorInvalidText.managedProperty().bind(viewModel.idTextValidatorProperty().not());
Expand Down Expand Up @@ -535,24 +584,18 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) {
Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title.");
case Book ->
Localization.lang("A single-volume book with one or more authors where the authors share credit for the work as a whole.");
case Booklet ->
Localization.lang("A book-like work without a formal publisher or sponsoring institution.");
case Booklet -> Localization.lang("A book-like work without a formal publisher or sponsoring institution.");
case Collection ->
Localization.lang("A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor.");
case Conference ->
Localization.lang("A legacy alias for \"InProceedings\".");
case InBook ->
Localization.lang("A part of a book which forms a self-contained unit with its own title.");
case Conference -> Localization.lang("A legacy alias for \"InProceedings\".");
case InBook -> Localization.lang("A part of a book which forms a self-contained unit with its own title.");
case InCollection ->
Localization.lang("A contribution to a collection which forms a self-contained unit with a distinct author and title.");
case InProceedings ->
Localization.lang("An article in a conference proceedings.");
case Manual ->
Localization.lang("Technical or other documentation, not necessarily in printed form.");
case InProceedings -> Localization.lang("An article in a conference proceedings.");
case Manual -> Localization.lang("Technical or other documentation, not necessarily in printed form.");
case MastersThesis ->
Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis.");
case Misc ->
Localization.lang("A fallback type for entries which do not fit into any other category.");
case Misc -> Localization.lang("A fallback type for entries which do not fit into any other category.");
case PhdThesis ->
Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis.");
case Proceedings ->
Expand All @@ -565,12 +608,9 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) {
Localization.lang("This type is similar to \"InBook\" but intended for works originally published as a stand-alone book.");
case InReference ->
Localization.lang("An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type.");
case MvBook ->
Localization.lang("A multi-volume \"Book\".");
case MvCollection ->
Localization.lang("A multi-volume \"Collection\".");
case MvProceedings ->
Localization.lang("A multi-volume \"Proceedings\" entry.");
case MvBook -> Localization.lang("A multi-volume \"Book\".");
case MvCollection -> Localization.lang("A multi-volume \"Collection\".");
case MvProceedings -> Localization.lang("A multi-volume \"Proceedings\" entry.");
case MvReference ->
Localization.lang("A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\".");
case Online ->
Expand All @@ -583,18 +623,15 @@ private static String descriptionOfStandardEntryType(StandardEntryType type) {
Localization.lang("An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography.");
case SuppBook ->
Localization.lang("Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only.");
case SuppCollection ->
Localization.lang("Supplemental material in a \"Collection\".");
case SuppCollection -> Localization.lang("Supplemental material in a \"Collection\".");
case SuppPeriodical ->
Localization.lang("Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title.");
case Thesis ->
Localization.lang("A thesis written for an educational institution to satisfy the requirements for a degree.");
case WWW ->
Localization.lang("An alias for \"Online\", provided for jurabib compatibility.");
case WWW -> Localization.lang("An alias for \"Online\", provided for jurabib compatibility.");
case Software ->
Localization.lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\".");
case Dataset ->
Localization.lang("A data set or a similar collection of (mostly) raw data.");
case Dataset -> Localization.lang("A data set or a similar collection of (mostly) raw data.");
};
}

Expand All @@ -611,20 +648,13 @@ private static String descriptionOfNonStandardEntryType(BiblatexNonStandardEntry
Localization.lang("This special entry type is not meant to be used in the bib file like other types. It is provided for third-party packages which merge notes into the bibliography.");
case Commentary ->
Localization.lang("Commentaries which have a status different from regular books, such as legal commentaries.");
case Image ->
Localization.lang("Images, pictures, photographs, and similar media.");
case Jurisdiction ->
Localization.lang("Court decisions, court recordings, and similar things.");
case Legislation ->
Localization.lang("Laws, bills, legislative proposals, and similar things.");
case Legal ->
Localization.lang("Legal documents such as treaties.");
case Letter ->
Localization.lang("Personal correspondence such as letters, emails, memoranda, etc.");
case Movie ->
Localization.lang("Motion pictures.");
case Music ->
Localization.lang("Musical recordings. This is a more specific variant of \"Audio\".");
case Image -> Localization.lang("Images, pictures, photographs, and similar media.");
case Jurisdiction -> Localization.lang("Court decisions, court recordings, and similar things.");
case Legislation -> Localization.lang("Laws, bills, legislative proposals, and similar things.");
case Legal -> Localization.lang("Legal documents such as treaties.");
case Letter -> Localization.lang("Personal correspondence such as letters, emails, memoranda, etc.");
case Movie -> Localization.lang("Motion pictures.");
case Music -> Localization.lang("Musical recordings. This is a more specific variant of \"Audio\".");
case Performance ->
Localization.lang("Musical and theatrical performances as well as other works of the performing arts. This type refers to the event as opposed to a recording, a score, or a printed play.");
case Review ->
Expand Down Expand Up @@ -655,22 +685,29 @@ private static IdBasedFetcher fetcherFromName(String fetcherName, List<IdBasedFe
return PlainCitationParserChoice.RULE_BASED_GENERAL;
}

/**
* Validates an identifier. DOI and ISBN identifiers are validated,
* other identifier types are considered valid by default.
*
* @param id the identifier to validate
* @return true if the identifier is valid, false otherwise
*/
private boolean isValidIdentifier(Identifier id) {
return switch (id) {
case DOI doi -> DOI.isValid(doi.asString());
case ISBN isbn -> isbn.isValid();
default -> true;
};
}

private Optional<Identifier> extractValidIdentifierFromClipboard() {
String clipboardText = ClipBoardManager.getContents().trim();

if (!StringUtil.isBlank(clipboardText) && !clipboardText.contains("\n")) {
Optional<Identifier> identifier = Identifier.from(clipboardText);
if (identifier.isPresent()) {
Identifier id = identifier.get();
boolean isValid = switch (id) {
case DOI doi ->
DOI.isValid(doi.asString());
case ISBN isbn ->
isbn.isValid();
default ->
true;
};
if (isValid) {
if (isValidIdentifier(id)) {
return Optional.of(id);
}
}
Expand Down
Loading