Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- 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)
- We added a hover button on group rows to quickly add a new group or subgroup. [#12289](https://github.com/JabRef/jabref/issues/12289)
- 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)
- 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)
- We added support for downloading full-text PDFs from Wiley journals via the Wiley TDM API. [#13404](https://github.com/JabRef/jabref/issues/13404)
Expand All @@ -26,6 +24,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- 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.
- We added a quick setting toggle to enable cover images download. [#15322](https://github.com/JabRef/jabref/pull/15322)
- 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)
- We added SearchRxiv integration to the SLR feature. [#12618](https://github.com/JabRef/jabref/issues/12618)
- Added context menu entry "Sort tabs alphabetically" to the library tabs. [#15425](https://github.com/JabRef/jabref/pull/15425)
- 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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,21 @@

import com.airhacks.afterburner.views.ViewLoader;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/// This class controls the user interface of the study definition management dialog. The UI elements and their layout
/// are defined in the FXML file.
public class ManageStudyDefinitionView extends BaseDialog<SlrStudyAndDirectory> {
private static final Logger LOGGER = LoggerFactory.getLogger(ManageStudyDefinitionView.class);

@FXML private TextField studyTitle;
@FXML private TextField addAuthor;
@FXML private TextField addResearchQuestion;
@FXML private TextField addQuery;
@FXML private TextField studyDirectory;
@FXML private Button selectStudyDirectory;
@FXML private Button shareOnSearchRxivButton;

@FXML private ButtonType saveSurveyButtonType;
@FXML private Label helpIcon;
Expand Down Expand Up @@ -177,6 +182,14 @@ private void initialize() {
initQueriesTab();
initCatalogsTab();
initValidationBindings();

shareOnSearchRxivButton.disableProperty().bind(
Bindings.or(
Bindings.isEmpty(viewModel.getQueries()),
Bindings.createBooleanBinding(
() -> viewModel.getCatalogs().stream().noneMatch(StudyCatalogItem::isEnabled),
viewModel.getCatalogs())
));
Comment thread
InAnYan marked this conversation as resolved.
}

private void updateDirectoryWarning(Path directory) {
Expand Down Expand Up @@ -295,6 +308,13 @@ private void setupCommonPropertiesForTables(Node addControl,
actionColumn.setResizable(false);
}

@FXML
private void shareOnSearchRxiv() {
viewModel.shareOnSearchRxiv(
pathToStudyDataDirectory,
preferences.getExternalApplicationsPreferences());
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.

The view model should get and store external applications preferences, so the method shareOnSearchRxiv should accept only one argument

}

private void setupCellFactories(TableColumn<String, String> contentColumn,
TableColumn<String, String> actionColumn,
Consumer<String> removeAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@

import org.jabref.gui.DialogService;
import org.jabref.gui.WorkspacePreferences;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.frame.ExternalApplicationsPreferences;
import org.jabref.gui.util.DirectoryDialogConfiguration;
import org.jabref.logic.crawler.StudyRepository;
import org.jabref.logic.crawler.StudyYamlParser;
import org.jabref.logic.exporter.SearchRxivExporter;
import org.jabref.logic.git.GitHandler;
import org.jabref.logic.git.preferences.GitPreferences;
import org.jabref.logic.importer.ImportFormatPreferences;
Expand Down Expand Up @@ -55,8 +59,10 @@ public class ManageStudyDefinitionViewModel {
private final ObservableList<String> authors = FXCollections.observableArrayList();
private final ObservableList<String> researchQuestions = FXCollections.observableArrayList();
private final ObservableList<String> queries = FXCollections.observableArrayList();
private final ObservableList<StudyCatalogItem> databases = FXCollections.observableArrayList();

// Observe changes to each item's enabledProperty so bindings re-evaluate when catalogs are toggled
private final ObservableList<StudyCatalogItem> databases = FXCollections.observableArrayList(
Comment thread
InAnYan marked this conversation as resolved.
item -> new javafx.beans.Observable[] {item.enabledProperty()}
);
// Hold the complement of databases for the selector
private final SimpleStringProperty directory = new SimpleStringProperty();

Expand Down Expand Up @@ -218,12 +224,7 @@ public void addQuery(String query) {
}

public SlrStudyAndDirectory saveStudy() {
Study study = new Study(
authors,
title.getValueSafe(),
researchQuestions,
queries.stream().map(StudyQuery::new).collect(Collectors.toList()),
databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).filter(StudyDatabase::isEnabled).collect(Collectors.toList()));
Study study = buildStudy();
Path studyDirectory;
final String studyDirectoryAsString = directory.getValueSafe();
try {
Expand Down Expand Up @@ -257,6 +258,19 @@ public SlrStudyAndDirectory saveStudy() {
return new SlrStudyAndDirectory(study, studyDirectory);
}

/// Builds a {@link Study} from the current UI state without persisting it.
public Study buildStudy() {
return new Study(
authors,
title.getValueSafe(),
researchQuestions,
queries.stream().map(StudyQuery::new).collect(Collectors.toList()),
databases.stream()
.map(item -> new StudyDatabase(item.getName(), item.isEnabled()))
.filter(StudyDatabase::isEnabled)
.collect(Collectors.toList()));
}

public Property<String> titleProperty() {
return title;
}
Expand Down Expand Up @@ -316,4 +330,30 @@ public StringProperty queriesValidationMessageProperty() {
public StringProperty catalogsValidationMessageProperty() {
return catalogsValidationMessage;
}

public void shareOnSearchRxiv(Path initialDirectory, ExternalApplicationsPreferences externalPreferences) {
Study study = buildStudy();
if (study.getDatabases().isEmpty()) {
dialogService.notify(Localization.lang("Please select at least one catalog."));
return;
}

DirectoryDialogConfiguration config = new DirectoryDialogConfiguration.Builder()
.withInitialDirectory(initialDirectory)
.build();

dialogService.showDirectorySelectionDialog(config).ifPresent(exportDirectory -> {
try {
new SearchRxivExporter().export(study, exportDirectory);
NativeDesktop.openBrowserShowPopup(
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.

I'm still not sure if we want to open a website after exporting the search queries to JSON. @koppor @calixtus @subhramit what do you think?

Copy link
Copy Markdown
Member

@calixtus calixtus Apr 12, 2026

Choose a reason for hiding this comment

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

I think this is partially known behaviour, if I export something, I open the browser to show the result of the export. If it's just Json exportet, I wouldn't show the result, its just technical.

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.

I couldn’t really find the button “use json search” on the website, so I think it’s better not to show

SearchRxivExporter.SEARCHRXIV_URL,
dialogService,
externalPreferences);
dialogService.notify(Localization.lang("Exported search queries for SearchRxiv."));
} catch (IOException e) {
LOGGER.error("Could not export search queries for SearchRxiv", e);
dialogService.notify(Localization.lang("Could not export search queries."));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,40 @@
</graphic>
</Button>
</HBox>
<Label fx:id="directoryWarning" text="%Warning: The selected directory is not empty." visible="false" styleClass="warning-message" />
<VBox spacing="5.0" fx:id="validationContainer">
<Label fx:id="validationHeaderLabel" text="%In order to proceed:" style="-fx-text-fill: -jr-error; -fx-font-weight: bold" visible="false" managed="false" />
<Button fx:id="shareOnSearchRxivButton"
onAction="#shareOnSearchRxiv"
text="%Export search queries (search-query format)">
<tooltip>
<Tooltip text="%Export search queries as search-query JSON files and open SearchRxiv"/>
</tooltip>
</Button>
<Label fx:id="directoryWarning"
text="%Warning: The selected directory is not empty."
visible="false"
styleClass="warning-message"/>
<VBox spacing="5.0"
fx:id="validationContainer">
<Label fx:id="validationHeaderLabel"
text="%In order to proceed:"
style="-fx-text-fill: -jr-error; -fx-font-weight: bold"
visible="false"
managed="false"/>
Comment thread
InAnYan marked this conversation as resolved.
<VBox spacing="3.0">
<Label fx:id="titleValidationLabel" visible="false" managed="false" />
<Label fx:id="authorsValidationLabel" visible="false" managed="false" />
<Label fx:id="questionsValidationLabel" visible="false" managed="false" />
<Label fx:id="queriesValidationLabel" visible="false" managed="false" />
<Label fx:id="catalogsValidationLabel" visible="false" managed="false" />
<Label fx:id="titleValidationLabel"
visible="false"
managed="false"/>
<Label fx:id="authorsValidationLabel"
visible="false"
managed="false"/>
<Label fx:id="questionsValidationLabel"
visible="false"
managed="false"/>
<Label fx:id="queriesValidationLabel"
visible="false"
managed="false"/>
<Label fx:id="catalogsValidationLabel"
visible="false"
managed="false"/>
</VBox>
</VBox>
</VBox>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.jabref.logic.exporter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;

import org.jabref.model.study.Study;
import org.jabref.model.study.StudyDatabase;
import org.jabref.model.study.StudyQuery;

import tools.jackson.databind.ObjectMapper;

/// Exports study search queries in the SearchRxiv / search-query JSON format.
/// One file is created per query and database combination,
/// as the format uses a single platform string per record.
///
/// The JSON format follows the search-query library's SearchFile specification.
///
/// @see <a href="https://github.com/CoLRev-Environment/search-query">search-query library</a>
/// @see <a href="https://colrev-environment.github.io/search-query/">search-query format documentation</a>
/// @see <a href="https://www.cabidigitallibrary.org/journal/searchrxiv">SearchRxiv</a>
public class SearchRxivExporter {

public static final String SEARCHRXIV_URL = "https://www.cabidigitallibrary.org/journal/searchrxiv";

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

/// Exports the study's search queries as JSON files to the given directory.
/// One file per query and database combination is created.
///
/// @param study The study containing queries, databases, and authors
/// @param directory The target directory to write the JSON files into
/// @throws IOException if a file cannot be written
public void export(Study study, Path directory) throws IOException {
int index = 0;
for (StudyQuery studyQuery : study.getQueries()) {
for (StudyDatabase database : study.getDatabases()) {
Path file = directory.resolve(buildFileName(studyQuery.getQuery(), database.getName(), index));
Files.writeString(file, buildJson(study, studyQuery.getQuery(), database.getName()));
index++;
}
}
}

/// Builds a JSON string following the search-query format.
/// Format spec: <a href="https://github.com/CoLRev-Environment/search-query">search-query</a>
private String buildJson(Study study, String query, String platform) throws IOException {
Map<String, Object> data = new LinkedHashMap<>();
data.put("search_string", query);
data.put("platform", platform.toLowerCase());
data.put("authors", study.getAuthors().stream()
.map(author -> Map.of("name", author))
.toList());
// Placeholder fields — to be filled by the user on SearchRxiv
data.put("record_info", Map.of());
data.put("date", Map.of());
return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(data);
}

private String buildFileName(String query, String databaseName, int index) {
String cleanDb = databaseName.replaceAll("[^A-Za-z0-9]", "_");
String cleanQuery = query.replaceAll("[^A-Za-z0-9]", "_");
if (cleanQuery.isEmpty()) {
cleanQuery = "query";
}
if (cleanQuery.length() > 20) {
cleanQuery = cleanQuery.substring(0, 20);
}
return cleanDb + "-" + cleanQuery + "-" + index + ".json";
}
}
19 changes: 4 additions & 15 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,15 @@ Error\ during\ persistence\ of\ crawling\ results.=Error during persistence of c
'%0'\ exists.\ Overwrite\ file?='%0' exists. Overwrite file?
Export=Export
Export\ to\ clipboard=Export to clipboard
Export\ search\ queries\ (search-query\ format)=Export search queries (search-query format)
Export\ search\ queries\ as\ search-query\ JSON\ files\ and\ open\ SearchRxiv=Export search queries as search-query JSON files and open SearchRxiv
Exported\ search\ queries\ for\ SearchRxiv.=Exported search queries for SearchRxiv.
Could\ not\ export\ search\ queries.=Could not export search queries.

Extract\ related\ work\ comments=Extract related work comments
Extract\ references\ from\ file\ (offline)=Extract references from file (offline)
Extract\ references\ from\ file\ (online)=Extract references from file (online)
Extract\ References\ (offline)=Extract References (offline)
Extract\ References\ (online)=Extract References (online)
Insert\ related\ work\ comments=Insert related work comments
Insert\ related\ work\ text=Insert related work text

Processing...=Processing...
Processing\ "%0"...=Processing "%0"...
Expand Down Expand Up @@ -1966,7 +1967,6 @@ Could\ not\ load\ PDF\:\ %0=Could not load PDF: %0
Loading\ PDF...=Loading PDF...
No\ PDF\ available\ for\ preview=No PDF available for preview
No\ PDF\ files\ available=No PDF files available
Please\ attach\ PDF\ files=Please attach PDF files

Live=Live
Locked=Locked
Expand Down Expand Up @@ -2158,17 +2158,6 @@ No\ active\ entry=No active entry
LaTeX\ citations=LaTeX citations
Search\ for\ citations\ in\ LaTeX\ files...=Search for citations in LaTeX files...
LaTeX\ Citations\ Search\ Results=LaTeX Citations Search Results
PDF\ file=PDF file
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.

Oh, but why are there so many deletions? I think the feature only introduces new strings, or not?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The deletions in JabRef_en.properties are from a bad merge, not intentional, i will fix it.

Comment\ owner=Comment owner
Source\ entry=Source entry
Related\ work\ text=Related work text
Matched\ references=Matched references
Citation\ marker=Citation marker
Comment\ preview=Comment preview
Insert=Insert
No\ citations\ were\ found\ in\ the\ related\ work\ text.=No citations were found in the related work text.
No\ matching\ references\ were\ found.=No matching references were found.
Processed\ related\ work\ comments.\ Inserted\:\ %0,\ unchanged\:\ %1.=Processed related work comments. Inserted: %0, unchanged: %1.
LaTeX\ files\ directory\:=LaTeX files directory:
LaTeX\ files\ found\:=LaTeX files found:
Search\ citations\ for\ this\ entry\ in\ LaTeX\ files=Search citations for this entry in LaTeX files
Expand Down
Loading
Loading