Skip to content

Commit 874d18f

Browse files
pluto-hankoppor
andauthored
Extract references from related work section (Part 1) (#15316)
* Extract text about papers from "related work" sections * Update comments Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * small refactor * small refactor of comments * Support more citation format * Modify logic for UI * Add UI * make `RelatedWorkAction` inline * add empty check before getting optional value * add javadoc and example to RelatedWorkSnippet.java * Update jabgui/src/main/java/org/jabref/gui/relatedwork/RelatedWorkResultDialogView.java Co-authored-by: Oliver Kopp <kopp.dev@gmail.com> * Fix comment style * Add backticks around `comment-{username}`` * Add @NullMarked * Change parameter order * Change variable order * remove copyOf * remove copyOf * remove `skipped` * change comment style * add comment * Add backticks around `comment-{username}` * Add more explanation in the comment * Add "undefined" to `getCitationKey` * get owner from preference * Add action helper to check if PDF file is present * Hihglight non-editabel fileds differently * Update CHANGELOG.md * Fix failed test * Focus related work text field, auto insert clipboard content * Use sealed interface instead of enum * Fix failed tests * Add logger * Trigger CI * Replace all page separators to "-" * Split RelatedWorkService to matcher and inserter * Replace "[^a-z0-9]" as a constant NON_ALPHANUMERIC * use switch to replace ifelse * use mock to create filepreference * Normalize linebreak * Add requirement * Add requirement * Fix md format fail --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>
1 parent 4f51dcb commit 874d18f

28 files changed

+1399
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
1111

1212
### Added
1313

14+
- We added a related work text extractor, which finds and inserts the related work text into bib entries from references in the texts. [#9840](https://github.com/JabRef/jabref/issues/9840)
1415
- We added a hover button on group rows to quickly add a new group or subgroup. [#12289](https://github.com/JabRef/jabref/issues/12289)
1516
- We added a shorthand for protecting terms in the fields: user can now select a text and type a opening curling brace to quickly wrap the selection in braces. [#15442](https://github.com/JabRef/jabref/pull/15442)
1617
- We added fallback search for `[DATE]` patterns in the file finder, so that if an exact date match is not found, progressively less specific dates (year-month, then year) are tried. [#8152](https://github.com/JabRef/jabref/issues/8152)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
parent: Requirements
3+
---
4+
# Dialog with text input
5+
6+
## Automatic clipboard content detection and focus for text input
7+
`req~textinput.clipboard.autofocus~1`
8+
9+
When a dialog with text input is opened:
10+
11+
- Text field should be focused.
12+
- If the clipboard contains contents, it should be in the field and be marked.
13+
14+
Needs: impl
15+
16+
<!-- markdownlint-disable-file MD022 -->

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,28 @@ public static BooleanExpression isFilePresentForSelectedEntry(StateManager state
111111
return BooleanExpression.booleanExpression(fileIsPresent);
112112
}
113113

114+
public static BooleanExpression isPdfFilePresentForSelectedEntry(StateManager stateManager, CliPreferences preferences) {
115+
ObservableList<BibEntry> selectedEntries = stateManager.getSelectedEntries();
116+
Binding<Boolean> pdfFileIsPresent = EasyBind.valueAt(selectedEntries, 0).mapOpt(entry -> {
117+
if (stateManager.getActiveDatabase().isEmpty()) {
118+
return false;
119+
}
120+
121+
return entry.getFiles().stream()
122+
.filter(linkedFile -> linkedFile.getFileName()
123+
.map(Path::of)
124+
.map(FileUtil::isPDFFile)
125+
.orElse(false))
126+
.anyMatch(linkedFile -> FileUtil.find(
127+
stateManager.getActiveDatabase().get(),
128+
linkedFile.getLink(),
129+
preferences.getFilePreferences())
130+
.isPresent());
131+
}).orElseOpt(false);
132+
133+
return BooleanExpression.booleanExpression(pdfFileIsPresent);
134+
}
135+
114136
/// Check if at least one of the selected entries has linked files
115137
/// <br>
116138
/// Used in {@link org.jabref.gui.maintable.OpenSelectedEntriesFilesAction} when multiple entries selected

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public enum StandardActions implements Action {
4343
OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
4444
EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), IconTheme.JabRefIcons.FILE_STAR),
4545
EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR),
46+
EXTRACT_RELATED_WORK_COMMENTS(Localization.lang("Extract related work comments"), IconTheme.JabRefIcons.COMMENT),
4647
OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI),
4748
SEARCH(Localization.lang("Search...")),
4849
SEARCH_GOOGLE_SCHOLAR(Localization.lang("Search Google Scholar")),

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import org.jabref.gui.preview.PreviewControls;
6868
import org.jabref.gui.pseudonymize.PseudonymizeAction;
6969
import org.jabref.gui.push.GuiPushToApplicationCommand;
70+
import org.jabref.gui.relatedwork.RelatedWorkAction;
7071
import org.jabref.gui.search.RebuildFulltextSearchIndexAction;
7172
import org.jabref.gui.shared.ConnectToSharedDatabaseCommand;
7273
import org.jabref.gui.shared.PullChangesFromSharedAction;
@@ -326,6 +327,7 @@ private void createMenu() {
326327
factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(frame, stateManager, dialogService)),
327328
factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_ONLINE, new NewLibraryFromPdfActionOnline(frame, stateManager, dialogService, preferences, taskExecutor)),
328329
factory.createMenuItem(StandardActions.NEW_LIBRARY_FROM_PDF_OFFLINE, new NewLibraryFromPdfActionOffline(frame, stateManager, dialogService, preferences, taskExecutor)),
330+
factory.createMenuItem(StandardActions.EXTRACT_RELATED_WORK_COMMENTS, new RelatedWorkAction(dialogService, stateManager, preferences)),
329331
factory.createMenuItem(StandardActions.PSEUDONYMIZE_LIBRARY, new PseudonymizeAction(stateManager, dialogService, preferences)),
330332

331333
new SeparatorMenuItem(),

jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.jabref.gui.preferences.GuiPreferences;
3131
import org.jabref.gui.preview.CopyCitationAction;
3232
import org.jabref.gui.preview.PreviewPreferences;
33+
import org.jabref.gui.relatedwork.RelatedWorkAction;
3334
import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory;
3435
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
3536
import org.jabref.logic.importer.WebFetchers;
@@ -94,6 +95,7 @@ public static ContextMenu create(LibraryTab libraryTab,
9495
factory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, new OpenSelectedEntriesFilesAction(dialogService, stateManager, preferences, taskExecutor)),
9596
extractFileReferencesOnline,
9697
extractFileReferencesOffline,
98+
factory.createMenuItem(StandardActions.EXTRACT_RELATED_WORK_COMMENTS, new RelatedWorkAction(dialogService, stateManager, preferences)),
9799

98100
factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferences)),
99101

jabgui/src/main/java/org/jabref/gui/newentry/NewEntryView.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ private void initializeLookupIdentifier() {
370370
}
371371

372372
private void initializeInterpretCitations() {
373+
// [impl->req~textinput.clipboard.autofocus~1]
373374
interpretText.textProperty().bindBidirectional(viewModel.interpretTextProperty());
374375
final String clipboardText = ClipBoardManager.getContents().trim();
375376
if (!StringUtil.isBlank(clipboardText)) {
@@ -443,6 +444,7 @@ private void switchInterpretCitations() {
443444
currentApproach = NewEntryDialogTab.INTERPRET_CITATIONS;
444445
newEntryPreferences.setLatestApproach(NewEntryDialogTab.INTERPRET_CITATIONS);
445446

447+
// [impl->req~textinput.clipboard.autofocus~1]
446448
if (interpretText != null) {
447449
Platform.runLater(() -> interpretText.requestFocus());
448450
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.jabref.gui.relatedwork;
2+
3+
import java.nio.file.Path;
4+
import java.util.Optional;
5+
6+
import org.jabref.gui.DialogService;
7+
import org.jabref.gui.StateManager;
8+
import org.jabref.gui.actions.ActionHelper;
9+
import org.jabref.gui.actions.SimpleCommand;
10+
import org.jabref.logic.l10n.Localization;
11+
import org.jabref.logic.preferences.CliPreferences;
12+
import org.jabref.logic.util.io.FileUtil;
13+
import org.jabref.model.database.BibDatabaseContext;
14+
import org.jabref.model.entry.BibEntry;
15+
import org.jabref.model.entry.LinkedFile;
16+
17+
public class RelatedWorkAction extends SimpleCommand {
18+
19+
private final DialogService dialogService;
20+
private final StateManager stateManager;
21+
22+
public RelatedWorkAction(DialogService dialogService, StateManager stateManager, CliPreferences preferences) {
23+
this.dialogService = dialogService;
24+
this.stateManager = stateManager;
25+
executable.bind(ActionHelper.needsDatabase(stateManager)
26+
.and(ActionHelper.needsEntriesSelected(1, stateManager))
27+
.and(ActionHelper.isPdfFilePresentForSelectedEntry(stateManager, preferences)));
28+
}
29+
30+
@Override
31+
public void execute() {
32+
BibDatabaseContext databaseContext = stateManager.getActiveDatabase().orElseThrow();
33+
BibEntry sourceEntry = stateManager.getSelectedEntries().getFirst();
34+
Optional<LinkedFile> linkedPDFFile = sourceEntry.getFiles().stream()
35+
.filter(this::isPDFLinkedFile)
36+
.findFirst();
37+
38+
if (linkedPDFFile.isEmpty()) {
39+
dialogService.showWarningDialogAndWait(
40+
Localization.lang("No PDF files available"),
41+
Localization.lang("Please attach PDF files")
42+
);
43+
return;
44+
}
45+
46+
Optional<String> citationKey = sourceEntry.getCitationKey();
47+
if (citationKey.isEmpty()) {
48+
dialogService.showWarningDialogAndWait(
49+
Localization.lang("Insert related work text"),
50+
Localization.lang("Selected entry does not have an associated citation key.")
51+
);
52+
return;
53+
}
54+
55+
dialogService.showCustomDialogAndWait(new RelatedWorkDialogView(
56+
databaseContext,
57+
sourceEntry,
58+
linkedPDFFile.get(),
59+
citationKey.get()
60+
));
61+
}
62+
63+
private boolean isPDFLinkedFile(LinkedFile linkedFile) {
64+
return linkedFile.getFileName()
65+
.map(Path::of)
66+
.map(FileUtil::isPDFFile)
67+
.orElse(false);
68+
}
69+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.jabref.gui.relatedwork;
2+
3+
import java.util.List;
4+
5+
import javafx.application.Platform;
6+
import javafx.fxml.FXML;
7+
import javafx.scene.control.Button;
8+
import javafx.scene.control.ButtonType;
9+
import javafx.scene.control.TextArea;
10+
import javafx.scene.control.TextField;
11+
12+
import org.jabref.gui.DialogService;
13+
import org.jabref.gui.clipboard.ClipBoardManager;
14+
import org.jabref.gui.util.BaseDialog;
15+
import org.jabref.gui.util.ControlHelper;
16+
import org.jabref.logic.l10n.Localization;
17+
import org.jabref.logic.preferences.CliPreferences;
18+
import org.jabref.logic.relatedwork.RelatedWorkMatchResult;
19+
import org.jabref.logic.util.strings.StringUtil;
20+
import org.jabref.model.database.BibDatabaseContext;
21+
import org.jabref.model.entry.BibEntry;
22+
import org.jabref.model.entry.BibEntryTypesManager;
23+
import org.jabref.model.entry.LinkedFile;
24+
25+
import com.airhacks.afterburner.views.ViewLoader;
26+
import jakarta.inject.Inject;
27+
28+
public class RelatedWorkDialogView extends BaseDialog<Void> {
29+
30+
private final BibDatabaseContext databaseContext;
31+
private final BibEntry sourceEntry;
32+
private final LinkedFile linkedFile;
33+
private final String sourceEntryCitationKey;
34+
35+
@FXML private TextField entryField;
36+
@FXML private TextField linkedFileField;
37+
@FXML private TextField userNameField;
38+
@FXML private TextArea relatedWorkTextArea;
39+
@FXML private ButtonType parseButtonType;
40+
41+
@Inject private DialogService dialogService;
42+
@Inject private CliPreferences preferences;
43+
@Inject private BibEntryTypesManager entryTypesManager;
44+
45+
private RelatedWorkDialogViewModel viewModel;
46+
47+
public RelatedWorkDialogView(BibDatabaseContext databaseContext,
48+
BibEntry sourceEntry,
49+
LinkedFile linkedPdfFile,
50+
String sourceEntryCitationKey) {
51+
this.databaseContext = databaseContext;
52+
this.sourceEntry = sourceEntry;
53+
this.linkedFile = linkedPdfFile;
54+
this.sourceEntryCitationKey = sourceEntryCitationKey;
55+
56+
setTitle(Localization.lang("Insert related work text"));
57+
58+
ViewLoader.view(this).load().setAsDialogPane(this);
59+
60+
ControlHelper.setAction(parseButtonType, getDialogPane(), event -> viewModel.matchRelatedWork().ifPresent(this::openResultDialog));
61+
62+
Button parseButton = (Button) getDialogPane().lookupButton(parseButtonType);
63+
parseButton.disableProperty().bind(viewModel.parseDisabledProperty());
64+
}
65+
66+
@FXML
67+
private void initialize() {
68+
this.viewModel = new RelatedWorkDialogViewModel(
69+
databaseContext,
70+
sourceEntry,
71+
linkedFile,
72+
sourceEntryCitationKey,
73+
dialogService,
74+
preferences,
75+
entryTypesManager
76+
);
77+
78+
this.entryField.textProperty().bind(viewModel.sourceEntryCitationKeyProperty());
79+
this.linkedFileField.textProperty().bind(viewModel.linkedPDFFileProperty());
80+
this.userNameField.textProperty().bind(viewModel.userNameProperty());
81+
this.relatedWorkTextArea.textProperty().bindBidirectional(viewModel.relatedWorkTextProperty());
82+
83+
// [impl->req~textinput.clipboard.autofocus~1]
84+
String clipboardText = ClipBoardManager.getContents().trim();
85+
if (!StringUtil.isBlank(clipboardText)) {
86+
relatedWorkTextArea.setText(clipboardText);
87+
relatedWorkTextArea.selectAll();
88+
}
89+
90+
Platform.runLater(relatedWorkTextArea::requestFocus);
91+
}
92+
93+
private void openResultDialog(List<RelatedWorkMatchResult> matchedResults) {
94+
dialogService.showCustomDialogAndWait(new RelatedWorkResultDialogView(sourceEntry, matchedResults, viewModel.userNameProperty().get()));
95+
}
96+
}

0 commit comments

Comments
 (0)