From 96873678a828c46c867599248d57609ea9858e39 Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Sun, 8 Jun 2025 17:24:18 -0700 Subject: [PATCH 1/9] feat: add 'Copy Field Content' submenu to entry context menu --- CHANGELOG.md | 1 + .../jabref/gui/actions/StandardActions.java | 6 + .../org/jabref/gui/edit/CopyMoreAction.java | 76 +++++ .../jabref/gui/maintable/RightClickMenu.java | 21 ++ .../jabref/gui/edit/CopyMoreActionTest.java | 274 +++++++++++++++++- 5 files changed, 377 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 234387fd283..ad0d10d015a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added support for import of a Refer/BibIX file format. [#13069](https://github.com/JabRef/jabref/issues/13069) - We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109) - We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129) +- We added a "Copy Field Content" submenu to the entry context menu, allowing users to quickly copy specific field contents including Author, Journal, Date, Keywords, and Abstract fields from selected entries. ### Changed diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index 647cef5e017..a2bdebd660c 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -20,6 +20,12 @@ public enum StandardActions implements Action { COPY_CITATION_HTML(Localization.lang("Copy citation (html)"), KeyBinding.COPY_PREVIEW), COPY_CITATION_TEXT(Localization.lang("Copy citation (text)")), COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), + COPY_FIELD_CONTENT(Localization.lang("Copy field content")), + COPY_FIELD_AUTHOR(Localization.lang("Author")), + COPY_FIELD_JOURNAL(Localization.lang("Journal")), + COPY_FIELD_DATE(Localization.lang("Date")), + COPY_FIELD_KEYWORDS(Localization.lang("Keywords")), + COPY_FIELD_ABSTRACT(Localization.lang("Abstract")), EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 80789f0c5fa..aacfcb82cfd 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -72,6 +72,16 @@ public void execute() { copyKeyAndLink(); case COPY_DOI, COPY_DOI_URL -> copyDoi(); + case COPY_FIELD_AUTHOR -> + copyField(StandardField.AUTHOR, "authors"); + case COPY_FIELD_JOURNAL -> + copyJournalField(); + case COPY_FIELD_DATE -> + copyField(StandardField.DATE, "dates"); + case COPY_FIELD_KEYWORDS -> + copyField(StandardField.KEYWORDS, "keywords"); + case COPY_FIELD_ABSTRACT -> + copyField(StandardField.ABSTRACT, "abstracts"); default -> LOGGER.info("Unknown copy command."); } @@ -264,4 +274,70 @@ private void copyKeyAndLink() { Long.toString(entries.size() - entriesWithKey.size()), Integer.toString(entries.size()))); } } + + private void copyField(StandardField field, String fieldDisplayName) { + List selectedBibEntries = stateManager.getSelectedEntries(); + + List fieldValues = selectedBibEntries.stream() + .filter(bibEntry -> bibEntry.getField(field).isPresent()) + .map(bibEntry -> bibEntry.getField(field).orElse("")) + .filter(value -> !value.isEmpty()) + .collect(Collectors.toList()); + + if (fieldValues.isEmpty()) { + dialogService.notify(Localization.lang("None of the selected entries have %0.", fieldDisplayName)); + return; + } + + final String copiedContent = String.join("\n", fieldValues); + clipBoardManager.setContent(copiedContent); + + if (fieldValues.size() == selectedBibEntries.size()) { + // All entries had the field. + dialogService.notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedContent))); + } else { + dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(selectedBibEntries.size() - fieldValues.size()), + Integer.toString(selectedBibEntries.size()), + fieldDisplayName)); + } + } + + private void copyJournalField() { + List selectedBibEntries = stateManager.getSelectedEntries(); + + List journalValues = selectedBibEntries.stream() + .filter(bibEntry -> + bibEntry.getField(StandardField.JOURNAL).isPresent() || + bibEntry.getField(StandardField.JOURNALTITLE).isPresent()) + .map(bibEntry -> { + // Prefer journal over journaltitle for consistency + if (bibEntry.getField(StandardField.JOURNAL).isPresent()) { + return bibEntry.getField(StandardField.JOURNAL).orElse(""); + } else { + return bibEntry.getField(StandardField.JOURNALTITLE).orElse(""); + } + }) + .filter(value -> !value.isEmpty()) + .collect(Collectors.toList()); + + if (journalValues.isEmpty()) { + dialogService.notify(Localization.lang("None of the selected entries have journal names.")); + return; + } + + final String copiedContent = String.join("\n", journalValues); + clipBoardManager.setContent(copiedContent); + + if (journalValues.size() == selectedBibEntries.size()) { + // All entries had journal fields. + dialogService.notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedContent))); + } else { + dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined journal names.", + Integer.toString(selectedBibEntries.size() - journalValues.size()), + Integer.toString(selectedBibEntries.size()))); + } + } } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 5a9c93e6cac..da210ccce9e 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -178,6 +178,8 @@ private static Menu createCopySubMenu(ActionFactory factory, copySpecialMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), + createCopyFieldContentSubMenu(factory, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository), + new SeparatorMenuItem(), factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), factory.createMenuItem(StandardActions.COPY_CITE_KEY, new CopyMoreAction(StandardActions.COPY_CITE_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new CopyMoreAction(StandardActions.COPY_KEY_AND_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), @@ -204,6 +206,25 @@ private static Menu createCopySubMenu(ActionFactory factory, return copySpecialMenu; } + private static Menu createCopyFieldContentSubMenu(ActionFactory factory, + DialogService dialogService, + StateManager stateManager, + ClipBoardManager clipBoardManager, + GuiPreferences preferences, + JournalAbbreviationRepository abbreviationRepository) { + Menu copyFieldContentMenu = factory.createMenu(StandardActions.COPY_FIELD_CONTENT); + + copyFieldContentMenu.getItems().addAll( + factory.createMenuItem(StandardActions.COPY_FIELD_AUTHOR, new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_FIELD_JOURNAL, new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_FIELD_DATE, new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_FIELD_KEYWORDS, new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), + factory.createMenuItem(StandardActions.COPY_FIELD_ABSTRACT, new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)) + ); + + return copyFieldContentMenu; + } + private static Menu createSendSubMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, diff --git a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java index 9a5b9743660..454eadf3ce5 100644 --- a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java +++ b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java @@ -43,6 +43,11 @@ class CopyMoreActionTest { private final List titles = new ArrayList<>(); private final List keys = new ArrayList<>(); private final List dois = new ArrayList<>(); + private final List authors = new ArrayList<>(); + private final List journals = new ArrayList<>(); + private final List dates = new ArrayList<>(); + private final List keywords = new ArrayList<>(); + private final List abstracts = new ArrayList<>(); private CopyMoreAction copyMoreAction; private BibEntry entry; @@ -50,16 +55,31 @@ class CopyMoreActionTest { @BeforeEach void setUp() { String title = "A tale from the trenches"; + String author = "Souti Chattopadhyay and Nicholas Nelson and Audrey Au and Natalia Morales and Christopher Sanchez and Rahul Pandita and Anita Sarma"; + String journal = "Journal of the American College of Nutrition"; + String date = "2001-10"; + String keyword = "software engineering, cognitive bias, empirical study"; + String abstractText = "This paper presents a study on cognitive biases in software development."; + entry = new BibEntry(StandardEntryType.Misc) - .withField(StandardField.AUTHOR, "Souti Chattopadhyay and Nicholas Nelson and Audrey Au and Natalia Morales and Christopher Sanchez and Rahul Pandita and Anita Sarma") + .withField(StandardField.AUTHOR, author) .withField(StandardField.TITLE, title) .withField(StandardField.YEAR, "2020") + .withField(StandardField.JOURNAL, journal) + .withField(StandardField.DATE, date) + .withField(StandardField.KEYWORDS, keyword) + .withField(StandardField.ABSTRACT, abstractText) .withField(StandardField.DOI, "10.1145/3377811.3380330") .withField(StandardField.SUBTITLE, "cognitive biases and software development") .withCitationKey("abc"); titles.add(title); keys.add("abc"); dois.add("10.1145/3377811.3380330"); + authors.add(author); + journals.add(journal); + dates.add(date); + keywords.add(keyword); + abstracts.add(abstractText); ExternalApplicationsPreferences externalApplicationsPreferences = mock(ExternalApplicationsPreferences.class); when(externalApplicationsPreferences.getCiteCommand()).thenReturn(new CitationCommandString("\\cite{", ",", "}")); @@ -226,4 +246,256 @@ void executeCopyDoiOnSuccess() { verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedDois))); } + + @Test + void executeCopyAuthorWithNoAuthor() { + BibEntry entryWithNoAuthor = (BibEntry) entry.clone(); + entryWithNoAuthor.clearField(StandardField.AUTHOR); + ObservableList entriesWithNoAuthors = FXCollections.observableArrayList(entryWithNoAuthor); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoAuthors)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoAuthors); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "authors")); + } + + @Test + void executeCopyAuthorOnPartialSuccess() { + BibEntry entryWithNoAuthor = (BibEntry) entry.clone(); + entryWithNoAuthor.clearField(StandardField.AUTHOR); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoAuthor, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedAuthors = String.join("\n", authors); + verify(clipBoardManager, times(1)).setContent(copiedAuthors); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(mixedEntries.size() - authors.size()), Integer.toString(mixedEntries.size()), "authors")); + } + + @Test + void executeCopyAuthorOnSuccess() { + ObservableList entriesWithAuthors = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithAuthors)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithAuthors); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedAuthors = String.join("\n", authors); + verify(clipBoardManager, times(1)).setContent(copiedAuthors); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedAuthors))); + } + + @Test + void executeCopyJournalWithNoJournal() { + BibEntry entryWithNoJournal = (BibEntry) entry.clone(); + entryWithNoJournal.clearField(StandardField.JOURNAL); + entryWithNoJournal.clearField(StandardField.JOURNALTITLE); + ObservableList entriesWithNoJournals = FXCollections.observableArrayList(entryWithNoJournal); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoJournals)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoJournals); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have journal names.")); + } + + @Test + void executeCopyJournalOnPartialSuccess() { + BibEntry entryWithNoJournal = (BibEntry) entry.clone(); + entryWithNoJournal.clearField(StandardField.JOURNAL); + entryWithNoJournal.clearField(StandardField.JOURNALTITLE); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoJournal, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedJournals = String.join("\n", journals); + verify(clipBoardManager, times(1)).setContent(copiedJournals); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined journal names.", + Integer.toString(mixedEntries.size() - journals.size()), Integer.toString(mixedEntries.size()))); + } + + @Test + void executeCopyJournalOnSuccess() { + ObservableList entriesWithJournals = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithJournals)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithJournals); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedJournals = String.join("\n", journals); + verify(clipBoardManager, times(1)).setContent(copiedJournals); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedJournals))); + } + + @Test + void executeCopyDateWithNoDate() { + BibEntry entryWithNoDate = (BibEntry) entry.clone(); + entryWithNoDate.clearField(StandardField.DATE); + ObservableList entriesWithNoDates = FXCollections.observableArrayList(entryWithNoDate); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoDates)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoDates); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "dates")); + } + + @Test + void executeCopyDateOnPartialSuccess() { + BibEntry entryWithNoDate = (BibEntry) entry.clone(); + entryWithNoDate.clearField(StandardField.DATE); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoDate, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedDates = String.join("\n", dates); + verify(clipBoardManager, times(1)).setContent(copiedDates); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(mixedEntries.size() - dates.size()), Integer.toString(mixedEntries.size()), "dates")); + } + + @Test + void executeCopyDateOnSuccess() { + ObservableList entriesWithDates = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithDates)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithDates); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedDates = String.join("\n", dates); + verify(clipBoardManager, times(1)).setContent(copiedDates); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedDates))); + } + + @Test + void executeCopyKeywordsWithNoKeywords() { + BibEntry entryWithNoKeywords = (BibEntry) entry.clone(); + entryWithNoKeywords.clearField(StandardField.KEYWORDS); + ObservableList entriesWithNoKeywords = FXCollections.observableArrayList(entryWithNoKeywords); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoKeywords)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoKeywords); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "keywords")); + } + + @Test + void executeCopyKeywordsOnPartialSuccess() { + BibEntry entryWithNoKeywords = (BibEntry) entry.clone(); + entryWithNoKeywords.clearField(StandardField.KEYWORDS); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoKeywords, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedKeywords = String.join("\n", keywords); + verify(clipBoardManager, times(1)).setContent(copiedKeywords); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(mixedEntries.size() - keywords.size()), Integer.toString(mixedEntries.size()), "keywords")); + } + + @Test + void executeCopyKeywordsOnSuccess() { + ObservableList entriesWithKeywords = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithKeywords)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithKeywords); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedKeywords = String.join("\n", keywords); + verify(clipBoardManager, times(1)).setContent(copiedKeywords); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedKeywords))); + } + + @Test + void executeCopyAbstractWithNoAbstract() { + BibEntry entryWithNoAbstract = (BibEntry) entry.clone(); + entryWithNoAbstract.clearField(StandardField.ABSTRACT); + ObservableList entriesWithNoAbstracts = FXCollections.observableArrayList(entryWithNoAbstract); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoAbstracts)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithNoAbstracts); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + verify(clipBoardManager, times(0)).setContent(any(String.class)); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "abstracts")); + } + + @Test + void executeCopyAbstractOnPartialSuccess() { + BibEntry entryWithNoAbstract = (BibEntry) entry.clone(); + entryWithNoAbstract.clearField(StandardField.ABSTRACT); + ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoAbstract, entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(mixedEntries); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedAbstracts = String.join("\n", abstracts); + verify(clipBoardManager, times(1)).setContent(copiedAbstracts); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(mixedEntries.size() - abstracts.size()), Integer.toString(mixedEntries.size()), "abstracts")); + } + + @Test + void executeCopyAbstractOnSuccess() { + ObservableList entriesWithAbstracts = FXCollections.observableArrayList(entry); + BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithAbstracts)); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.ofNullable(databaseContext)); + when(stateManager.getSelectedEntries()).thenReturn(entriesWithAbstracts); + copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); + copyMoreAction.execute(); + + String copiedAbstracts = String.join("\n", abstracts); + verify(clipBoardManager, times(1)).setContent(copiedAbstracts); + verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedAbstracts))); + } } From 38a72ceb8d31efbc35d11c8b6d4fa9e338dd3ad2 Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Sun, 8 Jun 2025 19:08:50 -0700 Subject: [PATCH 2/9] fix: fix label case consistency, replace String.join with StringJoiner in tests, add null safety check for createCopyFieldContentSubMenu method and use Localization.lang() for internationalization of field display names --- .../jabref/gui/actions/StandardActions.java | 10 +-- .../org/jabref/gui/edit/CopyMoreAction.java | 8 +-- .../jabref/gui/maintable/RightClickMenu.java | 5 ++ .../jabref/gui/edit/CopyMoreActionTest.java | 61 ++++++++++++++++--- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index a2bdebd660c..ce853a83615 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -21,11 +21,11 @@ public enum StandardActions implements Action { COPY_CITATION_TEXT(Localization.lang("Copy citation (text)")), COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), COPY_FIELD_CONTENT(Localization.lang("Copy field content")), - COPY_FIELD_AUTHOR(Localization.lang("Author")), - COPY_FIELD_JOURNAL(Localization.lang("Journal")), - COPY_FIELD_DATE(Localization.lang("Date")), - COPY_FIELD_KEYWORDS(Localization.lang("Keywords")), - COPY_FIELD_ABSTRACT(Localization.lang("Abstract")), + COPY_FIELD_AUTHOR(Localization.lang("author")), + COPY_FIELD_JOURNAL(Localization.lang("journal")), + COPY_FIELD_DATE(Localization.lang("date")), + COPY_FIELD_KEYWORDS(Localization.lang("keywords")), + COPY_FIELD_ABSTRACT(Localization.lang("abstract")), EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index aacfcb82cfd..6485f0168b3 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -73,15 +73,15 @@ public void execute() { case COPY_DOI, COPY_DOI_URL -> copyDoi(); case COPY_FIELD_AUTHOR -> - copyField(StandardField.AUTHOR, "authors"); + copyField(StandardField.AUTHOR, Localization.lang("authors")); case COPY_FIELD_JOURNAL -> copyJournalField(); case COPY_FIELD_DATE -> - copyField(StandardField.DATE, "dates"); + copyField(StandardField.DATE, Localization.lang("dates")); case COPY_FIELD_KEYWORDS -> - copyField(StandardField.KEYWORDS, "keywords"); + copyField(StandardField.KEYWORDS, Localization.lang("keywords")); case COPY_FIELD_ABSTRACT -> - copyField(StandardField.ABSTRACT, "abstracts"); + copyField(StandardField.ABSTRACT, Localization.lang("abstracts")); default -> LOGGER.info("Unknown copy command."); } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index da210ccce9e..1028dd84eaa 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -213,6 +213,11 @@ private static Menu createCopyFieldContentSubMenu(ActionFactory factory, GuiPreferences preferences, JournalAbbreviationRepository abbreviationRepository) { Menu copyFieldContentMenu = factory.createMenu(StandardActions.COPY_FIELD_CONTENT); + + // Ensure we never return null + if (copyFieldContentMenu == null) { + throw new IllegalStateException("Failed to create Copy Field Content menu"); + } copyFieldContentMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY_FIELD_AUTHOR, new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), diff --git a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java index 454eadf3ce5..5497f4b2662 100644 --- a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java +++ b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.StringJoiner; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -275,7 +276,11 @@ void executeCopyAuthorOnPartialSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedAuthors = String.join("\n", authors); + StringJoiner authorsJoiner = new StringJoiner("\n"); + for (String author : authors) { + authorsJoiner.add(author); + } + String copiedAuthors = authorsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAuthors); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", Integer.toString(mixedEntries.size() - authors.size()), Integer.toString(mixedEntries.size()), "authors")); @@ -291,7 +296,11 @@ void executeCopyAuthorOnSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedAuthors = String.join("\n", authors); + StringJoiner authorsJoiner = new StringJoiner("\n"); + for (String author : authors) { + authorsJoiner.add(author); + } + String copiedAuthors = authorsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAuthors); verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedAuthors))); @@ -327,7 +336,11 @@ void executeCopyJournalOnPartialSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedJournals = String.join("\n", journals); + StringJoiner journalsJoiner = new StringJoiner("\n"); + for (String journal : journals) { + journalsJoiner.add(journal); + } + String copiedJournals = journalsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedJournals); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined journal names.", Integer.toString(mixedEntries.size() - journals.size()), Integer.toString(mixedEntries.size()))); @@ -343,7 +356,11 @@ void executeCopyJournalOnSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_JOURNAL, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedJournals = String.join("\n", journals); + StringJoiner journalsJoiner = new StringJoiner("\n"); + for (String journal : journals) { + journalsJoiner.add(journal); + } + String copiedJournals = journalsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedJournals); verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedJournals))); @@ -377,7 +394,11 @@ void executeCopyDateOnPartialSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedDates = String.join("\n", dates); + StringJoiner datesJoiner = new StringJoiner("\n"); + for (String date : dates) { + datesJoiner.add(date); + } + String copiedDates = datesJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedDates); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", Integer.toString(mixedEntries.size() - dates.size()), Integer.toString(mixedEntries.size()), "dates")); @@ -393,7 +414,11 @@ void executeCopyDateOnSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_DATE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedDates = String.join("\n", dates); + StringJoiner datesJoiner = new StringJoiner("\n"); + for (String date : dates) { + datesJoiner.add(date); + } + String copiedDates = datesJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedDates); verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedDates))); @@ -427,7 +452,11 @@ void executeCopyKeywordsOnPartialSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedKeywords = String.join("\n", keywords); + StringJoiner keywordsJoiner = new StringJoiner("\n"); + for (String keyword : keywords) { + keywordsJoiner.add(keyword); + } + String copiedKeywords = keywordsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedKeywords); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", Integer.toString(mixedEntries.size() - keywords.size()), Integer.toString(mixedEntries.size()), "keywords")); @@ -443,7 +472,11 @@ void executeCopyKeywordsOnSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_KEYWORDS, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedKeywords = String.join("\n", keywords); + StringJoiner keywordsJoiner = new StringJoiner("\n"); + for (String keyword : keywords) { + keywordsJoiner.add(keyword); + } + String copiedKeywords = keywordsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedKeywords); verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedKeywords))); @@ -477,7 +510,11 @@ void executeCopyAbstractOnPartialSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedAbstracts = String.join("\n", abstracts); + StringJoiner abstractsJoiner = new StringJoiner("\n"); + for (String abstractText : abstracts) { + abstractsJoiner.add(abstractText); + } + String copiedAbstracts = abstractsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAbstracts); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", Integer.toString(mixedEntries.size() - abstracts.size()), Integer.toString(mixedEntries.size()), "abstracts")); @@ -493,7 +530,11 @@ void executeCopyAbstractOnSuccess() { copyMoreAction = new CopyMoreAction(StandardActions.COPY_FIELD_ABSTRACT, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository); copyMoreAction.execute(); - String copiedAbstracts = String.join("\n", abstracts); + StringJoiner abstractsJoiner = new StringJoiner("\n"); + for (String abstractText : abstracts) { + abstractsJoiner.add(abstractText); + } + String copiedAbstracts = abstractsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAbstracts); verify(dialogService, times(1)).notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedAbstracts))); From 56cb4979c0fa363cadc40342c0709496daa7e7b9 Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Sun, 8 Jun 2025 19:09:44 -0700 Subject: [PATCH 3/9] feat: add localization keys for copy field content feature --- jablib/src/main/resources/l10n/JabRef_en.properties | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 433be54d054..73b2219f9a6 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -27,6 +27,8 @@ LTWA=LTWA About\ JabRef=About JabRef Abstract=Abstract +abstract=abstract +abstracts=abstracts Accept=Accept @@ -167,6 +169,7 @@ Content=Content Copy=Copy Copy\ title=Copy title +Copy\ field\ content=Copy field content Copy\ citation\ (html)=Copy citation (html) Copy\ citation\ (text)=Copy citation (text) Copy\ citation\ key=Copy citation key @@ -541,6 +544,7 @@ keys\ in\ library=keys in library Keyword=Keyword Keywords=Keywords +keywords=keywords Label=Label Label\:\ %0=Label: %0 @@ -1116,6 +1120,7 @@ Citation=Citation Connecting...=Connecting... Select\ style=Select style Journals=Journals +journal=journal Cite=Cite Cite\ in-text=Cite in-text Insert\ empty\ citation=Insert empty citation @@ -1376,6 +1381,8 @@ Could\ not\ connect\ to\ %0=Could not connect to %0 Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ title.=Warning: %0 out of %1 entries have undefined title. Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ citation\ key.=Warning: %0 out of %1 entries have undefined citation key. Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ DOIs.=Warning: %0 out of %1 entries have undefined DOIs. +Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ %2.=Warning: %0 out of %1 entries have undefined %2. +Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ journal\ names.=Warning: %0 out of %1 entries have undefined journal names. Really\ delete\ the\ selected\ entry?=Really delete the selected entry? Really\ delete\ the\ %0\ selected\ entries?=Really delete the %0 selected entries? @@ -1466,6 +1473,8 @@ Display\ keywords\ appearing\ in\ ANY\ entry=Display keywords appearing in ANY e None\ of\ the\ selected\ entries\ have\ titles.=None of the selected entries have titles. None\ of\ the\ selected\ entries\ have\ citation\ keys.=None of the selected entries have citation keys. None\ of\ the\ selected\ entries\ have\ DOIs.=None of the selected entries have DOIs. +None\ of\ the\ selected\ entries\ have\ %0.=None of the selected entries have %0. +None\ of\ the\ selected\ entries\ have\ journal\ names.=None of the selected entries have journal names. Unabbreviate\ journal\ names=Unabbreviate journal names Unabbreviating...=Unabbreviating... @@ -1859,7 +1868,11 @@ You\ are\ attempting\ to\ download\ full\ text\ documents\ for\ %0\ entries.\nJa last\ four\ nonpunctuation\ characters\ should\ be\ numerals=last four nonpunctuation characters should be numerals Author=Author +author=author +authors=authors Date=Date +date=date +dates=dates File\ annotations=File annotations Show\ file\ annotations=Show file annotations shared=shared From e07c3143ecd4f1ca47944210c644924f35423ae7 Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Tue, 10 Jun 2025 05:10:22 -0700 Subject: [PATCH 4/9] fix: use getFieldOrAlias(), replace String.join with Collectors.joining and update required localization entries to JabRef_en.properties --- .../jabref/gui/actions/StandardActions.java | 10 ++-- .../org/jabref/gui/edit/CopyMoreAction.java | 53 +++---------------- .../jabref/gui/maintable/RightClickMenu.java | 5 -- .../main/resources/l10n/JabRef_en.properties | 9 +--- 4 files changed, 14 insertions(+), 63 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java index ce853a83615..a2bdebd660c 100644 --- a/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/jabgui/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -21,11 +21,11 @@ public enum StandardActions implements Action { COPY_CITATION_TEXT(Localization.lang("Copy citation (text)")), COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), COPY_FIELD_CONTENT(Localization.lang("Copy field content")), - COPY_FIELD_AUTHOR(Localization.lang("author")), - COPY_FIELD_JOURNAL(Localization.lang("journal")), - COPY_FIELD_DATE(Localization.lang("date")), - COPY_FIELD_KEYWORDS(Localization.lang("keywords")), - COPY_FIELD_ABSTRACT(Localization.lang("abstract")), + COPY_FIELD_AUTHOR(Localization.lang("Author")), + COPY_FIELD_JOURNAL(Localization.lang("Journal")), + COPY_FIELD_DATE(Localization.lang("Date")), + COPY_FIELD_KEYWORDS(Localization.lang("Keywords")), + COPY_FIELD_ABSTRACT(Localization.lang("Abstract")), EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 6485f0168b3..1b8cc32ad22 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -73,15 +73,15 @@ public void execute() { case COPY_DOI, COPY_DOI_URL -> copyDoi(); case COPY_FIELD_AUTHOR -> - copyField(StandardField.AUTHOR, Localization.lang("authors")); + copyField(StandardField.AUTHOR, Localization.lang("Author")); case COPY_FIELD_JOURNAL -> - copyJournalField(); + copyField(StandardField.JOURNAL, Localization.lang("Journal")); case COPY_FIELD_DATE -> - copyField(StandardField.DATE, Localization.lang("dates")); + copyField(StandardField.DATE, Localization.lang("Date")); case COPY_FIELD_KEYWORDS -> - copyField(StandardField.KEYWORDS, Localization.lang("keywords")); + copyField(StandardField.KEYWORDS, Localization.lang("Keywords")); case COPY_FIELD_ABSTRACT -> - copyField(StandardField.ABSTRACT, Localization.lang("abstracts")); + copyField(StandardField.ABSTRACT, Localization.lang("Abstract")); default -> LOGGER.info("Unknown copy command."); } @@ -279,8 +279,8 @@ private void copyField(StandardField field, String fieldDisplayName) { List selectedBibEntries = stateManager.getSelectedEntries(); List fieldValues = selectedBibEntries.stream() - .filter(bibEntry -> bibEntry.getField(field).isPresent()) - .map(bibEntry -> bibEntry.getField(field).orElse("")) + .filter(bibEntry -> bibEntry.getFieldOrAlias(field).isPresent()) + .map(bibEntry -> bibEntry.getFieldOrAlias(field).orElse("")) .filter(value -> !value.isEmpty()) .collect(Collectors.toList()); @@ -289,7 +289,7 @@ private void copyField(StandardField field, String fieldDisplayName) { return; } - final String copiedContent = String.join("\n", fieldValues); + final String copiedContent = fieldValues.stream().collect(Collectors.joining("\n")); clipBoardManager.setContent(copiedContent); if (fieldValues.size() == selectedBibEntries.size()) { @@ -303,41 +303,4 @@ private void copyField(StandardField field, String fieldDisplayName) { fieldDisplayName)); } } - - private void copyJournalField() { - List selectedBibEntries = stateManager.getSelectedEntries(); - - List journalValues = selectedBibEntries.stream() - .filter(bibEntry -> - bibEntry.getField(StandardField.JOURNAL).isPresent() || - bibEntry.getField(StandardField.JOURNALTITLE).isPresent()) - .map(bibEntry -> { - // Prefer journal over journaltitle for consistency - if (bibEntry.getField(StandardField.JOURNAL).isPresent()) { - return bibEntry.getField(StandardField.JOURNAL).orElse(""); - } else { - return bibEntry.getField(StandardField.JOURNALTITLE).orElse(""); - } - }) - .filter(value -> !value.isEmpty()) - .collect(Collectors.toList()); - - if (journalValues.isEmpty()) { - dialogService.notify(Localization.lang("None of the selected entries have journal names.")); - return; - } - - final String copiedContent = String.join("\n", journalValues); - clipBoardManager.setContent(copiedContent); - - if (journalValues.size() == selectedBibEntries.size()) { - // All entries had journal fields. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(copiedContent))); - } else { - dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined journal names.", - Integer.toString(selectedBibEntries.size() - journalValues.size()), - Integer.toString(selectedBibEntries.size()))); - } - } } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 1028dd84eaa..da210ccce9e 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -213,11 +213,6 @@ private static Menu createCopyFieldContentSubMenu(ActionFactory factory, GuiPreferences preferences, JournalAbbreviationRepository abbreviationRepository) { Menu copyFieldContentMenu = factory.createMenu(StandardActions.COPY_FIELD_CONTENT); - - // Ensure we never return null - if (copyFieldContentMenu == null) { - throw new IllegalStateException("Failed to create Copy Field Content menu"); - } copyFieldContentMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY_FIELD_AUTHOR, new CopyMoreAction(StandardActions.COPY_FIELD_AUTHOR, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 73b2219f9a6..30578f392d5 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -27,8 +27,6 @@ LTWA=LTWA About\ JabRef=About JabRef Abstract=Abstract -abstract=abstract -abstracts=abstracts Accept=Accept @@ -544,7 +542,6 @@ keys\ in\ library=keys in library Keyword=Keyword Keywords=Keywords -keywords=keywords Label=Label Label\:\ %0=Label: %0 @@ -1119,8 +1116,8 @@ Problem\ collecting\ citations=Problem collecting citations Citation=Citation Connecting...=Connecting... Select\ style=Select style +Journal=Journal Journals=Journals -journal=journal Cite=Cite Cite\ in-text=Cite in-text Insert\ empty\ citation=Insert empty citation @@ -1868,11 +1865,7 @@ You\ are\ attempting\ to\ download\ full\ text\ documents\ for\ %0\ entries.\nJa last\ four\ nonpunctuation\ characters\ should\ be\ numerals=last four nonpunctuation characters should be numerals Author=Author -author=author -authors=authors Date=Date -date=date -dates=dates File\ annotations=File annotations Show\ file\ annotations=Show file annotations shared=shared From 578c2bbf35d733273ba09b4e6326d59b7e066c33 Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Tue, 10 Jun 2025 05:40:19 -0700 Subject: [PATCH 5/9] fix: update CompyMoreActionTest --- .../jabref/gui/edit/CopyMoreActionTest.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java index 5497f4b2662..977d3862834 100644 --- a/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java +++ b/jabgui/src/test/java/org/jabref/gui/edit/CopyMoreActionTest.java @@ -261,7 +261,7 @@ void executeCopyAuthorWithNoAuthor() { copyMoreAction.execute(); verify(clipBoardManager, times(0)).setContent(any(String.class)); - verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "authors")); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "Author")); } @Test @@ -283,7 +283,7 @@ void executeCopyAuthorOnPartialSuccess() { String copiedAuthors = authorsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAuthors); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", - Integer.toString(mixedEntries.size() - authors.size()), Integer.toString(mixedEntries.size()), "authors")); + Integer.toString(mixedEntries.size() - authors.size()), Integer.toString(mixedEntries.size()), "Author")); } @Test @@ -320,7 +320,7 @@ void executeCopyJournalWithNoJournal() { copyMoreAction.execute(); verify(clipBoardManager, times(0)).setContent(any(String.class)); - verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have journal names.")); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "Journal")); } @Test @@ -342,8 +342,8 @@ void executeCopyJournalOnPartialSuccess() { } String copiedJournals = journalsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedJournals); - verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined journal names.", - Integer.toString(mixedEntries.size() - journals.size()), Integer.toString(mixedEntries.size()))); + verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", + Integer.toString(mixedEntries.size() - journals.size()), Integer.toString(mixedEntries.size()), "Journal")); } @Test @@ -370,6 +370,7 @@ void executeCopyJournalOnSuccess() { void executeCopyDateWithNoDate() { BibEntry entryWithNoDate = (BibEntry) entry.clone(); entryWithNoDate.clearField(StandardField.DATE); + entryWithNoDate.clearField(StandardField.YEAR); ObservableList entriesWithNoDates = FXCollections.observableArrayList(entryWithNoDate); BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(entriesWithNoDates)); @@ -379,13 +380,14 @@ void executeCopyDateWithNoDate() { copyMoreAction.execute(); verify(clipBoardManager, times(0)).setContent(any(String.class)); - verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "dates")); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "Date")); } @Test void executeCopyDateOnPartialSuccess() { BibEntry entryWithNoDate = (BibEntry) entry.clone(); entryWithNoDate.clearField(StandardField.DATE); + entryWithNoDate.clearField(StandardField.YEAR); ObservableList mixedEntries = FXCollections.observableArrayList(entryWithNoDate, entry); BibDatabaseContext databaseContext = new BibDatabaseContext(new BibDatabase(mixedEntries)); @@ -401,7 +403,7 @@ void executeCopyDateOnPartialSuccess() { String copiedDates = datesJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedDates); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", - Integer.toString(mixedEntries.size() - dates.size()), Integer.toString(mixedEntries.size()), "dates")); + Integer.toString(mixedEntries.size() - dates.size()), Integer.toString(mixedEntries.size()), "Date")); } @Test @@ -437,7 +439,7 @@ void executeCopyKeywordsWithNoKeywords() { copyMoreAction.execute(); verify(clipBoardManager, times(0)).setContent(any(String.class)); - verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "keywords")); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "Keywords")); } @Test @@ -459,7 +461,7 @@ void executeCopyKeywordsOnPartialSuccess() { String copiedKeywords = keywordsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedKeywords); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", - Integer.toString(mixedEntries.size() - keywords.size()), Integer.toString(mixedEntries.size()), "keywords")); + Integer.toString(mixedEntries.size() - keywords.size()), Integer.toString(mixedEntries.size()), "Keywords")); } @Test @@ -495,7 +497,7 @@ void executeCopyAbstractWithNoAbstract() { copyMoreAction.execute(); verify(clipBoardManager, times(0)).setContent(any(String.class)); - verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "abstracts")); + verify(dialogService, times(1)).notify(Localization.lang("None of the selected entries have %0.", "Abstract")); } @Test @@ -517,7 +519,7 @@ void executeCopyAbstractOnPartialSuccess() { String copiedAbstracts = abstractsJoiner.toString(); verify(clipBoardManager, times(1)).setContent(copiedAbstracts); verify(dialogService, times(1)).notify(Localization.lang("Warning: %0 out of %1 entries have undefined %2.", - Integer.toString(mixedEntries.size() - abstracts.size()), Integer.toString(mixedEntries.size()), "abstracts")); + Integer.toString(mixedEntries.size() - abstracts.size()), Integer.toString(mixedEntries.size()), "Abstract")); } @Test From 63f003736f52449973992428c5156fdbce7d97ea Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Tue, 10 Jun 2025 05:58:04 -0700 Subject: [PATCH 6/9] fix: update localization --- jablib/src/main/resources/l10n/JabRef_en.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/jablib/src/main/resources/l10n/JabRef_en.properties b/jablib/src/main/resources/l10n/JabRef_en.properties index 30578f392d5..cd81995baba 100644 --- a/jablib/src/main/resources/l10n/JabRef_en.properties +++ b/jablib/src/main/resources/l10n/JabRef_en.properties @@ -1379,7 +1379,6 @@ Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ title.=Warning: %0 out of Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ citation\ key.=Warning: %0 out of %1 entries have undefined citation key. Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ DOIs.=Warning: %0 out of %1 entries have undefined DOIs. Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ %2.=Warning: %0 out of %1 entries have undefined %2. -Warning\:\ %0\ out\ of\ %1\ entries\ have\ undefined\ journal\ names.=Warning: %0 out of %1 entries have undefined journal names. Really\ delete\ the\ selected\ entry?=Really delete the selected entry? Really\ delete\ the\ %0\ selected\ entries?=Really delete the %0 selected entries? @@ -1471,7 +1470,6 @@ None\ of\ the\ selected\ entries\ have\ titles.=None of the selected entries hav None\ of\ the\ selected\ entries\ have\ citation\ keys.=None of the selected entries have citation keys. None\ of\ the\ selected\ entries\ have\ DOIs.=None of the selected entries have DOIs. None\ of\ the\ selected\ entries\ have\ %0.=None of the selected entries have %0. -None\ of\ the\ selected\ entries\ have\ journal\ names.=None of the selected entries have journal names. Unabbreviate\ journal\ names=Unabbreviate journal names Unabbreviating...=Unabbreviating... From ca0b2fe181682a3bf86dd4c0cb1b79588e2f3861 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 25 Jun 2025 20:49:46 +0200 Subject: [PATCH 7/9] Update CHANGELOG.md --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eb32bd89bd..c015c7ba552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,13 +37,10 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added support for import of a Refer/BibIX file format. [#13069](https://github.com/JabRef/jabref/issues/13069) - We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109) - We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129) -<<<<<<< feature/copy-field-submenu - We added a "Copy Field Content" submenu to the entry context menu, allowing users to quickly copy specific field contents including Author, Journal, Date, Keywords, and Abstract fields from selected entries. -======= - We added a highlighted diff regarding changes to the Group Tree Structure of a bib file, made outside JabRef. [#11221](https://github.com/JabRef/jabref/issues/11221) - We added a new setting in the 'Entry Editor' preferences to hide the 'File Annotations' tab when no annotations are available. [#13143](https://github.com/JabRef/jabref/issues/13143) - We added support for multi-file import across different formats. [#13269](https://github.com/JabRef/jabref/issues/13269) ->>>>>>> main ### Changed From 0e4f03ae8615d8315702c954da31c5a2c4f687bd Mon Sep 17 00:00:00 2001 From: Yichen Yang Date: Mon, 14 Jul 2025 14:32:01 -0700 Subject: [PATCH 8/9] fix: apply LatexToUnicodeAdapter in copy operations --- jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 1b8cc32ad22..1268bf964b3 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -23,6 +23,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.strings.LatexToUnicodeAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,7 +93,7 @@ private void copyTitle() { List titles = selectedBibEntries.stream() .filter(bibEntry -> bibEntry.getTitle().isPresent()) - .map(bibEntry -> bibEntry.getTitle().get()) + .map(bibEntry -> LatexToUnicodeAdapter.format(bibEntry.getTitle().get())) .collect(Collectors.toList()); if (titles.isEmpty()) { @@ -280,7 +281,7 @@ private void copyField(StandardField field, String fieldDisplayName) { List fieldValues = selectedBibEntries.stream() .filter(bibEntry -> bibEntry.getFieldOrAlias(field).isPresent()) - .map(bibEntry -> bibEntry.getFieldOrAlias(field).orElse("")) + .map(bibEntry -> LatexToUnicodeAdapter.format(bibEntry.getFieldOrAlias(field).orElse(""))) .filter(value -> !value.isEmpty()) .collect(Collectors.toList()); From 10a77e5317fe77d5ed579299213d7e896d2a648a Mon Sep 17 00:00:00 2001 From: Yichen Yang <78476972+yoasaaa@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:42:21 -0700 Subject: [PATCH 9/9] Update CopyMoreAction.java --- jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 1268bf964b3..1fe50739ea6 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -294,7 +294,6 @@ private void copyField(StandardField field, String fieldDisplayName) { clipBoardManager.setContent(copiedContent); if (fieldValues.size() == selectedBibEntries.size()) { - // All entries had the field. dialogService.notify(Localization.lang("Copied '%0' to clipboard.", JabRefDialogService.shortenDialogMessage(copiedContent))); } else {