From 957b950372557ab72c560a1b9b21e532c530b073 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 26 Oct 2024 15:41:09 +0200 Subject: [PATCH 001/108] New Crowdin updates (#12091) * New translations jabref_en.properties (French) * New translations jabref_en.properties (Polish) * New translations jabref_en.properties (Portuguese, Brazilian) * New translations jabref_en.properties (Spanish) * New translations jabref_en.properties (Arabic) * New translations jabref_en.properties (Danish) * New translations jabref_en.properties (German) * New translations jabref_en.properties (Greek) * New translations jabref_en.properties (Finnish) * New translations jabref_en.properties (Italian) * New translations jabref_en.properties (Japanese) * New translations jabref_en.properties (Korean) * New translations jabref_en.properties (Dutch) * New translations jabref_en.properties (Norwegian) * New translations jabref_en.properties (Portuguese) * New translations jabref_en.properties (Russian) * New translations jabref_en.properties (Swedish) * New translations jabref_en.properties (Turkish) * New translations jabref_en.properties (Ukrainian) * New translations jabref_en.properties (Chinese Simplified) * New translations jabref_en.properties (Chinese Traditional) * New translations jabref_en.properties (Vietnamese) * New translations jabref_en.properties (Indonesian) * New translations jabref_en.properties (Persian) * New translations jabref_en.properties (Tagalog) --- src/main/resources/l10n/JabRef_ar.properties | 1 + src/main/resources/l10n/JabRef_da.properties | 1 + src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_el.properties | 1 + src/main/resources/l10n/JabRef_es.properties | 1 + src/main/resources/l10n/JabRef_fa.properties | 1 + src/main/resources/l10n/JabRef_fi.properties | 1 + src/main/resources/l10n/JabRef_fr.properties | 3 +++ src/main/resources/l10n/JabRef_id.properties | 1 + src/main/resources/l10n/JabRef_it.properties | 3 +++ src/main/resources/l10n/JabRef_ja.properties | 1 + src/main/resources/l10n/JabRef_ko.properties | 1 + src/main/resources/l10n/JabRef_nl.properties | 1 + src/main/resources/l10n/JabRef_no.properties | 1 + src/main/resources/l10n/JabRef_pl.properties | 3 +++ src/main/resources/l10n/JabRef_pt.properties | 1 + src/main/resources/l10n/JabRef_pt_BR.properties | 1 + src/main/resources/l10n/JabRef_ru.properties | 1 + src/main/resources/l10n/JabRef_sv.properties | 1 + src/main/resources/l10n/JabRef_tl.properties | 1 + src/main/resources/l10n/JabRef_tr.properties | 1 + src/main/resources/l10n/JabRef_uk.properties | 1 + src/main/resources/l10n/JabRef_vi.properties | 1 + src/main/resources/l10n/JabRef_zh_CN.properties | 1 + src/main/resources/l10n/JabRef_zh_TW.properties | 1 + 25 files changed, 31 insertions(+) diff --git a/src/main/resources/l10n/JabRef_ar.properties b/src/main/resources/l10n/JabRef_ar.properties index 231318377dd..d94abe33f65 100644 --- a/src/main/resources/l10n/JabRef_ar.properties +++ b/src/main/resources/l10n/JabRef_ar.properties @@ -721,5 +721,6 @@ File\ not\ found=لم يتم العثور على الملف + diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 527dcc58e28..b7b4662f889 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -1013,4 +1013,5 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 93a60774ef6..e4303880927 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2763,3 +2763,4 @@ Currently\ selected\ JStyle\:\ '%0' = Derzeit ausgewählter JStyle\: '%0' Currently\ selected\ CSL\ Style\:\ '%0' = Derzeit ausgewählter CSL-Stil\: '%0' Store\ url\ for\ downloaded\ file=Url für heruntergeladene Datei speichern + diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index ed247e38e89..022f17f0a1b 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -1792,3 +1792,4 @@ Related\ articles=Σχετικά άρθρα + diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index a21e480ccdf..1c796eb65fb 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2485,3 +2485,4 @@ Warning\:\ Failed\ to\ check\ if\ the\ directory\ is\ empty.=Atención\: no se h Warning\:\ The\ selected\ directory\ is\ not\ a\ valid\ directory.=Atención\: el directorio seleccionado no es válido. + diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 60c593eccb0..6baac9fb5eb 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -657,5 +657,6 @@ Auto\ complete\ enabled.=تکمیل خودکار غیرفعال شد. + diff --git a/src/main/resources/l10n/JabRef_fi.properties b/src/main/resources/l10n/JabRef_fi.properties index 748d3d59c95..d79921567de 100644 --- a/src/main/resources/l10n/JabRef_fi.properties +++ b/src/main/resources/l10n/JabRef_fi.properties @@ -601,5 +601,6 @@ Proxy\ requires\ password=Välityspalvelin vaatii salasanan + diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 987502da283..3d713f27f0a 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2798,3 +2798,6 @@ Store\ url\ for\ downloaded\ file=Enregistrer l'URL du fichier téléchargé Compare\ with\ existing\ entry=Comparer avec une entrée existante Library\ Entry=Entrée du fichier Citation\ Entry=Entrée de citation + +File\ Move\ Errors=Erreurs de déplacement de fichier +Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Impossible de déplacer le fichier %0. Veuillez fermer ce fichier et réessayer. diff --git a/src/main/resources/l10n/JabRef_id.properties b/src/main/resources/l10n/JabRef_id.properties index d81d3917b6e..b59b717dadd 100644 --- a/src/main/resources/l10n/JabRef_id.properties +++ b/src/main/resources/l10n/JabRef_id.properties @@ -1556,3 +1556,4 @@ Related\ articles=Artikel terkait + diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 36265d27f3c..81382559097 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2769,3 +2769,6 @@ Store\ url\ for\ downloaded\ file=Memorizza url per il file scaricato Compare\ with\ existing\ entry=Confronta con la voce esistente Library\ Entry=Voce della Libreria Citation\ Entry=Voce della Citazione + +File\ Move\ Errors=Errori di spostamente dei file +Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Impossibile spostare il file %0. Chiudere questo file e riprovare. diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 8dd4a2b101f..e73a20c053f 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2359,3 +2359,4 @@ Related\ articles=関連文献 + diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index 2d216f69340..ef9d996d5d2 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -2193,3 +2193,4 @@ Related\ articles=관련 글 + diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 03c73e912c5..07b9cd04d8f 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2458,3 +2458,4 @@ Related\ articles=Gerelateerde artikelen + diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 9f037dbae81..797de9bac31 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -1124,4 +1124,5 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index e2b1b356b0c..508de30647a 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -1889,3 +1889,6 @@ Store\ url\ for\ downloaded\ file=Przechowuj adres URL pobranego pliku Compare\ with\ existing\ entry=Porównaj z istniejącym wpisem Library\ Entry=Wpis biblioteki Citation\ Entry=Wpis cytatowania + +File\ Move\ Errors=Błąd przenoszenia pliku +Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Nie można przenieść pliku %0. Zamknij ten plik i spróbuj ponownie. diff --git a/src/main/resources/l10n/JabRef_pt.properties b/src/main/resources/l10n/JabRef_pt.properties index 32abbbce0ea..c48f6b7dcaf 100644 --- a/src/main/resources/l10n/JabRef_pt.properties +++ b/src/main/resources/l10n/JabRef_pt.properties @@ -1373,3 +1373,4 @@ Related\ articles=Artigos relacionados + diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 8ff637d5d3f..70de2417b98 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2790,3 +2790,4 @@ Store\ url\ for\ downloaded\ file=Salvar url do arquivo baixado Compare\ with\ existing\ entry=Comparar com entrada existente Library\ Entry=Registro de Biblioteca Citation\ Entry=Chave de citação + diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 8170dc01861..35d823f31c3 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2327,3 +2327,4 @@ Related\ articles=Связанные статьи + diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 9898920800f..5f4e23f1788 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -1520,3 +1520,4 @@ Warning\:\ Failed\ to\ check\ if\ the\ directory\ is\ empty.=Varning\: Det gick Warning\:\ The\ selected\ directory\ is\ not\ a\ valid\ directory.=Varning\: Den valda mappen är inte en giltig mapp. + diff --git a/src/main/resources/l10n/JabRef_tl.properties b/src/main/resources/l10n/JabRef_tl.properties index 1be3c7ae6c0..8c8201598b9 100644 --- a/src/main/resources/l10n/JabRef_tl.properties +++ b/src/main/resources/l10n/JabRef_tl.properties @@ -1253,3 +1253,4 @@ Related\ articles=Kaugnay na mga artikulo + diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 9ae2b65f140..d2764f53f6b 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2512,3 +2512,4 @@ More\ options...=Daha fazla seçenekler... + diff --git a/src/main/resources/l10n/JabRef_uk.properties b/src/main/resources/l10n/JabRef_uk.properties index 5aecfd04e31..d06e34795db 100644 --- a/src/main/resources/l10n/JabRef_uk.properties +++ b/src/main/resources/l10n/JabRef_uk.properties @@ -668,5 +668,6 @@ Proxy\ requires\ password=Потрібен пароль проксі-серве + diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 00a641ab526..38240e5c7f2 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -1058,4 +1058,5 @@ Path\ to\ %0=Đường dẫn đến %0 + diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 06466b53a4c..368355c08a4 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -2445,3 +2445,4 @@ Related\ articles=相关文章 + diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties index 9e502d9027c..7dabbae2b0c 100644 --- a/src/main/resources/l10n/JabRef_zh_TW.properties +++ b/src/main/resources/l10n/JabRef_zh_TW.properties @@ -1046,3 +1046,4 @@ Related\ articles=相關文章 + From 9eb13c31cdc78aafaa6f064e513b26592b2a3451 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 26 Oct 2024 22:00:34 +0200 Subject: [PATCH 002/108] Add test that %0 is kept (#12093) Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- src/test/java/org/jabref/logic/l10n/LocalizationTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/org/jabref/logic/l10n/LocalizationTest.java b/src/test/java/org/jabref/logic/l10n/LocalizationTest.java index c373608566c..12ca0737a5c 100644 --- a/src/test/java/org/jabref/logic/l10n/LocalizationTest.java +++ b/src/test/java/org/jabref/logic/l10n/LocalizationTest.java @@ -70,4 +70,10 @@ void unknownTranslation() { void unsetLanguageTranslation() { assertEquals("Groups", Localization.lang("Groups")); } + + @Test + void placeholderIsKeptWhenNoParameter() { + // This behavior is required when %0 should be transformed to a hyperlink in the UI. + assertEquals("Groups %0", Localization.lang("Groups %0")); + } } From 95f7db287904af8447c4d64a8ad1e2ad9bb42efc Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 26 Oct 2024 22:50:15 +0200 Subject: [PATCH 003/108] Refine PULL_REQUEST_TEMPLATE.md (#12094) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c52221619ed..a01fad86a7a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,7 @@ Link the issue that will be closed, e.g., "Closes #333". If your PR closes a kop --> - [x] I own the copyright of the code submitted and I licence it under the [MIT license](https://github.com/JabRef/jabref/blob/main/LICENSE) -- [ ] Change in `CHANGELOG.md` described in a way that is understandable for the average user (if applicable) +- [ ] Change in `CHANGELOG.md` described in a way that is understandable for the average user (if change is visible to the user) - [ ] Tests created for changes (if applicable) - [ ] Manually tested changed features in running JabRef (always required) - [ ] Screenshots added in PR description (for UI changes) From b0352699cd4e556b4dc0e7b5c01e13702f12a89a Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 26 Oct 2024 23:43:33 +0200 Subject: [PATCH 004/108] =?UTF-8?q?Add=20project=20move=20after=20manually?= =?UTF-8?q?=20putting=20"=F0=9F=93=8D=20Assigned"=20label=20(#12096)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/on-labeled-issue.yml | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on-labeled-issue.yml b/.github/workflows/on-labeled-issue.yml index 8839eca84b7..8cc260e8939 100644 --- a/.github/workflows/on-labeled-issue.yml +++ b/.github/workflows/on-labeled-issue.yml @@ -6,6 +6,34 @@ on: - labeled jobs: + Assigned: + # Triggered when manually assigned the label "📍 Assigned" to trigger the automatic unassignment after 30 days + name: "📍 Assigned" + if: ${{ github.event.label.name == '📍 Assigned' }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Move Issue to "Free to take" Column in "Candidates for University Projects" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/3" + target-labels: "📍 Assigned" + target-column: "Free to take" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + - name: Move Issue to "Free to take" Column in "Good First Issues" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/5" + target-labels: "📍 Assigned" + target-column: "Free to take" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true FirstTimeCodeContribution: if: ${{ github.event.label.name == 'FirstTimeCodeContribution' }} runs-on: ubuntu-latest @@ -35,7 +63,7 @@ jobs: with: github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} project-url: "https://github.com/orgs/JabRef/projects/3" - target-labels: "📍 Assigned" + target-labels: "FirstTimeCodeContribution" target-column: "Assigned" ignored-columns: "" default-column: "Free to take" @@ -45,7 +73,7 @@ jobs: with: github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} project-url: "https://github.com/orgs/JabRef/projects/5" - target-labels: "📍 Assigned" + target-labels: "FirstTimeCodeContribution" target-column: "Assigned" ignored-columns: "" default-column: "Free to take" From 33fba4cccfa69b1849bf3eed32f3db0d32fd75d0 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 01:26:03 +0200 Subject: [PATCH 005/108] Try to fix distinction between PRs from us and forks --- .github/workflows/pr-comment.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index f8ad213aea3..55384f04c53 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -37,22 +37,22 @@ jobs: - uses: actions/checkout@v4 - name: Determine owner if: ${{ steps.read-pr_number.outputs.pr_number != '' }} - id: owner + id: isCrossRepository run: | - owner=$(gh pr view $pr_number --json headRepositoryOwner --jq '.headRepositoryOwner') - echo "Got owner $owner" - echo owner=$owner >> $GITHUB_OUTPUT + isCrossRepository=$(gh pr view $pr_number --json isCrossRepository --jq '.isCrossRepository') + echo "Got isCrossRepository $isCrossRepository" + echo isCrossRepository=$isCrossRepository >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ github.token }} - name: Checkout - if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.owner.owner != 'JabRef') }} + if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.isCrossRepository.isCrossRepository == 'true') }} uses: actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' token: ${{ secrets.GITHUB_TOKEN }} - name: jbang - if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.owner.owner != 'JabRef') }} + if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.isCrossRepository.isCrossRepository == 'true') }} uses: jbangdev/jbang-action@v0.119.0 with: script: ghprcomment@koppor/ghprcomment From 08aa10c8c9521cca9f357128986f754784c7a763 Mon Sep 17 00:00:00 2001 From: Ricky-Du <141602008+u7465990@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:32:25 +1100 Subject: [PATCH 006/108] Add VScode (#12089) * First version of the double click issue * updated codes * add the vscode to open the tex file * solve csl-styles * solve csl-locales * solve buildres/abbrv.jabref.org * Add the default path for VScode * ADD a todo for issue6775 --- .../java/org/jabref/gui/icon/IconTheme.java | 1 + .../gui/preferences/JabRefGuiPreferences.java | 5 +++ .../jabref/gui/push/PushToApplications.java | 4 +- .../org/jabref/gui/push/PushToVScode.java | 38 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/jabref/gui/push/PushToVScode.java diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index aa6cff98462..3788b551b18 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -283,6 +283,7 @@ public enum JabRefIcons implements JabRefIcon { APPLICATION_SUBLIMETEXT(JabRefMaterialDesignIcon.SUBLIME_TEXT), APPLICATION_TEXSHOP(JabRefMaterialDesignIcon.TEXSHOP), APPLICATION_TEXWORS(JabRefMaterialDesignIcon.TEXWORKS), + APPLICATION_VSCODE(JabRefMaterialDesignIcon.VSCODE), KEY_BINDINGS(MaterialDesignK.KEYBOARD), FIND_DUPLICATES(MaterialDesignC.CODE_EQUAL), CONNECT_DB(MaterialDesignC.CLOUD_UPLOAD), diff --git a/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java b/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java index 07bb423173b..2ab75d18cb7 100644 --- a/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java +++ b/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java @@ -139,6 +139,7 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre private static final String PUSH_VIM_SERVER = "vimServer"; private static final String PUSH_VIM = "vim"; private static final String PUSH_SUBLIME_TEXT_PATH = "sublimeTextPath"; + private static final String PUSH_VSCODE_PATH = "VScodePath"; // endregion // region NameDisplayPreferences @@ -360,6 +361,7 @@ private JabRefGuiPreferences() { defaults.put(PUSH_VIM, "vim"); defaults.put(PUSH_VIM_SERVER, "vim"); defaults.put(PUSH_EMACS_ADDITIONAL_PARAMETERS, "-n -e"); + defaults.put(PUSH_VSCODE_PATH, OS.detectProgramPath("Code", "Microsoft VS Code")); if (OS.OS_X) { defaults.put(PUSH_EMACS_PATH, "emacsclient"); @@ -937,6 +939,7 @@ public PushToApplicationPreferences getPushToApplicationPreferences() { applicationCommands.put(PushToApplications.VIM, getEmptyIsDefault(PUSH_VIM)); applicationCommands.put(PushToApplications.WIN_EDT, getEmptyIsDefault(PUSH_WINEDT_PATH)); applicationCommands.put(PushToApplications.SUBLIME_TEXT, getEmptyIsDefault(PUSH_SUBLIME_TEXT_PATH)); + applicationCommands.put(PushToApplications.VSCODE, getEmptyIsDefault(PUSH_VSCODE_PATH)); pushToApplicationPreferences = new PushToApplicationPreferences( get(PUSH_TO_APPLICATION), @@ -971,6 +974,8 @@ private void storePushToApplicationPath(Map commandPair) { put(PUSH_WINEDT_PATH, value); case PushToApplications.SUBLIME_TEXT -> put(PUSH_SUBLIME_TEXT_PATH, value); + case PushToApplications.VSCODE -> + put(PUSH_VSCODE_PATH, value); } }); } diff --git a/src/main/java/org/jabref/gui/push/PushToApplications.java b/src/main/java/org/jabref/gui/push/PushToApplications.java index 08b551674a7..0a277e471a2 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplications.java +++ b/src/main/java/org/jabref/gui/push/PushToApplications.java @@ -18,6 +18,7 @@ public class PushToApplications { public static final String WIN_EDT = "WinEdt"; public static final String SUBLIME_TEXT = "Sublime Text"; public static final String TEXSHOP = "TeXShop"; + public static final String VSCODE = "VScode"; private static final List APPLICATIONS = new ArrayList<>(); @@ -38,7 +39,8 @@ public static List getAllApplications(DialogService dialogSer new PushToTeXworks(dialogService, preferences), new PushToVim(dialogService, preferences), new PushToWinEdt(dialogService, preferences), - new PushToTexShop(dialogService, preferences))); + new PushToTexShop(dialogService, preferences), + new PushToVScode(dialogService, preferences))); return APPLICATIONS; } diff --git a/src/main/java/org/jabref/gui/push/PushToVScode.java b/src/main/java/org/jabref/gui/push/PushToVScode.java new file mode 100644 index 00000000000..6a40a1edf45 --- /dev/null +++ b/src/main/java/org/jabref/gui/push/PushToVScode.java @@ -0,0 +1,38 @@ +package org.jabref.gui.push; + +import java.nio.file.Path; + +import org.jabref.gui.DialogService; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.preferences.GuiPreferences; + +public class PushToVScode extends AbstractPushToApplication { + + public static final String NAME = PushToApplications.VSCODE; + + public PushToVScode(DialogService dialogService, GuiPreferences preferences) { + super(dialogService, preferences); + } + + @Override + public String getDisplayName() { + return NAME; + } + + @Override + public JabRefIcon getApplicationIcon() { + return IconTheme.JabRefIcons.APPLICATION_VSCODE; + } + + @Override + protected String[] getCommandLine(String keyString) { + // TODO - Implementing this will fix https://github.com/JabRef/jabref/issues/6775 + return new String[] {commandPath}; + } + + @Override + public String[] jumpToLineCommandlineArguments(Path fileName, int line, int column) { + return new String[] {commandPath, "--g", "%s:%s:%s".formatted(fileName.toString(), line, column)}; + } +} From 61d342ad6bcb980e1680b2c897bfcd48792f0a35 Mon Sep 17 00:00:00 2001 From: "Baron.Mac" Date: Sun, 27 Oct 2024 10:45:30 +1100 Subject: [PATCH 007/108] Follow-up #12035 and #12068: Show an informative progress indicator when dragging and dropping PDF files to JabRef (#12072) * Updated changes following up for pull requests #12035 and #12068 * Updated CHANGELOG.md to display correct pull request * Updated StringUtil.shortenFileName to handle maxLength null case and added unit tests for maxLength null cases * Updated CHANGELOG.md to follow format and be more concise * Moved shortenFileName() to FileUtil * Refactored FileUtil.shortenFileName to better use of maxLength and Updated progress messages for Importing, Indexing files. --------- Co-authored-by: Baron Mac --- CHANGELOG.md | 1 + .../gui/externalfiles/ImportHandler.java | 18 ++++-- .../indexing/DefaultLinkedFilesIndexer.java | 7 ++- .../org/jabref/logic/util/io/FileUtil.java | 62 +++++++++++++++++++ src/main/resources/l10n/JabRef_en.properties | 7 ++- .../jabref/logic/util/io/FileUtilTest.java | 25 ++++++++ 6 files changed, 108 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc911099c31..7ff1b63f122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added automatic browser extension install on Windows for Chrome and Edge. [#6076](https://github.com/JabRef/jabref/issues/6076) - We added a search bar for filtering keyboard shortcuts. [#11686](https://github.com/JabRef/jabref/issues/11686) - By double clicking on a local citation in the Citation Relations Tab you can now jump the linked entry. [#11955](https://github.com/JabRef/jabref/pull/11955) +- We use the menu icon for background tasks as a progress indicator to visualise an import's progress when dragging and dropping several PDF files into the main table. [#12072](https://github.com/JabRef/jabref/pull/12072) ### Changed diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index 43038d286f5..9be3e860390 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -102,6 +102,7 @@ public BackgroundTask> importFilesInBackgro return new BackgroundTask<>() { private int counter; private final List results = new ArrayList<>(); + private final List allEntriesToAdd = new ArrayList<>(); @Override public List call() { @@ -115,8 +116,13 @@ public List call() { } UiTaskExecutor.runInJavaFXThread(() -> { - updateMessage(Localization.lang("Processing file %0", file.getFileName())); - updateProgress(counter, files.size() - 1d); + setTitle(Localization.lang("Importing files into %1 | %2 of %0 file(s) processed.", + files.size(), + bibDatabaseContext.getDatabasePath().map(path -> path.getFileName().toString()).orElse(Localization.lang("untitled")), + counter)); + updateMessage(Localization.lang("Processing %0", FileUtil.shortenFileName(file.getFileName().toString(), 68))); + updateProgress(counter, files.size()); + showToUser(true); }); try { @@ -168,10 +174,7 @@ public List call() { UiTaskExecutor.runInJavaFXThread(() -> updateMessage(Localization.lang("Error"))); } - - // We need to run the actual import on the FX Thread, otherwise we will get some deadlocks with the UIThreadList - // That method does a clone() on each entry - UiTaskExecutor.runInJavaFXThread(() -> importEntries(entriesToAdd)); + allEntriesToAdd.addAll(entriesToAdd); ce.addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entriesToAdd)); ce.end(); @@ -180,6 +183,9 @@ public List call() { counter++; } + // We need to run the actual import on the FX Thread, otherwise we will get some deadlocks with the UIThreadList + // That method does a clone() on each entry + UiTaskExecutor.runInJavaFXThread(() -> importEntries(allEntriesToAdd)); return results; } diff --git a/src/main/java/org/jabref/logic/search/indexing/DefaultLinkedFilesIndexer.java b/src/main/java/org/jabref/logic/search/indexing/DefaultLinkedFilesIndexer.java index 50683c8d8f6..bf7386ddef3 100644 --- a/src/main/java/org/jabref/logic/search/indexing/DefaultLinkedFilesIndexer.java +++ b/src/main/java/org/jabref/logic/search/indexing/DefaultLinkedFilesIndexer.java @@ -23,6 +23,7 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; @@ -133,8 +134,6 @@ private void addToIndex(Map> linkedFiles, BackgroundTas return; } - task.setTitle(Localization.lang("Indexing PDF files for %0", libraryName)); - task.showToUser(true); LOGGER.debug("Adding {} files to index", linkedFiles.size()); int i = 1; for (Map.Entry> entry : linkedFiles.entrySet()) { @@ -143,8 +142,10 @@ private void addToIndex(Map> linkedFiles, BackgroundTas return; } addToIndex(entry.getKey(), entry.getValue().getKey(), entry.getValue().getValue()); + task.setTitle(Localization.lang("Indexing files for %1 | %2 of %0 file(s) indexed.", linkedFiles.size(), libraryName, i)); task.updateProgress(i, linkedFiles.size()); - task.updateMessage(Localization.lang("Indexing %0. %1 of %2 files added to the index.", entry.getValue().getValue().getFileName(), i, linkedFiles.size())); + task.updateMessage(Localization.lang("Indexing %0", FileUtil.shortenFileName(entry.getValue().getValue().getFileName().toString(), 68))); + task.showToUser(true); i++; } LOGGER.debug("Added {} files to index", linkedFiles.size()); diff --git a/src/main/java/org/jabref/logic/util/io/FileUtil.java b/src/main/java/org/jabref/logic/util/io/FileUtil.java index 749b7d56cd4..3240e64034f 100644 --- a/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -45,6 +45,8 @@ public class FileUtil { public static final int MAXIMUM_FILE_NAME_LENGTH = 255; private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class); + private static final String ELLIPSIS = "..."; + private static final int ELLIPSIS_LENGTH = ELLIPSIS.length(); /** * MUST ALWAYS BE A SORTED ARRAY because it is used in a binary search @@ -523,6 +525,66 @@ public static boolean detectBadFileName(String fileName) { return false; } + /** + * Shorten a given file name in the middle of the name using ellipsis. Example: verylongfilenameisthis.pdf + * with maxLength = 20 is shortened into verylo...isthis.pdf + * + * @param fileName the given file name to be shortened + * @param maxLength the maximum number of characters in the string after shortening (including the extension) + * @return the original fileName if fileName.length() <= maxLength. Otherwise, a shortened fileName + */ + public static String shortenFileName(String fileName, Integer maxLength) { + if (fileName == null || maxLength == null || maxLength < ELLIPSIS_LENGTH) { + return ""; + } + + if (fileName.length() <= maxLength) { + return fileName; + } + + String name; + String extension; + + extension = FileUtil.getFileExtension(fileName).map(fileExtension -> '.' + fileExtension).orElse(""); + if (extension.isEmpty()) { + name = fileName; + } else { + name = fileName.substring(0, fileName.length() - extension.length()); + } + + int totalNeededLength = ELLIPSIS_LENGTH + extension.length(); + if (maxLength <= totalNeededLength) { + return fileName.substring(0, maxLength - ELLIPSIS_LENGTH) + ELLIPSIS; + } + + int charsForName = maxLength - totalNeededLength; + if (charsForName <= 0) { + return ELLIPSIS + extension; + } + + int numCharsBeforeEllipsis; + int numCharsAfterEllipsis; + if (charsForName == 1) { + numCharsBeforeEllipsis = 1; + numCharsAfterEllipsis = 0; + } else { + // Allow the front part to have the extra in odd cases + numCharsBeforeEllipsis = (charsForName + 1) / 2; + numCharsAfterEllipsis = charsForName / 2; + } + + numCharsBeforeEllipsis = Math.min(numCharsBeforeEllipsis, name.length()); + numCharsAfterEllipsis = Math.min(numCharsAfterEllipsis, name.length() - numCharsBeforeEllipsis); + + StringBuilder result = new StringBuilder(); + result.append(name, 0, numCharsBeforeEllipsis) + .append(ELLIPSIS) + .append(name.substring(name.length() - numCharsAfterEllipsis)) + .append(extension); + + return result.toString(); + } + public static boolean isCharLegal(char c) { return Arrays.binarySearch(ILLEGAL_CHARS, c) < 0; } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 7505818e8fe..94119350460 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -319,7 +319,8 @@ Extract\ References\ (online)=Extract References (online) Processing...=Processing... Processing\ "%0"...=Processing "%0"... Processing\ PDF(s)=Processing PDF(s) -Processing\ file\ %0=Processing file %0 +Processing\ %0=Processing %0 +Importing\ files\ into\ %1\ |\ %2\ of\ %0\ file(s)\ processed.=Importing files into %1 | %2 of %0 file(s) processed. Processing\ a\ large\ number\ of\ files=Processing a large number of files You\ are\ about\ to\ process\ %0\ files.\ Continue?=You are about to process %0 files. Continue? @@ -490,10 +491,10 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Indepe I\ Agree=I Agree Indexing\ bib\ fields\ for\ %0=Indexing bib fields for %0 -Indexing\ PDF\ files\ for\ %0=Indexing PDF files for %0 +Indexing\ %0=Indexing %0 +Indexing\ files\ for\ %1\ |\ %2\ of\ %0\ file(s)\ indexed.=Indexing files for %1 | %2 of %0 file(s) indexed. %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 of %1 entries added to the index. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 of %1 entries removed from the index. -Indexing\ %0.\ %1\ of\ %2\ files\ added\ to\ the\ index.=Indexing %0. %1 of %2 files added to the index. Removing\ entries\ from\ index\ for\ %0=Removing entries from index for %0 Invalid\ URL=Invalid URL diff --git a/src/test/java/org/jabref/logic/util/io/FileUtilTest.java b/src/test/java/org/jabref/logic/util/io/FileUtilTest.java index 416b898429a..e09b5e89b8e 100644 --- a/src/test/java/org/jabref/logic/util/io/FileUtilTest.java +++ b/src/test/java/org/jabref/logic/util/io/FileUtilTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; @@ -477,4 +478,28 @@ void legalPaths(String fileName) { void illegalPaths(String fileName) { assertTrue(FileUtil.detectBadFileName(fileName)); } + + @ParameterizedTest + @CsvSource({ + "'' , , ", + "'' , , -3", + "'' , , 0", + "'' , , 3", + "'' , , 5", + "'' , , 10", + "'' , thisisatestfile.pdf , ", + "'' , thisisatestfile.pdf , -5", + "'' , thisisatestfile.pdf , 0", + "... , thisisatestfile.pdf , 3", + "th... , thisisatestfile.pdf , 5", + "th...e.pdf , thisisatestfile.pdf , 10", + "thisisatestfile.pdf , thisisatestfile.pdf , 20", + "lo... , longfilename.extremelylongextension , 5", + "longfil... , longfilename.extremelylongextension , 10", + "longfilename.extr... , longfilename.extremelylongextension , 20", + "lo...me.extremelylongextension , longfilename.extremelylongextension , 30", + }) + void shortenFileName(String expected, String fileName, Integer maxLength) { + assertEquals(expected, FileUtil.shortenFileName(fileName, maxLength)); + } } From 734ceece8bc2d8ad664d8e7b49315ba8e9512787 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 01:49:45 +0200 Subject: [PATCH 008/108] Limit some workflows to main repository (#12097) --- .github/workflows/assign-issue.yml | 1 + .github/workflows/check-links.yml | 1 + .github/workflows/cleanup-pr.yml | 16 +--------------- .github/workflows/deployment-arm64.yml | 1 + .github/workflows/deployment-jdk-ea.yml | 1 + .github/workflows/gource.yml | 1 + .github/workflows/on-labeled-issue.yml | 12 ++++++------ .github/workflows/on-labeled-pr.yml | 2 +- .github/workflows/on-unlabeled-issue.yml | 2 +- .github/workflows/pr-comment.yml | 2 +- .github/workflows/update-gradle-wrapper.yml | 1 + 11 files changed, 16 insertions(+), 24 deletions(-) diff --git a/.github/workflows/assign-issue.yml b/.github/workflows/assign-issue.yml index e88f6b762d2..a4c8899c249 100644 --- a/.github/workflows/assign-issue.yml +++ b/.github/workflows/assign-issue.yml @@ -9,6 +9,7 @@ on: jobs: assign: + if: github.repository_owner == 'JabRef' runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 67881841c46..ce4c607f16c 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -18,6 +18,7 @@ concurrency: jobs: lychee: + if: github.repository_owner == 'JabRef' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/cleanup-pr.yml b/.github/workflows/cleanup-pr.yml index bd38e11ea16..ecd3936ef52 100644 --- a/.github/workflows/cleanup-pr.yml +++ b/.github/workflows/cleanup-pr.yml @@ -7,27 +7,14 @@ on: jobs: cleanup: runs-on: ubuntu-latest + if: github.repository_owner == 'JabRef' steps: - name: Cancel deployment run uses: styfle/cancel-workflow-action@0.12.1 with: ignore_sha: true workflow_id: 9813 # workflow "Deployment" - - name: Check secrets presence - id: checksecrets - shell: bash - run: | - if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then - echo "secretspresent=NO" >> $GITHUB_OUTPUT - echo "❌ Secret BUILDJABREFPRIVATEKEY not present" - else - echo "secretspresent=YES" >> $GITHUB_OUTPUT - echo "✔️ Secret BUILDJABREFPRIVATEKEY present" - fi - env: - BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - name: Delete folder on builds.jabref.org - if: steps.checksecrets.outputs.secretspresent == 'YES' uses: appleboy/ssh-action@v1.1.0 with: script: rm -rf /var/www/builds.jabref.org/www/pull/${{ github.event.pull_request.number }} || true @@ -36,7 +23,6 @@ jobs: username: jrrsync key: ${{ secrets.buildJabRefPrivateKey }} - name: Update PR comment - if: steps.checksecrets.outputs.secretspresent == 'YES' uses: thollander/actions-comment-pull-request@v3 with: comment-tag: download-link diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index cd360c1f474..231fb3f15cb 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -34,6 +34,7 @@ concurrency: jobs: build: + if: github.repository_owner == 'JabRef' strategy: fail-fast: false matrix: diff --git a/.github/workflows/deployment-jdk-ea.yml b/.github/workflows/deployment-jdk-ea.yml index 2bc7390f568..e956241d703 100644 --- a/.github/workflows/deployment-jdk-ea.yml +++ b/.github/workflows/deployment-jdk-ea.yml @@ -32,6 +32,7 @@ concurrency: jobs: build: + if: github.repository_owner == 'JabRef' strategy: fail-fast: false matrix: diff --git a/.github/workflows/gource.yml b/.github/workflows/gource.yml index cfdbc44d6e0..3bccc1f3d14 100644 --- a/.github/workflows/gource.yml +++ b/.github/workflows/gource.yml @@ -15,6 +15,7 @@ concurrency: jobs: action: + if: github.repository_owner == 'JabRef' runs-on: ubuntu-latest steps: - name: 'Checkout' diff --git a/.github/workflows/on-labeled-issue.yml b/.github/workflows/on-labeled-issue.yml index 8cc260e8939..93e65e1cf66 100644 --- a/.github/workflows/on-labeled-issue.yml +++ b/.github/workflows/on-labeled-issue.yml @@ -9,7 +9,7 @@ jobs: Assigned: # Triggered when manually assigned the label "📍 Assigned" to trigger the automatic unassignment after 30 days name: "📍 Assigned" - if: ${{ github.event.label.name == '📍 Assigned' }} + if: ${{ github.event.label.name == '📍 Assigned' && github.repository_owner == 'JabRef' }} runs-on: ubuntu-latest permissions: issues: write @@ -35,7 +35,7 @@ jobs: default-column: "Free to take" skip-if-not-in-project: true FirstTimeCodeContribution: - if: ${{ github.event.label.name == 'FirstTimeCodeContribution' }} + if: ${{ github.event.label.name == 'FirstTimeCodeContribution' && github.repository_owner == 'JabRef' }} runs-on: ubuntu-latest permissions: issues: write @@ -80,7 +80,7 @@ jobs: skip-if-not-in-project: true good-first-issue: name: "good first issue" - if: "${{ github.event.label.name == 'good first issue' }}" + if: "${{ github.event.label.name == 'good first issue' && github.repository_owner == 'JabRef' }}" runs-on: ubuntu-latest steps: - name: "good first issue" @@ -90,7 +90,7 @@ jobs: ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") gh project item-add 5 --owner JabRef --url $ISSUE_URL needs-refinement: - if: github.event.label.name == 'needs-refinement' + if: github.event.label.name == 'needs-refinement' && github.repository_owner == 'JabRef' runs-on: ubuntu-latest steps: - name: needs-refinement @@ -101,7 +101,7 @@ jobs: gh project item-add 15 --owner JabRef --url $ISSUE_URL status-freeze: name: "status: freeze" - if: "${{ github.event.label.name == 'status: freeze' }}" + if: "${{ github.event.label.name == 'status: freeze' && github.repository_owner == 'JabRef' }}" runs-on: ubuntu-latest steps: - name: "status: freeze" @@ -111,7 +111,7 @@ jobs: ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") gh project item-add 9 --owner JabRef --url $ISSUE_URL ui: - if: "${{ github.event.label.name == 'ui' }}" + if: "${{ github.event.label.name == 'ui' && github.repository_owner == 'JabRef' }}" runs-on: ubuntu-latest steps: - name: ui diff --git a/.github/workflows/on-labeled-pr.yml b/.github/workflows/on-labeled-pr.yml index 4ceb3844fe7..2e64d79f1f7 100644 --- a/.github/workflows/on-labeled-pr.yml +++ b/.github/workflows/on-labeled-pr.yml @@ -8,7 +8,7 @@ on: jobs: automerge: name: Auto Merge - if: "${{ github.event.label.name == 'automerge' }}" + if: "${{ github.event.label.name == 'automerge' && github.repository_owner == 'JabRef' }}" runs-on: ubuntu-latest steps: - name: Approve PR diff --git a/.github/workflows/on-unlabeled-issue.yml b/.github/workflows/on-unlabeled-issue.yml index 9c5870066ca..64872289b3d 100644 --- a/.github/workflows/on-unlabeled-issue.yml +++ b/.github/workflows/on-unlabeled-issue.yml @@ -7,7 +7,7 @@ on: jobs: FirstTimeCodeContribution_or_Assigned: - if: ${{ (github.event.label.name == 'FirstTimeCodeContribution') || (github.event.label.name == '📍 Assigned') }} + if: ${{ ((github.event.label.name == 'FirstTimeCodeContribution') || (github.event.label.name == '📍 Assigned')) && github.repository_owner == 'JabRef' }} runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index 55384f04c53..b93face4b88 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -14,7 +14,7 @@ on: jobs: comment: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-a-workflow-based-on-the-conclusion-of-another-workflow - if: ${{ github.event.workflow_run.conclusion == 'failure' }} + if: ${{ github.event.workflow_run.conclusion == 'failure' && (github.repository_owner == 'JabRef') }} runs-on: ubuntu-latest permissions: actions: read diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index 826a211b195..f65338cf884 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -7,6 +7,7 @@ on: jobs: update-gradle-wrapper: + if: github.repository_owner == 'JabRef' runs-on: ubuntu-latest steps: From f0d8632c5410813422b79bb420ed682fed7cb5a6 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 02:18:52 +0200 Subject: [PATCH 009/108] Fix reading PR number --- .github/workflows/pr-comment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index b93face4b88..e6fe90e40f9 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -44,6 +44,7 @@ jobs: echo isCrossRepository=$isCrossRepository >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ github.token }} + pr_number: ${{ steps.read-pr_number.outputs.pr_number }} - name: Checkout if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.isCrossRepository.isCrossRepository == 'true') }} uses: actions/checkout@v4 From ea7c2aec8f5a2709de432590805b4d1287bf4fcb Mon Sep 17 00:00:00 2001 From: MadDingzhen <143514319+MadDingzhen@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:23:24 +1100 Subject: [PATCH 010/108] Make Identifier paste work with Unicode REPLACEMENT CHARACTER (#12083) * Solved "Identifier paste should work with Unicode REPLACEMENT CHARACTER #11986" * Style fix * Added a CHANGELOG.md entry --- CHANGELOG.md | 1 + src/main/java/org/jabref/model/entry/identifier/DOI.java | 1 + .../java/org/jabref/model/entry/identifier/DOITest.java | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff1b63f122..0c7d259a82e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where it was not possible to select selecting content of other user's comments.[#11106](https://github.com/JabRef/jabref/issues/11106) - We fixed an issue where web search preferences "Custom API key" table modifications not discarded. [#11925](https://github.com/JabRef/jabref/issues/11925) - We fixed an issue where trying to open a library from a failed mounted directory on Mac would cause an error. [#10548](https://github.com/JabRef/jabref/issues/10548) +- We fixed an issue where identifier paste couldn't work with Unicode REPLACEMENT CHARACTER. [#11986](https://github.com/JabRef/jabref/issues/11986) ### Removed diff --git a/src/main/java/org/jabref/model/entry/identifier/DOI.java b/src/main/java/org/jabref/model/entry/identifier/DOI.java index fb457522809..58e5c59c03b 100644 --- a/src/main/java/org/jabref/model/entry/identifier/DOI.java +++ b/src/main/java/org/jabref/model/entry/identifier/DOI.java @@ -201,6 +201,7 @@ public static boolean isValid(String doi) { */ public static Optional findInText(String text) { Optional result = Optional.empty(); + text = text.replaceAll("[�]", ""); Matcher matcher = FIND_DOI_PATT.matcher(text); if (matcher.find()) { diff --git a/src/test/java/org/jabref/model/entry/identifier/DOITest.java b/src/test/java/org/jabref/model/entry/identifier/DOITest.java index 382dea3622b..51c9b8f9d3a 100644 --- a/src/test/java/org/jabref/model/entry/identifier/DOITest.java +++ b/src/test/java/org/jabref/model/entry/identifier/DOITest.java @@ -229,7 +229,12 @@ private static Stream testData() { // findDoiWithSpecialCharactersInText Arguments.of("10.1175/1520-0493(2002)130%3C1913:EDAWPO%3E2.0.CO;2", - DOI.findInText("https://doi.org/10.1175/1520-0493(2002)130%3C1913:EDAWPO%3E2.0.CO;2").get().asString()) + DOI.findInText("https://doi.org/10.1175/1520-0493(2002)130%3C1913:EDAWPO%3E2.0.CO;2").get().asString()), + + // Test with Unicode replacement character + Arguments.of("10.1006/jmbi.1998.2354", DOI.findInText("other stuff �10.1006/jmbi.1998.2354").get().asString()), + Arguments.of("10.1006/jmbi.1998.2354", DOI.findInText("other stuff 10.1006/jmbi.1998.2354�").get().asString()), + Arguments.of("10/gf4gqc", DOI.findInText("other stuff �doi�:10�/gf4����gqc�").get().asString()) ); } From 598d269589905d61e2023f96cca2d75733f9d1b3 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 02:39:51 +0200 Subject: [PATCH 011/108] Fix cleanup for PRs from forks --- .github/workflows/cleanup-pr.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cleanup-pr.yml b/.github/workflows/cleanup-pr.yml index ecd3936ef52..a4caf50b257 100644 --- a/.github/workflows/cleanup-pr.yml +++ b/.github/workflows/cleanup-pr.yml @@ -6,15 +6,29 @@ on: jobs: cleanup: - runs-on: ubuntu-latest if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest steps: - name: Cancel deployment run uses: styfle/cancel-workflow-action@0.12.1 with: ignore_sha: true workflow_id: 9813 # workflow "Deployment" + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - name: Delete folder on builds.jabref.org + if: steps.checksecrets.outputs.secretspresent == 'YES' uses: appleboy/ssh-action@v1.1.0 with: script: rm -rf /var/www/builds.jabref.org/www/pull/${{ github.event.pull_request.number }} || true @@ -23,6 +37,7 @@ jobs: username: jrrsync key: ${{ secrets.buildJabRefPrivateKey }} - name: Update PR comment + if: steps.checksecrets.outputs.secretspresent == 'YES' uses: thollander/actions-comment-pull-request@v3 with: comment-tag: download-link From e9a6c5a6f7cb11df7b3ec161a2f66d9650f036c0 Mon Sep 17 00:00:00 2001 From: leaf-soba Date: Sun, 27 Oct 2024 09:41:31 +0900 Subject: [PATCH 012/108] replace most of Deprecated APIs (#12045) * fix CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN fix CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN Deprecated issue in comment * fix override deprecated method fix override deprecated method in IconValidationDecorator * fix Deprecated ANTLRInputStream replace ANTLRInputStream to CharStreams * fix Deprecated BOMInputStream use builder instead. * fix wrong CharStreams method fromFileName -> fromString * fix requested changes 1.fix requested changes 2.add more unit test to support change in IconValidationDecorator * fix last Deprecated API remove Deprecated setGroup * remove unnecessary IOException and add comment remove unnecessary IOException and add comment * revert wrong change revert wrong change with added comment * merge from main merge from main * Discard changes to src/main/java/org/jabref/logic/database/DatabaseMerger.java --------- Co-authored-by: Oliver Kopp --- .../SmartConstrainedResizePolicy.java | 1 + .../gui/util/IconValidationDecorator.java | 14 ++---- .../fileformat/CitaviXmlImporter.java | 9 ++-- .../jabref/model/groups/GroupTreeNode.java | 2 - .../util/IconValidationDecoratorTest.java | 50 +++++++++++++++++++ 5 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 src/test/java/org/jabref/logic/util/IconValidationDecoratorTest.java diff --git a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 4096455c765..74a6345cc71 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -17,6 +17,7 @@ * This resize policy is almost the same as {@link TableView#CONSTRAINED_RESIZE_POLICY} * We make sure that the width of all columns sums up to the total width of the table. * However, in contrast to {@link TableView#CONSTRAINED_RESIZE_POLICY} we size the columns initially by their preferred width. + * Although {@link TableView#CONSTRAINED_RESIZE_POLICY} is deprecated, this policy maintains a similar resizing behavior. */ public class SmartConstrainedResizePolicy implements Callback { diff --git a/src/main/java/org/jabref/gui/util/IconValidationDecorator.java b/src/main/java/org/jabref/gui/util/IconValidationDecorator.java index 2608c5c0663..6128ff040c6 100644 --- a/src/main/java/org/jabref/gui/util/IconValidationDecorator.java +++ b/src/main/java/org/jabref/gui/util/IconValidationDecorator.java @@ -31,19 +31,11 @@ public IconValidationDecorator(Pos position) { this.position = position; } - @Override - protected Node createErrorNode() { - return IconTheme.JabRefIcons.ERROR.getGraphicNode(); - } - - @Override - protected Node createWarningNode() { - return IconTheme.JabRefIcons.WARNING.getGraphicNode(); - } - @Override public Node createDecorationNode(ValidationMessage message) { - Node graphic = Severity.ERROR == message.getSeverity() ? createErrorNode() : createWarningNode(); + Node graphic = Severity.ERROR == message.getSeverity() + ? IconTheme.JabRefIcons.ERROR.getGraphicNode() + : IconTheme.JabRefIcons.WARNING.getGraphicNode(); graphic.getStyleClass().add(Severity.ERROR == message.getSeverity() ? "error-icon" : "warning-icon"); Label label = new Label(); label.setGraphic(graphic); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/CitaviXmlImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/CitaviXmlImporter.java index 78b0400932c..9bed9fdeadb 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/CitaviXmlImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/CitaviXmlImporter.java @@ -462,10 +462,11 @@ private BufferedReader getReaderFromZip(Path filePath) throws IOException { // Solution inspired by https://stackoverflow.com/a/37445972/873282 return new BufferedReader( new InputStreamReader( - new BOMInputStream( - Files.newInputStream(newFile, StandardOpenOption.READ), - false, - ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE))); + new BOMInputStream.Builder() + .setInputStream(Files.newInputStream(newFile, StandardOpenOption.READ)) + .setInclude(false) + .setByteOrderMarks(ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE) + .get())); } private String clean(String input) { diff --git a/src/main/java/org/jabref/model/groups/GroupTreeNode.java b/src/main/java/org/jabref/model/groups/GroupTreeNode.java index 6e5b3284a68..6fa9e354823 100644 --- a/src/main/java/org/jabref/model/groups/GroupTreeNode.java +++ b/src/main/java/org/jabref/model/groups/GroupTreeNode.java @@ -58,9 +58,7 @@ public ObjectProperty getGroupProperty() { * Associates the specified group with this node. * * @param newGroup the new group (has to be non-null) - * @deprecated use {@link #setGroup(AbstractGroup, boolean, boolean, List)}} instead */ - @Deprecated public void setGroup(AbstractGroup newGroup) { this.groupProperty.set(Objects.requireNonNull(newGroup)); } diff --git a/src/test/java/org/jabref/logic/util/IconValidationDecoratorTest.java b/src/test/java/org/jabref/logic/util/IconValidationDecoratorTest.java new file mode 100644 index 00000000000..7f5e9c81276 --- /dev/null +++ b/src/test/java/org/jabref/logic/util/IconValidationDecoratorTest.java @@ -0,0 +1,50 @@ +package org.jabref.logic.util; + +import javafx.scene.control.Control; +import javafx.scene.control.Label; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.util.IconValidationDecorator; + +import org.controlsfx.validation.Severity; +import org.controlsfx.validation.ValidationMessage; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.testfx.framework.junit5.ApplicationExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(ApplicationExtension.class) +public class IconValidationDecoratorTest { + static Object[][] decorationTestData() { + return new Object[][]{ + {Severity.ERROR, IconTheme.JabRefIcons.ERROR.getGraphicNode().toString()}, + {Severity.WARNING, IconTheme.JabRefIcons.WARNING.getGraphicNode().toString()} + }; + } + + @ParameterizedTest + @MethodSource("decorationTestData") + public void createDecorationNodeTest(Severity severity, String expectedGraphic) { + IconValidationDecorator iconValidationDecorator = new IconValidationDecorator(); + Label node = (Label) iconValidationDecorator.createDecorationNode(new ValidationMessage() { + @Override + public String getText() { + return "test"; + } + + @Override + public Severity getSeverity() { + return severity; + } + + @Override + public Control getTarget() { + return null; + } + }); + + assertEquals(node.getGraphic().toString(), expectedGraphic); + } +} From 798d6de3a74a7ca6235ae8c1b1c86043cf90ed51 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 02:21:37 +0100 Subject: [PATCH 013/108] Fix typo in condition --- .github/workflows/pr-comment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index e6fe90e40f9..5afc843116c 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -46,14 +46,14 @@ jobs: GH_TOKEN: ${{ github.token }} pr_number: ${{ steps.read-pr_number.outputs.pr_number }} - name: Checkout - if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.isCrossRepository.isCrossRepository == 'true') }} + if: ${{ steps.read-pr_number.outputs.pr_number != '' && steps.isCrossRepository.outputs.isCrossRepository == 'true' }} uses: actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' token: ${{ secrets.GITHUB_TOKEN }} - name: jbang - if: ${{ (steps.read-pr_number.outputs.pr_number != '') && (steps.isCrossRepository.isCrossRepository == 'true') }} + if: ${{ steps.read-pr_number.outputs.pr_number != '' && steps.isCrossRepository.outputs.isCrossRepository == 'true' }} uses: jbangdev/jbang-action@v0.119.0 with: script: ghprcomment@koppor/ghprcomment From 9c9556e00576b726ea58d9d340e23dfdd05ac989 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 02:22:10 +0100 Subject: [PATCH 014/108] Fix title of step --- .github/workflows/pr-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml index 5afc843116c..090adf78352 100644 --- a/.github/workflows/pr-comment.yml +++ b/.github/workflows/pr-comment.yml @@ -35,7 +35,7 @@ jobs: echo "Read PR number $PR_NUMBER" echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - uses: actions/checkout@v4 - - name: Determine owner + - name: Is PR from forked? if: ${{ steps.read-pr_number.outputs.pr_number != '' }} id: isCrossRepository run: | From 1bb4e39d8d80f3d2aba848b311c2811c77956a76 Mon Sep 17 00:00:00 2001 From: Loay Ghreeb Date: Sun, 27 Oct 2024 04:32:55 +0300 Subject: [PATCH 015/108] Fix lucene query (#12100) * Disable Postgres debug * Fix NPE with FulltextSearchResultsTab * Clear source tab highlight after clearing search query * Fix indexing of long name libraires * Migrate search groups syntax of testbib libraries * Use random name for table name instead of hashCode * Fix entry id query Field names shouldn't contain any special characters; otherwise, the parser will throw an exception. * Fix Lucene query --- .../org/jabref/gui/entryeditor/SourceTab.java | 7 +- .../FulltextSearchResultsTab.java | 6 +- .../jabref/logic/search/PostgreServer.java | 1 + .../search/indexing/BibFieldsIndexer.java | 3 +- .../search/query/SearchQueryConversion.java | 5 +- .../search/query/SearchToLuceneVisitor.java | 169 +++++------ .../search/retrieval/LinkedFilesSearcher.java | 26 +- .../model/database/BibDatabaseContext.java | 9 - .../jabref/model/search/PostgreConstants.java | 2 +- .../SearchQueryExtractorConversionTest.java | 3 +- .../SearchQueryLuceneConversionTest.java | 74 ++--- .../query/SearchQuerySQLConversionTest.java | 284 +++++++++--------- .../logic/pseudonymization/Chocolate.bib | 8 +- src/test/resources/testbib/Chocolate.bib | 8 +- .../testbib/simple-search-library.bib | 2 + 15 files changed, 305 insertions(+), 302 deletions(-) diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 629a991e0e8..a71cdd3b86f 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -105,13 +105,16 @@ public SourceTab(BibDatabaseContext bibDatabaseContext, } private void highlightSearchPattern() { - if (codeArea == null || searchQueryProperty.get().isEmpty()) { + if (codeArea == null) { return; } codeArea.setStyleClass(0, codeArea.getLength(), TEXT_STYLE); - Map, List> searchTermsMap = Highlighter.groupTermsByField(searchQueryProperty.get().get()); + if (searchQueryProperty.get().isEmpty()) { + return; + } + Map, List> searchTermsMap = Highlighter.groupTermsByField(searchQueryProperty.get().get()); searchTermsMap.forEach((optionalField, terms) -> { Optional searchPattern = Highlighter.buildSearchPattern(terms); if (searchPattern.isEmpty()) { diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java index 5e98f7d737d..c727655356a 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FulltextSearchResultsTab.java @@ -90,9 +90,6 @@ protected void bindToEntry(BibEntry entry) { if (entry == null || !shouldShow(entry)) { return; } - if (documentViewerView == null) { - documentViewerView = new DocumentViewerView(); - } this.entry = entry; content.getChildren().clear(); @@ -159,6 +156,9 @@ private Text createPageLink(LinkedFile linkedFile, int pageNumber) { pageLink.setOnMouseClicked(event -> { if (MouseButton.PRIMARY == event.getButton()) { + if (documentViewerView == null) { + documentViewerView = new DocumentViewerView(); + } documentViewerView.switchToFile(linkedFile); documentViewerView.gotoPage(pageNumber); documentViewerView.disableLiveMode(); diff --git a/src/main/java/org/jabref/logic/search/PostgreServer.java b/src/main/java/org/jabref/logic/search/PostgreServer.java index 13a113c441a..012f92f011d 100644 --- a/src/main/java/org/jabref/logic/search/PostgreServer.java +++ b/src/main/java/org/jabref/logic/search/PostgreServer.java @@ -23,6 +23,7 @@ public PostgreServer() { EmbeddedPostgres embeddedPostgres; try { embeddedPostgres = EmbeddedPostgres.builder() + .setOutputRedirector(ProcessBuilder.Redirect.DISCARD) .start(); LOGGER.info("Postgres server started, connection port: {}", embeddedPostgres.getPort()); } catch (IOException e) { diff --git a/src/main/java/org/jabref/logic/search/indexing/BibFieldsIndexer.java b/src/main/java/org/jabref/logic/search/indexing/BibFieldsIndexer.java index 6312ba54ffb..6ace9d64537 100644 --- a/src/main/java/org/jabref/logic/search/indexing/BibFieldsIndexer.java +++ b/src/main/java/org/jabref/logic/search/indexing/BibFieldsIndexer.java @@ -23,6 +23,7 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.search.PostgreConstants; +import io.github.thibaultmeyer.cuid.CUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +54,7 @@ public BibFieldsIndexer(BibEntryPreferences bibEntryPreferences, BibDatabaseCont this.keywordSeparator = bibEntryPreferences.getKeywordSeparator(); this.libraryName = databaseContext.getDatabasePath().map(path -> path.getFileName().toString()).orElse("unsaved"); - this.mainTable = databaseContext.getUniqueName(); + this.mainTable = CUID.randomCUID2(12).toString(); this.splitValuesTable = mainTable + SPLIT_TABLE_SUFFIX; this.schemaMainTableReference = PostgreConstants.getMainTableSchemaReference(mainTable); diff --git a/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java b/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java index 9c2b8ea0ff2..e782f31d1dc 100644 --- a/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java +++ b/src/main/java/org/jabref/logic/search/query/SearchQueryConversion.java @@ -7,7 +7,6 @@ import org.jabref.model.search.query.SqlQueryNode; import org.jabref.search.SearchParser; -import org.apache.lucene.search.Query; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +23,9 @@ public static String flagsToSearchExpression(SearchQuery searchQuery) { return new SearchFlagsToExpressionVisitor(searchQuery.getSearchFlags()).visit(searchQuery.getContext()); } - public static Query searchToLucene(SearchQuery searchQuery) { + public static String searchToLucene(SearchQuery searchQuery) { LOGGER.debug("Converting search expression to Lucene: {}", searchQuery.getSearchExpression()); - return new SearchToLuceneVisitor().visit(searchQuery.getContext()); + return new SearchToLuceneVisitor(searchQuery.getSearchFlags()).visit(searchQuery.getContext()); } public static List extractSearchTerms(SearchQuery searchQuery) { diff --git a/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java b/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java index 4f38a0ed1e2..c25a162045c 100644 --- a/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java +++ b/src/main/java/org/jabref/logic/search/query/SearchToLuceneVisitor.java @@ -1,152 +1,131 @@ package org.jabref.logic.search.query; +import java.util.EnumSet; import java.util.List; +import java.util.Locale; import org.jabref.model.search.LinkedFilesConstants; +import org.jabref.model.search.SearchFlags; import org.jabref.search.SearchBaseVisitor; import org.jabref.search.SearchParser; -import org.apache.lucene.index.Term; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.MatchNoDocsQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.RegexpQuery; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.util.QueryBuilder; +import org.apache.lucene.queryparser.classic.QueryParser; /** * Tests are located in {@link org.jabref.logic.search.query.SearchQueryLuceneConversionTest}. */ -public class SearchToLuceneVisitor extends SearchBaseVisitor { +public class SearchToLuceneVisitor extends SearchBaseVisitor { + private final EnumSet searchFlags; - private static final List SEARCH_FIELDS = LinkedFilesConstants.PDF_FIELDS; - - private final QueryBuilder queryBuilder; - - public SearchToLuceneVisitor() { - this.queryBuilder = new QueryBuilder(LinkedFilesConstants.LINKED_FILES_ANALYZER); + public SearchToLuceneVisitor(EnumSet searchFlags) { + this.searchFlags = searchFlags; } @Override - public Query visitStart(SearchParser.StartContext ctx) { + public String visitStart(SearchParser.StartContext ctx) { return visit(ctx.andExpression()); } @Override - public Query visitImplicitAndExpression(SearchParser.ImplicitAndExpressionContext ctx) { - List children = ctx.expression().stream().map(this::visit).toList(); - if (children.size() == 1) { - return children.getFirst(); - } - BooleanQuery.Builder builder = new BooleanQuery.Builder(); - for (Query child : children) { - builder.add(child, BooleanClause.Occur.MUST); - } - return builder.build(); + public String visitImplicitAndExpression(SearchParser.ImplicitAndExpressionContext ctx) { + List children = ctx.expression().stream().map(this::visit).toList(); + return children.size() == 1 ? children.getFirst() : String.join(" ", children); } @Override - public Query visitParenExpression(SearchParser.ParenExpressionContext ctx) { - return visit(ctx.andExpression()); + public String visitParenExpression(SearchParser.ParenExpressionContext ctx) { + String expr = visit(ctx.andExpression()); + return expr.isEmpty() ? "" : "(" + expr + ")"; } @Override - public Query visitNegatedExpression(SearchParser.NegatedExpressionContext ctx) { - Query innerQuery = visit(ctx.expression()); - if (innerQuery instanceof MatchNoDocsQuery) { - return innerQuery; - } - BooleanQuery.Builder builder = new BooleanQuery.Builder(); - builder.add(innerQuery, BooleanClause.Occur.MUST_NOT); - return builder.build(); + public String visitNegatedExpression(SearchParser.NegatedExpressionContext ctx) { + return "NOT (" + visit(ctx.expression()) + ")"; } @Override - public Query visitBinaryExpression(SearchParser.BinaryExpressionContext ctx) { - Query left = visit(ctx.left); - Query right = visit(ctx.right); + public String visitBinaryExpression(SearchParser.BinaryExpressionContext ctx) { + String left = visit(ctx.left); + String right = visit(ctx.right); - if (left instanceof MatchNoDocsQuery) { + if (left.isEmpty() && right.isEmpty()) { + return ""; + } + if (left.isEmpty()) { return right; } - if (right instanceof MatchNoDocsQuery) { + if (right.isEmpty()) { return left; } - BooleanQuery.Builder builder = new BooleanQuery.Builder(); + String operator = ctx.bin_op.getType() == SearchParser.AND ? " AND " : " OR "; + return left + operator + right; + } + + @Override + public String visitComparison(SearchParser.ComparisonContext ctx) { + String term = SearchQueryConversion.unescapeSearchValue(ctx.searchValue()); + boolean isQuoted = ctx.searchValue().getStart().getType() == SearchParser.STRING_LITERAL; + + // unfielded expression + if (ctx.FIELD() == null) { + if (searchFlags.contains(SearchFlags.REGULAR_EXPRESSION)) { + return "/" + term + "/"; + } + return isQuoted ? "\"" + escapeQuotes(term) + "\"" : QueryParser.escape(term); + } - if (ctx.bin_op.getType() == SearchParser.AND) { - builder.add(left, BooleanClause.Occur.MUST); - builder.add(right, BooleanClause.Occur.MUST); - } else if (ctx.bin_op.getType() == SearchParser.OR) { - builder.add(left, BooleanClause.Occur.SHOULD); - builder.add(right, BooleanClause.Occur.SHOULD); + String field = ctx.FIELD().getText().toLowerCase(Locale.ROOT); + if (!isValidField(field)) { + return ""; } - return builder.build(); + field = "any".equals(field) || "anyfield".equals(field) ? "" : field + ":"; + int operator = ctx.operator().getStart().getType(); + return buildFieldExpression(field, term, operator, isQuoted); } - @Override - public Query visitComparisonExpression(SearchParser.ComparisonExpressionContext ctx) { - return visit(ctx.comparison()); + private boolean isValidField(String field) { + return "any".equals(field) || "anyfield".equals(field) || LinkedFilesConstants.PDF_FIELDS.contains(field); } - @Override - public Query visitComparison(SearchParser.ComparisonContext ctx) { - String field = ctx.FIELD() == null ? null : ctx.FIELD().getText(); - String term = SearchQueryConversion.unescapeSearchValue(ctx.searchValue()); + private String buildFieldExpression(String field, String term, int operator, boolean isQuoted) { + boolean isRegexOp = isRegexOperator(operator); + boolean isNegationOp = isNegationOperator(operator); - // unfielded expression - if (field == null || "anyfield".equals(field) || "any".equals(field)) { - return createMultiFieldQuery(term, ctx.operator()); - } else if (SEARCH_FIELDS.contains(field)) { - return createFieldQuery(field, term, ctx.operator()); + if (isRegexOp) { + String expression = field + "/" + term + "/"; + return isNegationOp ? "NOT " + expression : expression; } else { - return new MatchNoDocsQuery(); + term = isQuoted ? "\"" + escapeQuotes(term) + "\"" : QueryParser.escape(term); + String expression = field + term; + return isNegationOp ? "NOT " + expression : expression; } } - private Query createMultiFieldQuery(String value, SearchParser.OperatorContext operator) { - BooleanQuery.Builder builder = new BooleanQuery.Builder(); - for (String field : SEARCH_FIELDS) { - builder.add(createFieldQuery(field, value, operator), BooleanClause.Occur.SHOULD); - } - return builder.build(); + private static String escapeQuotes(String term) { + return term.replace("\"", "\\\""); } - private Query createFieldQuery(String field, String value, SearchParser.OperatorContext operator) { - if (operator == null) { - return createTermOrPhraseQuery(field, value); - } - - return switch (operator.getStart().getType()) { - case SearchParser.REQUAL, - SearchParser.CREEQUAL -> - new RegexpQuery(new Term(field, value)); + private static boolean isNegationOperator(int operator) { + return switch (operator) { case SearchParser.NEQUAL, SearchParser.NCEQUAL, SearchParser.NEEQUAL, - SearchParser.NCEEQUAL -> - createNegatedQuery(createTermOrPhraseQuery(field, value)); - case SearchParser.NREQUAL, - SearchParser.NCREEQUAL -> - createNegatedQuery(new RegexpQuery(new Term(field, value))); - default -> - createTermOrPhraseQuery(field, value); + SearchParser.NCEEQUAL, + SearchParser.NREQUAL, + SearchParser.NCREEQUAL -> true; + default -> false; }; } - private Query createNegatedQuery(Query query) { - BooleanQuery.Builder negatedQuery = new BooleanQuery.Builder(); - negatedQuery.add(query, BooleanClause.Occur.MUST_NOT); - return negatedQuery.build(); - } - - private Query createTermOrPhraseQuery(String field, String value) { - if (value.contains("*") || value.contains("?")) { - return new TermQuery(new Term(field, value)); - } - return queryBuilder.createPhraseQuery(field, value); + private static boolean isRegexOperator(int operator) { + return switch (operator) { + case SearchParser.REQUAL, + SearchParser.CREEQUAL, + SearchParser.NREQUAL, + SearchParser.NCREEQUAL -> true; + default -> false; + }; } } diff --git a/src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java b/src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java index 5b2a93047fe..1e3e47a11b4 100644 --- a/src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java +++ b/src/main/java/org/jabref/logic/search/retrieval/LinkedFilesSearcher.java @@ -22,6 +22,9 @@ import org.apache.lucene.document.Document; import org.apache.lucene.index.StoredFields; +import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; @@ -39,11 +42,14 @@ public final class LinkedFilesSearcher { private final FilePreferences filePreferences; private final BibDatabaseContext databaseContext; private final SearcherManager searcherManager; + private final MultiFieldQueryParser parser; public LinkedFilesSearcher(BibDatabaseContext databaseContext, LuceneIndexer linkedFilesIndexer, FilePreferences filePreferences) { this.searcherManager = linkedFilesIndexer.getSearcherManager(); this.databaseContext = databaseContext; this.filePreferences = filePreferences; + this.parser = new MultiFieldQueryParser(LinkedFilesConstants.PDF_FIELDS.toArray(new String[0]), LinkedFilesConstants.LINKED_FILES_ANALYZER); + parser.setDefaultOperator(QueryParser.Operator.AND); } public SearchResults search(SearchQuery searchQuery) { @@ -51,17 +57,21 @@ public SearchResults search(SearchQuery searchQuery) { return new SearchResults(); } - Query luceneQuery = SearchQueryConversion.searchToLucene(searchQuery); + Optional luceneQuery = getLuceneQuery(searchQuery); + if (luceneQuery.isEmpty()) { + return new SearchResults(); + } + EnumSet searchFlags = searchQuery.getSearchFlags(); boolean shouldSearchInLinkedFiles = searchFlags.contains(SearchFlags.FULLTEXT) && filePreferences.shouldFulltextIndexLinkedFiles(); if (!shouldSearchInLinkedFiles) { return new SearchResults(); } - LOGGER.debug("Searching in linked files with query: {}", luceneQuery); + LOGGER.debug("Searching in linked files with query: {}", luceneQuery.get()); try { IndexSearcher linkedFilesIndexSearcher = acquireIndexSearcher(searcherManager); - SearchResults searchResults = search(linkedFilesIndexSearcher, luceneQuery); + SearchResults searchResults = search(linkedFilesIndexSearcher, luceneQuery.get()); releaseIndexSearcher(searcherManager, linkedFilesIndexSearcher); return searchResults; } catch (IOException | IndexSearcher.TooManyClauses e) { @@ -70,6 +80,16 @@ public SearchResults search(SearchQuery searchQuery) { return new SearchResults(); } + private Optional getLuceneQuery(SearchQuery searchQuery) { + String query = SearchQueryConversion.searchToLucene(searchQuery); + try { + return Optional.of(parser.parse(query)); + } catch (ParseException e) { + LOGGER.error("Error during query parsing", e); + return Optional.empty(); + } + } + private SearchResults search(IndexSearcher indexSearcher, Query searchQuery) throws IOException { TopDocs topDocs = indexSearcher.search(searchQuery, Integer.MAX_VALUE); StoredFields storedFields = indexSearcher.storedFields(); diff --git a/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/src/main/java/org/jabref/model/database/BibDatabaseContext.java index c09138a978d..bf4aa5154d8 100644 --- a/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -40,7 +40,6 @@ public class BibDatabaseContext { private static final Logger LOGGER = LoggerFactory.getLogger(BibDatabaseContext.class); - private static int NUMBER_OF_UNSAVED_LIBRARIES = 0; private final BibDatabase database; private MetaData metaData; @@ -275,14 +274,6 @@ public Path getFulltextIndexPath() { return indexPath; } - public String getUniqueName() { - if (getDatabasePath().isPresent()) { - Path databasePath = getDatabasePath().get(); - return BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); - } - return "unsaved" + NUMBER_OF_UNSAVED_LIBRARIES++; - } - @Override public String toString() { return "BibDatabaseContext{" + diff --git a/src/main/java/org/jabref/model/search/PostgreConstants.java b/src/main/java/org/jabref/model/search/PostgreConstants.java index 811d3e7d9af..e2cf9d07904 100644 --- a/src/main/java/org/jabref/model/search/PostgreConstants.java +++ b/src/main/java/org/jabref/model/search/PostgreConstants.java @@ -5,7 +5,7 @@ public enum PostgreConstants { BIB_FIELDS_SCHEME("bib_fields"), SPLIT_TABLE_SUFFIX("_split_values"), - ENTRY_ID("entry_id"), + ENTRY_ID("entryid"), FIELD_NAME("field_name"), FIELD_VALUE_LITERAL("field_value_literal"), // contains the value as-is FIELD_VALUE_TRANSFORMED("field_value_transformed"); // contains the value transformed for better querying diff --git a/src/test/java/org/jabref/logic/search/query/SearchQueryExtractorConversionTest.java b/src/test/java/org/jabref/logic/search/query/SearchQueryExtractorConversionTest.java index bdd4340f583..e6d9498fb59 100644 --- a/src/test/java/org/jabref/logic/search/query/SearchQueryExtractorConversionTest.java +++ b/src/test/java/org/jabref/logic/search/query/SearchQueryExtractorConversionTest.java @@ -26,7 +26,8 @@ public static Stream searchConversion() { Arguments.of(List.of(), "NOT a"), Arguments.of(List.of("a", "b", "c"), "(any = a OR any = b) AND NOT (NOT c AND title = d)"), Arguments.of(List.of("b", "c"), "title != a OR b OR c"), - Arguments.of(List.of("a", "b"), "a b") + Arguments.of(List.of("a", "b"), "a b"), + Arguments.of(List.of("term1 term2"), "\"term1 term2\"") ); } diff --git a/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java b/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java index 992cdd7dca5..7936a99b555 100644 --- a/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java +++ b/src/test/java/org/jabref/logic/search/query/SearchQueryLuceneConversionTest.java @@ -14,53 +14,55 @@ class SearchQueryLuceneConversionTest { public static Stream searchConversion() { return Stream.of( - Arguments.of("content:term annotations:term", "term"), - Arguments.of("content:term annotations:term", "any = term"), - Arguments.of("content:term annotations:term", "any CONTAINS term"), - Arguments.of("content:term annotations:term", "any MATCHES term"), - Arguments.of("content:term annotations:term", "any =! term"), - Arguments.of("content:term annotations:term", "any == term"), - Arguments.of("content:term annotations:term", "any ==! term"), + Arguments.of("a", "a"), + Arguments.of("the", "the"), + Arguments.of("term", "term"), + Arguments.of("term", "any = term"), + Arguments.of("term", "any CONTAINS term"), + Arguments.of("term", "any MATCHES term"), + Arguments.of("term", "any =! term"), + Arguments.of("term", "any == term"), + Arguments.of("term", "any ==! term"), - Arguments.of("content:\"two term\" annotations:\"two term\"", "\"two terms\""), - Arguments.of("content:\"two term\" annotations:\"two term\"", "any = \"two terms\""), + Arguments.of("\"two terms\"", "\"two terms\""), + Arguments.of("\"two terms\"", "any = \"two terms\""), + Arguments.of("NOT (term)", "NOT term"), - Arguments.of("content:imag", "content = image"), - Arguments.of("annotations:imag", "annotations = image"), - Arguments.of("content:\"imag process\"", "content = \"image processing\""), - Arguments.of("+content:imag +annotations:process", "content = image AND annotations = processing"), - Arguments.of("+(content:imag annotations:process) +(content:term annotations:term)", "(content = image OR annotations = processing) AND term"), - Arguments.of("(content:on annotations:on) (+(content:two annotations:two) +(content:three annotations:three))", "one OR (two AND three)"), + Arguments.of("content:image", "content = image"), + Arguments.of("annotations:image", "annotations = image"), + Arguments.of("content:\"image processing\"", "content = \"image processing\""), + Arguments.of("content:image AND annotations:processing", "content = image AND annotations = processing"), + Arguments.of("(content:image OR annotations:processing) AND term", "(content = image OR annotations = processing) AND term"), + Arguments.of("one OR (two AND three)", "one OR (two AND three)"), - Arguments.of("(-content:term) (-annotations:term)", "any != term"), - Arguments.of("(-content:term) (-annotations:term)", "any !== term"), - Arguments.of("(-content:term) (-annotations:term)", "any !=! term"), - Arguments.of("(-content:\"two term\") (-annotations:\"two term\")", "any != \"two terms\""), - Arguments.of("+(-content:imag) +(-annotations:process)", "content != image AND annotations != processing"), + Arguments.of("NOT term", "any != term"), + Arguments.of("NOT term", "any !== term"), + Arguments.of("NOT term", "any !=! term"), + Arguments.of("NOT \"two terms\"", "any != \"two terms\""), + Arguments.of("content:image AND NOT annotations:processing", "content = image AND annotations != processing"), - Arguments.of("MatchNoDocsQuery(\"\")", "title = image"), - Arguments.of("content:\"imag process\" annotations:\"imag process\"", "\"image processing\" AND author = smith"), - Arguments.of("+(content:imag annotations:imag) +(content:process annotations:process)", "image AND (title = term OR processing)"), - Arguments.of("(content:imag annotations:imag) (content:process annotations:process)", "image OR (title = term OR processing)"), - Arguments.of("MatchNoDocsQuery(\"\")", "title = \"image processing\" AND author = smith"), + // ignore non pdf fields + Arguments.of("", "title = image"), + Arguments.of("\"image processing\"", "\"image processing\" AND author = smith"), + Arguments.of("image AND (processing)", "image AND (title = term OR processing)"), + Arguments.of("image OR (processing)", "image OR (title = term OR processing)"), + Arguments.of("", "title = \"image processing\" AND author = smith"), - Arguments.of("content:neighbou?r annotations:neighbou?r", "neighbou?r"), - Arguments.of("content:neighbo* annotations:neighbo*", "neighbo*"), - Arguments.of("MatchNoDocsQuery(\"\")", "title = neighbou?r"), - Arguments.of("MatchNoDocsQuery(\"\")", "(title == chocolate) OR (author == smith)"), + Arguments.of("neighbou\\?r", "neighbou?r"), + Arguments.of("neighbo\\*", "neighbo*"), + Arguments.of("", "title = neighbou?r"), + Arguments.of("", "(title == chocolate) OR (author == smith)"), - Arguments.of("content:/(John|Doe).+(John|Doe)/ annotations:/(John|Doe).+(John|Doe)/", "any =~ \"(John|Doe).+(John|Doe)\""), - Arguments.of("content:/rev*/ annotations:/rev*/", "anyfield=~ rev*"), - Arguments.of("content:/*rev*/ annotations:/*rev*/", "anyfield=~ *rev*"), - Arguments.of("(-content:/.+/) (-annotations:/.+/)", "any !=~ .+"), - Arguments.of("(-content:/.+/) (-annotations:/.+/)", "groups !=~ .+ AND any !=~ .+") + // regex + Arguments.of("/(John|Doe).+(John|Doe)/", "any =~ \"(John|Doe).+(John|Doe)\""), + Arguments.of("/rev*/", "anyfield=~ rev*"), + Arguments.of("NOT /.+/", "any !=~ .+") ); } @ParameterizedTest @MethodSource void searchConversion(String expected, String searchExpression) { - String result = SearchQueryConversion.searchToLucene(new SearchQuery(searchExpression)).toString(); - assertEquals(expected, result); + assertEquals(expected, SearchQueryConversion.searchToLucene(new SearchQuery(searchExpression))); } } diff --git a/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java b/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java index d207d25e2e4..87172feff1d 100644 --- a/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java +++ b/src/test/java/org/jabref/logic/search/query/SearchQuerySQLConversionTest.java @@ -42,13 +42,13 @@ public static Stream searchConversion() { """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal ILIKE ('%smith%')) OR (main_table.field_value_transformed ILIKE ('%smith%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -56,13 +56,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal ILIKE ('%smith%')) OR (main_table.field_value_transformed ILIKE ('%smith%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -70,13 +70,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal LIKE ('%smith%')) OR (main_table.field_value_transformed LIKE ('%smith%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -84,17 +84,17 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table WHERE ( (inner_table.field_name = 'author') AND ((inner_table.field_value_literal ILIKE ('%smith%')) OR (inner_table.field_value_transformed ILIKE ('%smith%'))) ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -102,17 +102,17 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table WHERE ( (inner_table.field_name = 'author') AND ((inner_table.field_value_literal LIKE ('%smith%')) OR (inner_table.field_value_transformed LIKE ('%smith%'))) ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -120,17 +120,17 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (main_table.entry_id = split_table.entry_id AND main_table.field_name = split_table.field_name) + ON (main_table.entryid = split_table.entryid AND main_table.field_name = split_table.field_name) WHERE ( ((main_table.field_name = 'author') AND ((main_table.field_value_literal ILIKE ('smith')) OR (main_table.field_value_transformed ILIKE ('smith')))) OR ((split_table.field_name = 'author') AND ((split_table.field_value_literal ILIKE ('smith')) OR (split_table.field_value_transformed ILIKE ('smith')))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -138,17 +138,17 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (main_table.entry_id = split_table.entry_id AND main_table.field_name = split_table.field_name) + ON (main_table.entryid = split_table.entryid AND main_table.field_name = split_table.field_name) WHERE ( ((main_table.field_name = 'author') AND ((main_table.field_value_literal ILIKE ('smith')) OR (main_table.field_value_transformed ILIKE ('smith')))) OR ((split_table.field_name = 'author') AND ((split_table.field_value_literal ILIKE ('smith')) OR (split_table.field_value_transformed ILIKE ('smith')))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -156,17 +156,17 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (main_table.entry_id = split_table.entry_id AND main_table.field_name = split_table.field_name) + ON (main_table.entryid = split_table.entryid AND main_table.field_name = split_table.field_name) WHERE ( ((main_table.field_name = 'author') AND ((main_table.field_value_literal LIKE ('smith')) OR (main_table.field_value_transformed LIKE ('smith')))) OR ((split_table.field_name = 'author') AND ((split_table.field_value_literal LIKE ('smith')) OR (split_table.field_value_transformed LIKE ('smith')))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -174,13 +174,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (inner_table.entry_id = split_table.entry_id AND inner_table.field_name = split_table.field_name) + ON (inner_table.entryid = split_table.entryid AND inner_table.field_name = split_table.field_name) WHERE ( ((inner_table.field_name = 'author') AND ((inner_table.field_value_literal ILIKE ('smith')) OR (inner_table.field_value_transformed ILIKE ('smith')))) OR @@ -188,7 +188,7 @@ WHERE main_table.entry_id NOT IN ( ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -196,13 +196,13 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (inner_table.entry_id = split_table.entry_id AND inner_table.field_name = split_table.field_name) + ON (inner_table.entryid = split_table.entryid AND inner_table.field_name = split_table.field_name) WHERE ( ((inner_table.field_name = 'author') AND ((inner_table.field_value_literal LIKE ('smith')) OR (inner_table.field_value_transformed LIKE ('smith')))) OR @@ -210,7 +210,7 @@ WHERE main_table.entry_id NOT IN ( ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -218,13 +218,13 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal ~* ('smith')) OR (main_table.field_value_transformed ~* ('smith'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -232,13 +232,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal ~ ('smith')) OR (main_table.field_value_transformed ~ ('smith'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -246,17 +246,17 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table WHERE ( (inner_table.field_name = 'author') AND ((inner_table.field_value_literal ~* ('smith')) OR (inner_table.field_value_transformed ~* ('smith'))) ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -264,17 +264,17 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table WHERE ( (inner_table.field_name = 'author') AND ((inner_table.field_value_literal ~ ('smith')) OR (inner_table.field_value_transformed ~ ('smith'))) ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -282,13 +282,13 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%smith%')) OR (main_table.field_value_transformed ILIKE ('%smith%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -296,10 +296,10 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table LEFT JOIN bib_fields."tableName_split_values" AS split_table - ON (main_table.entry_id = split_table.entry_id AND main_table.field_name = split_table.field_name) + ON (main_table.entryid = split_table.entryid AND main_table.field_name = split_table.field_name) WHERE ( (main_table.field_name != 'groups') AND ( @@ -309,7 +309,7 @@ cte0 AS ( ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -317,17 +317,17 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT inner_table.entry_id + WHERE main_table.entryid NOT IN ( + SELECT inner_table.entryid FROM bib_fields."tableName" AS inner_table WHERE ( (inner_table.field_name != 'groups') AND ((inner_table.field_value_literal ILIKE ('%smith%')) OR (inner_table.field_value_transformed ILIKE ('%smith%'))) ) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -335,13 +335,13 @@ WHERE main_table.entry_id NOT IN ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'title') AND ((main_table.field_value_literal ILIKE ('%computer science%')) OR (main_table.field_value_transformed ILIKE ('%computer science%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -349,7 +349,7 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%term1%')) OR (main_table.field_value_transformed ILIKE ('%term1%'))) @@ -357,7 +357,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%term2%')) OR (main_table.field_value_transformed ILIKE ('%term2%'))) @@ -365,7 +365,7 @@ cte1 AS ( ) , cte2 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%term3%')) OR (main_table.field_value_transformed ILIKE ('%term3%'))) @@ -373,13 +373,13 @@ cte2 AS ( ) , cte3 AS ( - SELECT entry_id FROM cte0 + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id FROM cte1 + SELECT entryid FROM cte1 INTERSECT - SELECT entry_id FROM cte2 + SELECT entryid FROM cte2 ) - SELECT * FROM cte3 GROUP BY entry_id""" + SELECT * FROM cte3 GROUP BY entryid""" ), Arguments.of( @@ -387,7 +387,7 @@ cte3 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -395,7 +395,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -403,7 +403,7 @@ cte1 AS ( ) , cte2 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -411,21 +411,21 @@ cte2 AS ( ) , cte3 AS ( - SELECT entry_id + SELECT entryid FROM cte1 INTERSECT - SELECT entry_id + SELECT entryid FROM cte2 ) , cte4 AS ( - SELECT entry_id + SELECT entryid FROM cte0 UNION - SELECT entry_id + SELECT entryid FROM cte3 ) - SELECT * FROM cte4 GROUP BY entry_id""" + SELECT * FROM cte4 GROUP BY entryid""" ), Arguments.of( @@ -433,7 +433,7 @@ cte4 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -441,7 +441,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -449,15 +449,15 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id + SELECT entryid FROM cte1 ) , cte3 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -465,13 +465,13 @@ cte3 AS ( ) , cte4 AS ( - SELECT entry_id + SELECT entryid FROM cte2 UNION - SELECT entry_id + SELECT entryid FROM cte3 ) - SELECT * FROM cte4 GROUP BY entry_id""" + SELECT * FROM cte4 GROUP BY entryid""" ), Arguments.of( @@ -479,7 +479,7 @@ cte4 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -487,7 +487,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -495,15 +495,15 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 UNION - SELECT entry_id + SELECT entryid FROM cte1 ) , cte3 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -511,13 +511,13 @@ cte3 AS ( ) , cte4 AS ( - SELECT entry_id + SELECT entryid FROM cte2 INTERSECT - SELECT entry_id + SELECT entryid FROM cte3 ) - SELECT * FROM cte4 GROUP BY entry_id""" + SELECT * FROM cte4 GROUP BY entryid""" ), Arguments.of( @@ -525,7 +525,7 @@ cte4 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -533,7 +533,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -541,7 +541,7 @@ cte1 AS ( ) , cte2 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -549,21 +549,21 @@ cte2 AS ( ) , cte3 AS ( - SELECT entry_id + SELECT entryid FROM cte1 INTERSECT - SELECT entry_id + SELECT entryid FROM cte2 ) , cte4 AS ( - SELECT entry_id + SELECT entryid FROM cte0 UNION - SELECT entry_id + SELECT entryid FROM cte3 ) - SELECT * FROM cte4 GROUP BY entry_id""" + SELECT * FROM cte4 GROUP BY entryid""" ), Arguments.of( @@ -571,7 +571,7 @@ cte4 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -579,7 +579,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -587,15 +587,15 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 UNION - SELECT entry_id + SELECT entryid FROM cte1 ) , cte3 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -603,7 +603,7 @@ cte3 AS ( ) , cte4 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%d%')) OR (main_table.field_value_transformed ILIKE ('%d%'))) @@ -611,21 +611,21 @@ cte4 AS ( ) , cte5 AS ( - SELECT entry_id + SELECT entryid FROM cte3 UNION - SELECT entry_id + SELECT entryid FROM cte4 ) , cte6 AS ( - SELECT entry_id + SELECT entryid FROM cte2 INTERSECT - SELECT entry_id + SELECT entryid FROM cte5 ) - SELECT * FROM cte6 GROUP BY entry_id""" + SELECT * FROM cte6 GROUP BY entryid""" ), Arguments.of( @@ -633,7 +633,7 @@ cte6 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a%')) OR (main_table.field_value_transformed ILIKE ('%a%'))) @@ -641,7 +641,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%b%')) OR (main_table.field_value_transformed ILIKE ('%b%'))) @@ -649,7 +649,7 @@ cte1 AS ( ) , cte2 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%c%')) OR (main_table.field_value_transformed ILIKE ('%c%'))) @@ -657,30 +657,30 @@ cte2 AS ( ) , cte3 AS ( - SELECT entry_id + SELECT entryid FROM cte1 UNION - SELECT entry_id + SELECT entryid FROM cte2 ) , cte4 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table - WHERE main_table.entry_id NOT IN ( - SELECT entry_id + WHERE main_table.entryid NOT IN ( + SELECT entryid FROM cte3 ) ) , cte5 AS ( - SELECT entry_id + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id + SELECT entryid FROM cte4 ) - SELECT * FROM cte5 GROUP BY entry_id""" + SELECT * FROM cte5 GROUP BY entryid""" ), Arguments.of( @@ -688,13 +688,13 @@ cte5 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%a''b%')) OR (main_table.field_value_transformed ILIKE ('%a''b%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ) ); } @@ -722,13 +722,13 @@ public static Stream unFieldedTermsWithSearchBarFlags() { """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%Test%')) OR (main_table.field_value_transformed ILIKE ('%Test%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -737,13 +737,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal LIKE ('%Test%')) OR (main_table.field_value_transformed LIKE ('%Test%'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -752,13 +752,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ~* ('Test')) OR (main_table.field_value_transformed ~* ('Test'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -767,13 +767,13 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ~ ('Test')) OR (main_table.field_value_transformed ~ ('Test'))) ) ) - SELECT * FROM cte0 GROUP BY entry_id""" + SELECT * FROM cte0 GROUP BY entryid""" ), Arguments.of( @@ -782,7 +782,7 @@ cte0 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ILIKE ('%Test%')) OR (main_table.field_value_transformed ILIKE ('%Test%'))) @@ -790,7 +790,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal LIKE ('%Smith%')) OR (main_table.field_value_transformed LIKE ('%Smith%'))) @@ -798,13 +798,13 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id + SELECT entryid FROM cte1 ) - SELECT * FROM cte2 GROUP BY entry_id""" + SELECT * FROM cte2 GROUP BY entryid""" ), Arguments.of( @@ -813,7 +813,7 @@ cte2 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal LIKE ('%Test%')) OR (main_table.field_value_transformed LIKE ('%Test%'))) @@ -821,7 +821,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal LIKE ('%Smith%')) OR (main_table.field_value_transformed LIKE ('%Smith%'))) @@ -829,13 +829,13 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id + SELECT entryid FROM cte1 ) - SELECT * FROM cte2 GROUP BY entry_id""" + SELECT * FROM cte2 GROUP BY entryid""" ), Arguments.of( @@ -844,7 +844,7 @@ cte2 AS ( """ WITH cte0 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name != 'groups') AND ((main_table.field_value_literal ~* ('Test')) OR (main_table.field_value_transformed ~* ('Test'))) @@ -852,7 +852,7 @@ cte0 AS ( ) , cte1 AS ( - SELECT main_table.entry_id + SELECT main_table.entryid FROM bib_fields."tableName" AS main_table WHERE ( (main_table.field_name = 'author') AND ((main_table.field_value_literal LIKE ('%Smith%')) OR (main_table.field_value_transformed LIKE ('%Smith%'))) @@ -860,13 +860,13 @@ cte1 AS ( ) , cte2 AS ( - SELECT entry_id + SELECT entryid FROM cte0 INTERSECT - SELECT entry_id + SELECT entryid FROM cte1 ) - SELECT * FROM cte2 GROUP BY entry_id""" + SELECT * FROM cte2 GROUP BY entryid""" ) ); } diff --git a/src/test/resources/org/jabref/logic/pseudonymization/Chocolate.bib b/src/test/resources/org/jabref/logic/pseudonymization/Chocolate.bib index 5fbb0f7af03..a6a676fc61e 100644 --- a/src/test/resources/org/jabref/logic/pseudonymization/Chocolate.bib +++ b/src/test/resources/org/jabref/logic/pseudonymization/Chocolate.bib @@ -214,11 +214,13 @@ @Comment{jabref-meta: @Comment{jabref-meta: grouping: 0 AllEntriesGroup:; -1 SearchGroup:Entries without a group\;0\;groups != .+\;0\;1\;1\;\;\;\;; -1 SearchGroup:Entries without a linked file\;0\;file != .+\;0\;1\;1\;\;\;\;; +1 SearchGroup:Entries without a group\;0\;groups !=~ .+\;0\;1\;1\;\;\;\;; +1 SearchGroup:Entries without a linked file\;0\;file !=~ .+\;0\;1\;1\;\;\;\;; 1 StaticGroup:Paywalled\;0\;1\;\;\;\;; -1 SearchGroup:To read\;0\;groups != .+ and readstatus != .+\;0\;1\;1\;0x008080ff\;\;\;; +1 SearchGroup:To read\;0\;groups !=~ .+ and readstatus !=~ .+\;0\;1\;1\;0x008080ff\;\;\;; 1 KeywordGroup:Skimmed\;0\;readstatus\;skimmed\;0\;0\;1\;0xffff00ff\;\;\;; 1 KeywordGroup:Read\;0\;readstatus\;read\;0\;0\;0\;0x00ff00ff\;\;\;; 1 StaticGroup:Used\;0\;1\;0x0000ffff\;\;\;; } + +@Comment{jabref-meta: groups-search-syntax-version:6.0-alpha_1} diff --git a/src/test/resources/testbib/Chocolate.bib b/src/test/resources/testbib/Chocolate.bib index 7a5301de470..5169c8f239c 100644 --- a/src/test/resources/testbib/Chocolate.bib +++ b/src/test/resources/testbib/Chocolate.bib @@ -210,13 +210,15 @@ @Comment{jabref-meta: @Comment{jabref-meta: grouping: 0 AllEntriesGroup:; -1 SearchGroup:Entries without a group\;0\;groups != .+\;0\;1\;1\;\;\;\;; -1 SearchGroup:Entries without a linked file\;0\;file != .+\;0\;1\;1\;\;\;\;; +1 SearchGroup:Entries without a group\;0\;groups !=~ .+\;0\;1\;1\;\;\;\;; +1 SearchGroup:Entries without a linked file\;0\;file !=~ .+\;0\;1\;1\;\;\;\;; 1 StaticGroup:Paywalled\;0\;1\;\;\;\;; -1 SearchGroup:To read\;0\;groups != .+ and readstatus != .+\;0\;1\;1\;0x008080ff\;\;\;; +1 SearchGroup:To read\;0\;groups !=~ .+ and readstatus !=~ .+\;0\;1\;1\;0x008080ff\;\;\;; 1 KeywordGroup:Skimmed\;0\;readstatus\;skimmed\;0\;0\;1\;0xffff00ff\;\;\;; 1 KeywordGroup:Read\;0\;readstatus\;read\;0\;0\;0\;0x00ff00ff\;\;\;; 1 StaticGroup:Used\;0\;1\;0x0000ffff\;\;\;; } +@Comment{jabref-meta: groups-search-syntax-version:6.0-alpha_1} + @Comment{jabref-meta: keypatterndefault:[auth]_[year];} diff --git a/src/test/resources/testbib/simple-search-library.bib b/src/test/resources/testbib/simple-search-library.bib index 9d4d58d4a7c..7e4cee0935c 100644 --- a/src/test/resources/testbib/simple-search-library.bib +++ b/src/test/resources/testbib/simple-search-library.bib @@ -68,3 +68,5 @@ @Comment{jabref-meta: 0 AllEntriesGroup:; 1 SearchGroup:g\;0\;g\;0\;0\;1\;\;\;\;; } + +@Comment{jabref-meta: groups-search-syntax-version:6.0-alpha_1} From 93e3f110f17ce397148b448efb2eeb06e245ef70 Mon Sep 17 00:00:00 2001 From: Ricky-Du <141602008+u7465990@users.noreply.github.com> Date: Sun, 27 Oct 2024 22:09:38 +1100 Subject: [PATCH 016/108] Double click citation for te xworks (#12106) * First version of the double click issue * updated codes * add the vscode to open the tex file * solve csl-styles * solve csl-locales * solve buildres/abbrv.jabref.org * Add the default path for VScode * ADD a todo for issue6775 * make the double click works for TeXworks. --- src/main/java/org/jabref/gui/push/PushToTeXworks.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/gui/push/PushToTeXworks.java b/src/main/java/org/jabref/gui/push/PushToTeXworks.java index 6459d848c13..87c3a3b9931 100644 --- a/src/main/java/org/jabref/gui/push/PushToTeXworks.java +++ b/src/main/java/org/jabref/gui/push/PushToTeXworks.java @@ -38,7 +38,6 @@ protected String[] getCommandLine(String keyString) { @Override protected String[] jumpToLineCommandlineArguments(Path fileName, int line, int column) { - // No command known to jump to a specific line - return new String[] {commandPath, fileName.toString()}; + return new String[] {commandPath, "--position=\"%s\"".formatted(line), fileName.toString()}; } } From 69c7e21c40117a333cd9ff6fc43db142f8ce716b Mon Sep 17 00:00:00 2001 From: NoahXu718 Date: Mon, 28 Oct 2024 00:10:44 +1100 Subject: [PATCH 017/108] Support gpt4 all server api #11870 (#12078) * response cutoff bug fixed * add Chat4All option when choose the aiProvider(Preference). complete Chat4AllModel class to send request to the local model and parse the response successfully! * Push the Chat4AllModel class * apikey disable if GPT4ALL is selected * fix time out problem * fix some warning in code * write the CHANGELOG.md * fix some check styles * convert some class to record * continue to fix the checkstyle * using checkstyle.xml to detect the inappropriate code and fix it. * remove some code that not useful, and fix some code style. * Change all the Chat4All to Gpt4All and privacy policy for GPT4All. * add the prompt text for Gpt4All, rewrite the CHANGELOG.md and restore the JvmOpenAiChatLanguageModel.java to its original state(main). * Change GPT_CHAT_MODEL_PROMPT to GPT_4_ALL_CHAT_MODEL_PROMPT. * fix the name problem and add some comments in Gpt4AllModel.java, using BLANK when the model name is not specified * change Gpt4All to GPT4ALL * fix some bug when Api key is empty * fix the checkstyle * use GPT4All as it's the official name in CHANGELOG.md * Update src/main/java/org/jabref/gui/preferences/ai/AiTab.java * use GPT4All in prompt text and separate BLANK into BLANK_HUGGING_FACE and BLANK_GPT4ALL * Update CHANGELOG.md - Add AI preferences section to changelog - Add AI providers to changelog --------- Co-authored-by: qwsxmkoi Co-authored-by: Ruslan Co-authored-by: Oliver Kopp Co-authored-by: ThiloteE <73715071+ThiloteE@users.noreply.github.com> --- CHANGELOG.md | 5 +- .../privacynotice/PrivacyNoticeComponent.fxml | 9 ++ .../privacynotice/PrivacyNoticeComponent.java | 2 + .../org/jabref/gui/preferences/ai/AiTab.java | 13 +- .../gui/preferences/ai/AiTabViewModel.java | 22 +++ .../jabref/logic/ai/AiDefaultPreferences.java | 7 +- .../org/jabref/logic/ai/AiPreferences.java | 35 +++++ .../logic/ai/chatting/model/Gpt4AllModel.java | 148 ++++++++++++++++++ .../model/JabRefChatLanguageModel.java | 9 +- .../preferences/JabRefCliPreferences.java | 8 + .../java/org/jabref/model/ai/AiProvider.java | 5 +- 11 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/jabref/logic/ai/chatting/model/Gpt4AllModel.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7d259a82e..806deb5b445 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,11 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) - We added an AI-based chat for entries with linked PDF files. [#11430](https://github.com/JabRef/jabref/pull/11430) - We added an AI-based summarization possibility for entries with linked PDF files. [#11430](https://github.com/JabRef/jabref/pull/11430) +- We added an AI section in JabRef's [preferences](https://docs.jabref.org/ai/preferences). [#11430](https://github.com/JabRef/jabref/pull/11430) +- We added AI providers: OpenAI, Mistral AI, Hugging Face and Google. [#11430](https://github.com/JabRef/jabref/pull/11430), [#11736](https://github.com/JabRef/jabref/pull/11736) +- We added AI providers: [Ollama](https://docs.jabref.org/ai/local-llm#step-by-step-guide-for-ollama) and GPT4All, which add the possibility to use local LLMs privately on your own device. [#11430](https://github.com/JabRef/jabref/pull/11430), [#11870](https://github.com/JabRef/jabref/issues/11870) - We added support for selecting and using CSL Styles in JabRef's OpenOffice/LibreOffice integration for inserting bibliographic and in-text citations into a document. [#2146](https://github.com/JabRef/jabref/issues/2146), [#8893](https://github.com/JabRef/jabref/issues/8893) -- We added Tools > New library based on references in PDF file... to create a new library based on the references section in a PDF file. [#11522](https://github.com/JabRef/jabref/pull/11522) +- We added "Tools > New library based on references in PDF file" ... to create a new library based on the references section in a PDF file. [#11522](https://github.com/JabRef/jabref/pull/11522) - When converting the references section of a paper (PDF file), more than the last page is treated. [#11522](https://github.com/JabRef/jabref/pull/11522) - Added the functionality to invoke offline reference parsing explicitly. [#11565](https://github.com/JabRef/jabref/pull/11565) - The dialog for [adding an entry using reference text](https://docs.jabref.org/collect/newentryfromplaintext) is now filled with the clipboard contents as default. [#11565](https://github.com/JabRef/jabref/pull/11565) diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml index 2f439beac31..a4f4da47cc2 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml @@ -66,6 +66,15 @@ + + + + + + + + + diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java index 12dad69d580..4ef78da8c36 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -25,6 +25,7 @@ public class PrivacyNoticeComponent extends ScrollPane { @FXML private TextFlow mistralAiPrivacyTextFlow; @FXML private TextFlow geminiPrivacyTextFlow; @FXML private TextFlow huggingFacePrivacyTextFlow; + @FXML private TextFlow gpt4AllTextFlow; @FXML private Text embeddingModelText; private final AiPreferences aiPreferences; @@ -49,6 +50,7 @@ private void initialize() { initPrivacyHyperlink(mistralAiPrivacyTextFlow, AiProvider.MISTRAL_AI); initPrivacyHyperlink(geminiPrivacyTextFlow, AiProvider.GEMINI); initPrivacyHyperlink(huggingFacePrivacyTextFlow, AiProvider.HUGGING_FACE); + initPrivacyHyperlink(gpt4AllTextFlow, AiProvider.GPT4ALL); String newEmbeddingModelText = embeddingModelText.getText().replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo()); embeddingModelText.setText(newEmbeddingModelText); diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTab.java b/src/main/java/org/jabref/gui/preferences/ai/AiTab.java index efa406a1d27..3360e3a5b4a 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTab.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTab.java @@ -1,6 +1,7 @@ package org.jabref.gui.preferences.ai; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -28,6 +29,7 @@ public class AiTab extends AbstractPreferenceTabView implements PreferencesTab { private static final String HUGGING_FACE_CHAT_MODEL_PROMPT = "TinyLlama/TinyLlama_v1.1 (or any other model name)"; + private static final String GPT_4_ALL_CHAT_MODEL_PROMPT = "Phi-3.1-mini (or any other local model name from GPT4All)"; @FXML private CheckBox enableAi; @FXML private CheckBox autoGenerateEmbeddings; @@ -87,10 +89,19 @@ public void initialize() { if (newValue == AiProvider.HUGGING_FACE) { chatModelComboBox.setPromptText(HUGGING_FACE_CHAT_MODEL_PROMPT); } + if (newValue == AiProvider.GPT4ALL) { + chatModelComboBox.setPromptText(GPT_4_ALL_CHAT_MODEL_PROMPT); + } }); apiKeyTextField.textProperty().bindBidirectional(viewModel.apiKeyProperty()); - apiKeyTextField.disableProperty().bind(viewModel.disableBasicSettingsProperty()); + // Disable if GPT4ALL is selected + apiKeyTextField.disableProperty().bind( + Bindings.or( + viewModel.disableBasicSettingsProperty(), + aiProviderComboBox.valueProperty().isEqualTo(AiProvider.GPT4ALL) + ) + ); customizeExpertSettingsCheckbox.selectedProperty().bindBidirectional(viewModel.customizeExpertSettingsProperty()); customizeExpertSettingsCheckbox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index c1c4ef5f6a5..b8871f235e4 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -54,6 +54,7 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final StringProperty mistralAiChatModel = new SimpleStringProperty(); private final StringProperty geminiChatModel = new SimpleStringProperty(); private final StringProperty huggingFaceChatModel = new SimpleStringProperty(); + private final StringProperty gpt4AllChatModel = new SimpleStringProperty(); private final StringProperty currentApiKey = new SimpleStringProperty(); @@ -61,6 +62,7 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final StringProperty mistralAiApiKey = new SimpleStringProperty(); private final StringProperty geminiAiApiKey = new SimpleStringProperty(); private final StringProperty huggingFaceApiKey = new SimpleStringProperty(); + private final StringProperty gpt4AllApiKey = new SimpleStringProperty(); private final BooleanProperty customizeExpertSettings = new SimpleBooleanProperty(); @@ -75,6 +77,7 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final StringProperty mistralAiApiBaseUrl = new SimpleStringProperty(); private final StringProperty geminiApiBaseUrl = new SimpleStringProperty(); private final StringProperty huggingFaceApiBaseUrl = new SimpleStringProperty(); + private final StringProperty gpt4AllApiBaseUrl = new SimpleStringProperty(); private final StringProperty instruction = new SimpleStringProperty(); private final StringProperty temperature = new SimpleStringProperty(); @@ -150,6 +153,11 @@ public AiTabViewModel(CliPreferences preferences) { huggingFaceApiKey.set(currentApiKey.get()); huggingFaceApiBaseUrl.set(currentApiBaseUrl.get()); } + case GPT4ALL-> { + gpt4AllChatModel.set(oldChatModel); + gpt4AllApiKey.set(currentApiKey.get()); + gpt4AllApiBaseUrl.set(currentApiBaseUrl.get()); + } } } @@ -174,6 +182,11 @@ public AiTabViewModel(CliPreferences preferences) { currentApiKey.set(huggingFaceApiKey.get()); currentApiBaseUrl.set(huggingFaceApiBaseUrl.get()); } + case GPT4ALL -> { + currentChatModel.set(gpt4AllChatModel.get()); + currentApiKey.set(gpt4AllApiKey.get()); + currentApiBaseUrl.set(gpt4AllApiBaseUrl.get()); + } } }); @@ -183,6 +196,7 @@ public AiTabViewModel(CliPreferences preferences) { case MISTRAL_AI -> mistralAiChatModel.set(newValue); case GEMINI -> geminiChatModel.set(newValue); case HUGGING_FACE -> huggingFaceChatModel.set(newValue); + case GPT4ALL -> gpt4AllChatModel.set(newValue); } contextWindowSize.set(AiDefaultPreferences.getContextWindowSize(selectedAiProvider.get(), newValue)); @@ -194,6 +208,7 @@ public AiTabViewModel(CliPreferences preferences) { case MISTRAL_AI -> mistralAiApiKey.set(newValue); case GEMINI -> geminiAiApiKey.set(newValue); case HUGGING_FACE -> huggingFaceApiKey.set(newValue); + case GPT4ALL -> gpt4AllApiKey.set(newValue); } }); @@ -203,6 +218,7 @@ public AiTabViewModel(CliPreferences preferences) { case MISTRAL_AI -> mistralAiApiBaseUrl.set(newValue); case GEMINI -> geminiApiBaseUrl.set(newValue); case HUGGING_FACE -> huggingFaceApiBaseUrl.set(newValue); + case GPT4ALL -> gpt4AllApiBaseUrl.set(newValue); } }); @@ -279,16 +295,19 @@ public void setValues() { mistralAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.MISTRAL_AI)); geminiAiApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.GEMINI)); huggingFaceApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.HUGGING_FACE)); + gpt4AllApiKey.setValue(aiPreferences.getApiKeyForAiProvider(AiProvider.GPT4ALL)); openAiApiBaseUrl.setValue(aiPreferences.getOpenAiApiBaseUrl()); mistralAiApiBaseUrl.setValue(aiPreferences.getMistralAiApiBaseUrl()); geminiApiBaseUrl.setValue(aiPreferences.getGeminiApiBaseUrl()); huggingFaceApiBaseUrl.setValue(aiPreferences.getHuggingFaceApiBaseUrl()); + gpt4AllApiBaseUrl.setValue(aiPreferences.getGpt4AllApiBaseUrl()); openAiChatModel.setValue(aiPreferences.getOpenAiChatModel()); mistralAiChatModel.setValue(aiPreferences.getMistralAiChatModel()); geminiChatModel.setValue(aiPreferences.getGeminiChatModel()); huggingFaceChatModel.setValue(aiPreferences.getHuggingFaceChatModel()); + gpt4AllChatModel.setValue(aiPreferences.getGpt4AllChatModel()); enableAi.setValue(aiPreferences.getEnableAi()); autoGenerateSummaries.setValue(aiPreferences.getAutoGenerateSummaries()); @@ -320,11 +339,13 @@ public void storeSettings() { aiPreferences.setMistralAiChatModel(mistralAiChatModel.get() == null ? "" : mistralAiChatModel.get()); aiPreferences.setGeminiChatModel(geminiChatModel.get() == null ? "" : geminiChatModel.get()); aiPreferences.setHuggingFaceChatModel(huggingFaceChatModel.get() == null ? "" : huggingFaceChatModel.get()); + aiPreferences.setGpt4AllChatModel(gpt4AllChatModel.get() == null ? "" : gpt4AllChatModel.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.OPEN_AI, openAiApiKey.get() == null ? "" : openAiApiKey.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.MISTRAL_AI, mistralAiApiKey.get() == null ? "" : mistralAiApiKey.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.GEMINI, geminiAiApiKey.get() == null ? "" : geminiAiApiKey.get()); aiPreferences.storeAiApiKeyInKeyring(AiProvider.HUGGING_FACE, huggingFaceApiKey.get() == null ? "" : huggingFaceApiKey.get()); + aiPreferences.storeAiApiKeyInKeyring(AiProvider.GPT4ALL, gpt4AllApiKey.get() == null ? "" : gpt4AllApiKey.get()); // We notify in all cases without a real check if something was changed aiPreferences.apiKeyUpdated(); @@ -336,6 +357,7 @@ public void storeSettings() { aiPreferences.setMistralAiApiBaseUrl(mistralAiApiBaseUrl.get() == null ? "" : mistralAiApiBaseUrl.get()); aiPreferences.setGeminiApiBaseUrl(geminiApiBaseUrl.get() == null ? "" : geminiApiBaseUrl.get()); aiPreferences.setHuggingFaceApiBaseUrl(huggingFaceApiBaseUrl.get() == null ? "" : huggingFaceApiBaseUrl.get()); + aiPreferences.setGpt4AllApiBaseUrl(gpt4AllApiBaseUrl.get() == null ? "" : gpt4AllApiBaseUrl.get()); aiPreferences.setInstruction(instruction.get()); // We already check the correctness of temperature and RAG minimum score in validators, so we don't need to check it here. diff --git a/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java b/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java index e83c1a084b5..3cdbd9bfda8 100644 --- a/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java +++ b/src/main/java/org/jabref/logic/ai/AiDefaultPreferences.java @@ -23,7 +23,9 @@ public enum PredefinedChatModel { GEMINI_1_5_PRO(AiProvider.GEMINI, "gemini-1.5-pro", 2097152), GEMINI_1_0_PRO(AiProvider.GEMINI, "gemini-1.0-pro", 32000), // Dummy variant for Hugging Face models. - HUGGING_FACE(AiProvider.HUGGING_FACE, "", 0); + // Blank entry used for cases where the model name is not specified. + BLANK_HUGGING_FACE(AiProvider.HUGGING_FACE, "", 0), + BLANK_GPT4ALL(AiProvider.GPT4ALL, "", 0); private final AiProvider aiProvider; private final String name; @@ -62,7 +64,8 @@ public String toString() { AiProvider.OPEN_AI, PredefinedChatModel.GPT_4O_MINI, AiProvider.MISTRAL_AI, PredefinedChatModel.OPEN_MIXTRAL_8X22B, AiProvider.GEMINI, PredefinedChatModel.GEMINI_1_5_FLASH, - AiProvider.HUGGING_FACE, PredefinedChatModel.HUGGING_FACE + AiProvider.HUGGING_FACE, PredefinedChatModel.BLANK_HUGGING_FACE, + AiProvider.GPT4ALL, PredefinedChatModel.BLANK_GPT4ALL ); public static final boolean CUSTOMIZE_SETTINGS = false; diff --git a/src/main/java/org/jabref/logic/ai/AiPreferences.java b/src/main/java/org/jabref/logic/ai/AiPreferences.java index 3a07a02e70c..48b10812338 100644 --- a/src/main/java/org/jabref/logic/ai/AiPreferences.java +++ b/src/main/java/org/jabref/logic/ai/AiPreferences.java @@ -40,6 +40,7 @@ public class AiPreferences { private final StringProperty mistralAiChatModel; private final StringProperty geminiChatModel; private final StringProperty huggingFaceChatModel; + private final StringProperty gpt4AllChatModel; private final BooleanProperty customizeExpertSettings; @@ -47,6 +48,7 @@ public class AiPreferences { private final StringProperty mistralAiApiBaseUrl; private final StringProperty geminiApiBaseUrl; private final StringProperty huggingFaceApiBaseUrl; + private final StringProperty gpt4AllApiBaseUrl; private final ObjectProperty embeddingModel; private final StringProperty instruction; @@ -67,11 +69,13 @@ public AiPreferences(boolean enableAi, String mistralAiChatModel, String geminiChatModel, String huggingFaceChatModel, + String gpt4AllModel, boolean customizeExpertSettings, String openAiApiBaseUrl, String mistralAiApiBaseUrl, String geminiApiBaseUrl, String huggingFaceApiBaseUrl, + String gpt4AllApiBaseUrl, EmbeddingModel embeddingModel, String instruction, double temperature, @@ -91,6 +95,7 @@ public AiPreferences(boolean enableAi, this.mistralAiChatModel = new SimpleStringProperty(mistralAiChatModel); this.geminiChatModel = new SimpleStringProperty(geminiChatModel); this.huggingFaceChatModel = new SimpleStringProperty(huggingFaceChatModel); + this.gpt4AllChatModel = new SimpleStringProperty(gpt4AllModel); this.customizeExpertSettings = new SimpleBooleanProperty(customizeExpertSettings); @@ -98,6 +103,7 @@ public AiPreferences(boolean enableAi, this.mistralAiApiBaseUrl = new SimpleStringProperty(mistralAiApiBaseUrl); this.geminiApiBaseUrl = new SimpleStringProperty(geminiApiBaseUrl); this.huggingFaceApiBaseUrl = new SimpleStringProperty(huggingFaceApiBaseUrl); + this.gpt4AllApiBaseUrl = new SimpleStringProperty(gpt4AllApiBaseUrl); this.embeddingModel = new SimpleObjectProperty<>(embeddingModel); this.instruction = new SimpleStringProperty(instruction); @@ -233,6 +239,18 @@ public void setHuggingFaceChatModel(String huggingFaceChatModel) { this.huggingFaceChatModel.set(huggingFaceChatModel); } + public StringProperty gpt4AllChatModelProperty() { + return gpt4AllChatModel; + } + + public String getGpt4AllChatModel() { + return huggingFaceChatModel.get(); + } + + public void setGpt4AllChatModel(String gpt4AllChatModel) { + this.gpt4AllChatModel.set(gpt4AllChatModel); + } + public BooleanProperty customizeExpertSettingsProperty() { return customizeExpertSettings; } @@ -309,6 +327,18 @@ public void setHuggingFaceApiBaseUrl(String huggingFaceApiBaseUrl) { this.huggingFaceApiBaseUrl.set(huggingFaceApiBaseUrl); } + public StringProperty gpt4AllApiBaseUrlProperty() { + return gpt4AllApiBaseUrl; + } + + public String getGpt4AllApiBaseUrl() { + return gpt4AllApiBaseUrl.get(); + } + + public void setGpt4AllApiBaseUrl(String gpt4AllApiBaseUrl) { + this.gpt4AllApiBaseUrl.set(gpt4AllApiBaseUrl); + } + public StringProperty instructionProperty() { return instruction; } @@ -354,6 +384,7 @@ public int getContextWindowSize() { case MISTRAL_AI -> AiDefaultPreferences.getContextWindowSize(AiProvider.MISTRAL_AI, mistralAiChatModel.get()); case HUGGING_FACE -> AiDefaultPreferences.getContextWindowSize(AiProvider.HUGGING_FACE, huggingFaceChatModel.get()); case GEMINI -> AiDefaultPreferences.getContextWindowSize(AiProvider.GEMINI, geminiChatModel.get()); + case GPT4ALL -> AiDefaultPreferences.getContextWindowSize(AiProvider.GPT4ALL, gpt4AllChatModel.get()); }; } } @@ -481,6 +512,8 @@ public String getSelectedChatModel() { huggingFaceChatModel.get(); case GEMINI -> geminiChatModel.get(); + case GPT4ALL -> + gpt4AllChatModel.get(); }; } @@ -495,6 +528,8 @@ public String getSelectedApiBaseUrl() { huggingFaceApiBaseUrl.get(); case GEMINI -> geminiApiBaseUrl.get(); + case GPT4ALL -> + gpt4AllApiBaseUrl.get(); }; } else { return aiProvider.get().getApiUrl(); diff --git a/src/main/java/org/jabref/logic/ai/chatting/model/Gpt4AllModel.java b/src/main/java/org/jabref/logic/ai/chatting/model/Gpt4AllModel.java new file mode 100644 index 00000000000..20c26951f69 --- /dev/null +++ b/src/main/java/org/jabref/logic/ai/chatting/model/Gpt4AllModel.java @@ -0,0 +1,148 @@ +package org.jabref.logic.ai.chatting.model; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +import org.jabref.logic.ai.AiPreferences; + +import com.google.gson.Gson; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.ToolExecutionResultMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.output.FinishReason; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.model.output.TokenUsage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Gpt4AllModel implements ChatLanguageModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(Gpt4AllModel.class); + + private final AiPreferences aiPreferences; + private final HttpClient httpClient; + private final Gson gson = new Gson(); + + public Gpt4AllModel(AiPreferences aiPreferences, HttpClient httpClient) { + this.aiPreferences = aiPreferences; + this.httpClient = httpClient; + } + + @Override + public Response generate(List list) { + LOGGER.debug("Generating response from Gpt4All model with {} messages: {}", list.size(), list); + + List messages = list.stream() + .map(chatMessage -> switch (chatMessage) { + case AiMessage aiMessage -> new Message("assistant", aiMessage.text()); + case SystemMessage systemMessage -> new Message("system", systemMessage.text()); + case ToolExecutionResultMessage toolExecutionResultMessage -> new Message("tool", toolExecutionResultMessage.text()); + case UserMessage userMessage -> new Message("user", userMessage.singleText()); + default -> throw new IllegalStateException("Unknown ChatMessage type: " + chatMessage); + }).collect(Collectors.toList()); + + TextGenerationRequest request = TextGenerationRequest + .builder() + .model(aiPreferences.getSelectedChatModel()) + .messages(messages) + .temperature(aiPreferences.getTemperature()) + .max_tokens(2048) + .build(); + + try { + String requestBody = gson.toJson(request); + String baseUrl = aiPreferences.getSelectedApiBaseUrl(); + String fullUrl = baseUrl.endsWith("/") ? baseUrl + "chat/completions" : baseUrl + "/chat/completions"; + HttpRequest httpRequest = HttpRequest.newBuilder() + .uri(URI.create(fullUrl)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .timeout(Duration.ofSeconds(60)) + .build(); + + HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + LOGGER.info("Gpt4All response: {}", response.body()); + + TextGenerationResponse textGenerationResponse = gson.fromJson(response.body(), TextGenerationResponse.class); + if (textGenerationResponse.choices() == null || textGenerationResponse.choices().isEmpty()) { + throw new IllegalArgumentException("No choices returned in the response"); + } + + String generatedText = textGenerationResponse.choices().getFirst().message().content(); + if (generatedText == null || generatedText.isEmpty()) { + throw new IllegalArgumentException("Generated text is null or empty"); + } + + // Note: We do not check the token usage and finish reason here. + // This class is not a complete implementation of langchain4j's ChatLanguageModel. + // We only implemented the functionality we specifically need. + return new Response<>(new AiMessage(generatedText), new TokenUsage(0, 0), FinishReason.OTHER); + } catch (Exception e) { + LOGGER.error("Error generating message from Gpt4All", e); + throw new RuntimeException("Failed to generate AI message", e); + } + } + + private static class TextGenerationRequest { + protected final String model; + protected final List messages; + protected final Double temperature; + protected final Integer max_tokens; + + private TextGenerationRequest(Builder builder) { + this.model = builder.model; + this.messages = builder.messages; + this.temperature = builder.temperature; + this.max_tokens = builder.max_tokens; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String model; + private List messages; + private Double temperature; + private Integer max_tokens; + + public Builder model(String model) { + this.model = model; + return this; + } + + public Builder messages(List messages) { + this.messages = messages; + return this; + } + + public Builder temperature(Double temperature) { + this.temperature = temperature; + return this; + } + + public Builder max_tokens(Integer max_tokens) { + this.max_tokens = max_tokens; + return this; + } + + public TextGenerationRequest build() { + return new TextGenerationRequest(this); + } + } + } + + private record TextGenerationResponse(List choices) { } + + private record Choice(Message message) { } + + private record Message(String role, String content) { } +} diff --git a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java index 92fc11df484..8445e15347d 100644 --- a/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java +++ b/src/main/java/org/jabref/logic/ai/chatting/model/JabRefChatLanguageModel.java @@ -10,6 +10,7 @@ import org.jabref.logic.ai.AiPreferences; import org.jabref.logic.ai.chatting.AiChatLogic; import org.jabref.logic.l10n.Localization; +import org.jabref.model.ai.AiProvider; import com.google.common.util.concurrent.ThreadFactoryBuilder; import dev.langchain4j.data.message.AiMessage; @@ -54,7 +55,7 @@ public JabRefChatLanguageModel(AiPreferences aiPreferences) { */ private void rebuild() { String apiKey = aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider()); - if (!aiPreferences.getEnableAi() || apiKey.isEmpty()) { + if (!aiPreferences.getEnableAi() || (apiKey.isEmpty() && aiPreferences.getAiProvider() != AiProvider.GPT4ALL)) { langchainChatModel = Optional.empty(); return; } @@ -64,6 +65,10 @@ private void rebuild() { langchainChatModel = Optional.of(new JvmOpenAiChatLanguageModel(aiPreferences, httpClient)); } + case GPT4ALL-> { + langchainChatModel = Optional.of(new Gpt4AllModel(aiPreferences, httpClient)); + } + case MISTRAL_AI -> { langchainChatModel = Optional.of(MistralAiChatModel .builder() @@ -129,7 +134,7 @@ public Response generate(List list) { if (langchainChatModel.isEmpty()) { if (!aiPreferences.getEnableAi()) { throw new RuntimeException(Localization.lang("In order to use AI chat, you need to enable chatting with attached PDF files in JabRef preferences (AI tab).")); - } else if (aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider()).isEmpty()) { + } else if (aiPreferences.getApiKeyForAiProvider(aiPreferences.getAiProvider()).isEmpty() && aiPreferences.getAiProvider() != AiProvider.GPT4ALL) { throw new RuntimeException(Localization.lang("In order to use AI chat, set an API key inside JabRef preferences (AI tab).")); } else { rebuild(); diff --git a/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java b/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java index f3ecebe41d9..27760c02f70 100644 --- a/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java +++ b/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java @@ -356,12 +356,14 @@ public class JabRefCliPreferences implements CliPreferences { private static final String AI_MISTRAL_AI_CHAT_MODEL = "aiMistralAiChatModel"; private static final String AI_GEMINI_CHAT_MODEL = "aiGeminiChatModel"; private static final String AI_HUGGING_FACE_CHAT_MODEL = "aiHuggingFaceChatModel"; + private static final String AI_GPT_4_ALL_MODEL = "aiGpt4AllChatModel"; private static final String AI_CUSTOMIZE_SETTINGS = "aiCustomizeSettings"; private static final String AI_EMBEDDING_MODEL = "aiEmbeddingModel"; private static final String AI_OPEN_AI_API_BASE_URL = "aiOpenAiApiBaseUrl"; private static final String AI_MISTRAL_AI_API_BASE_URL = "aiMistralAiApiBaseUrl"; private static final String AI_GEMINI_API_BASE_URL = "aiGeminiApiBaseUrl"; private static final String AI_HUGGING_FACE_API_BASE_URL = "aiHuggingFaceApiBaseUrl"; + private static final String AI_GPT_4_ALL_API_BASE_URL = "aiGpt4AllApiBaseUrl"; private static final String AI_SYSTEM_MESSAGE = "aiSystemMessage"; private static final String AI_TEMPERATURE = "aiTemperature"; private static final String AI_CONTEXT_WINDOW_SIZE = "aiMessageWindowSize"; @@ -641,12 +643,14 @@ protected JabRefCliPreferences() { defaults.put(AI_MISTRAL_AI_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.MISTRAL_AI).getName()); defaults.put(AI_GEMINI_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.GEMINI).getName()); defaults.put(AI_HUGGING_FACE_CHAT_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.HUGGING_FACE).getName()); + defaults.put(AI_GPT_4_ALL_MODEL, AiDefaultPreferences.CHAT_MODELS.get(AiProvider.GPT4ALL).getName()); defaults.put(AI_CUSTOMIZE_SETTINGS, AiDefaultPreferences.CUSTOMIZE_SETTINGS); defaults.put(AI_EMBEDDING_MODEL, AiDefaultPreferences.EMBEDDING_MODEL.name()); defaults.put(AI_OPEN_AI_API_BASE_URL, AiProvider.OPEN_AI.getApiUrl()); defaults.put(AI_MISTRAL_AI_API_BASE_URL, AiProvider.MISTRAL_AI.getApiUrl()); defaults.put(AI_GEMINI_API_BASE_URL, AiProvider.GEMINI.getApiUrl()); defaults.put(AI_HUGGING_FACE_API_BASE_URL, AiProvider.HUGGING_FACE.getApiUrl()); + defaults.put(AI_GPT_4_ALL_API_BASE_URL, AiProvider.GPT4ALL.getApiUrl()); defaults.put(AI_SYSTEM_MESSAGE, AiDefaultPreferences.SYSTEM_MESSAGE); defaults.put(AI_TEMPERATURE, AiDefaultPreferences.TEMPERATURE); defaults.put(AI_CONTEXT_WINDOW_SIZE, AiDefaultPreferences.getContextWindowSize(AiDefaultPreferences.PROVIDER, AiDefaultPreferences.CHAT_MODELS.get(AiDefaultPreferences.PROVIDER).getName())); @@ -1835,11 +1839,13 @@ public AiPreferences getAiPreferences() { get(AI_MISTRAL_AI_CHAT_MODEL), get(AI_GEMINI_CHAT_MODEL), get(AI_HUGGING_FACE_CHAT_MODEL), + get(AI_GPT_4_ALL_MODEL), getBoolean(AI_CUSTOMIZE_SETTINGS), get(AI_OPEN_AI_API_BASE_URL), get(AI_MISTRAL_AI_API_BASE_URL), get(AI_GEMINI_API_BASE_URL), get(AI_HUGGING_FACE_API_BASE_URL), + get(AI_GPT_4_ALL_API_BASE_URL), EmbeddingModel.valueOf(get(AI_EMBEDDING_MODEL)), get(AI_SYSTEM_MESSAGE), getDouble(AI_TEMPERATURE), @@ -1859,6 +1865,7 @@ public AiPreferences getAiPreferences() { EasyBind.listen(aiPreferences.mistralAiChatModelProperty(), (obs, oldValue, newValue) -> put(AI_MISTRAL_AI_CHAT_MODEL, newValue)); EasyBind.listen(aiPreferences.geminiChatModelProperty(), (obs, oldValue, newValue) -> put(AI_GEMINI_CHAT_MODEL, newValue)); EasyBind.listen(aiPreferences.huggingFaceChatModelProperty(), (obs, oldValue, newValue) -> put(AI_HUGGING_FACE_CHAT_MODEL, newValue)); + EasyBind.listen(aiPreferences.gpt4AllChatModelProperty(), (obs, oldValue, newValue) -> put(AI_GPT_4_ALL_MODEL, newValue)); EasyBind.listen(aiPreferences.customizeExpertSettingsProperty(), (obs, oldValue, newValue) -> putBoolean(AI_CUSTOMIZE_SETTINGS, newValue)); @@ -1866,6 +1873,7 @@ public AiPreferences getAiPreferences() { EasyBind.listen(aiPreferences.mistralAiApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_MISTRAL_AI_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.geminiApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_GEMINI_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.huggingFaceApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_HUGGING_FACE_API_BASE_URL, newValue)); + EasyBind.listen(aiPreferences.gpt4AllApiBaseUrlProperty(), (obs, oldValue, newValue) -> put(AI_GPT_4_ALL_API_BASE_URL, newValue)); EasyBind.listen(aiPreferences.embeddingModelProperty(), (obs, oldValue, newValue) -> put(AI_EMBEDDING_MODEL, newValue.name())); EasyBind.listen(aiPreferences.instructionProperty(), (obs, oldValue, newValue) -> put(AI_SYSTEM_MESSAGE, newValue)); diff --git a/src/main/java/org/jabref/model/ai/AiProvider.java b/src/main/java/org/jabref/model/ai/AiProvider.java index 00382e98d36..053406fdc5f 100644 --- a/src/main/java/org/jabref/model/ai/AiProvider.java +++ b/src/main/java/org/jabref/model/ai/AiProvider.java @@ -3,10 +3,11 @@ import java.io.Serializable; public enum AiProvider implements Serializable { - OPEN_AI("OpenAI", "https://openai.com/policies/privacy-policy/", "https://openai.com/policies/privacy-policy/"), + OPEN_AI("OpenAI", "https://api.openai.com/v1", "https://openai.com/policies/privacy-policy/"), MISTRAL_AI("Mistral AI", "https://mistral.ai/terms/#privacy-policy", "https://mistral.ai/terms/#privacy-policy"), GEMINI("Gemini", "https://huggingface.co/privacy", "https://ai.google.dev/gemini-api/terms"), - HUGGING_FACE("Hugging Face", "https://huggingface.co/api", "https://huggingface.co/privacy"); + HUGGING_FACE("Hugging Face", "https://huggingface.co/api", "https://huggingface.co/privacy"), + GPT4ALL("GPT4All", "http://localhost:4891/v1", "https://www.nomic.ai/gpt4all/legal/privacy-policy"); private final String label; private final String apiUrl; From a5b6156aa5bde4404f495efd9bb5d85813492000 Mon Sep 17 00:00:00 2001 From: ityyrm <151578808+ityyrm@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:14:08 +0000 Subject: [PATCH 018/108] Added minimum window sizing for creating new entries (#12033) * Added minimum window sizing for new entry window * Added minimum window sizing for plain citation parser window * Included modification to minimum window sizing in CHANGELOG.md * Fixed import organisation to match checkstyle --------- Co-authored-by: u7469164 --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/entrytype/EntryTypeView.java | 5 +++++ .../gui/plaincitationparser/PlainCitationParserDialog.java | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 806deb5b445..40e18f7a6b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We changed instances of 'Search Selected' to 'Search Pre-configured' in Web Search Preferences UI. [#11871](https://github.com/JabRef/jabref/pull/11871) - We added a new CSS style class `main-table` for the main table. [#11881](https://github.com/JabRef/jabref/pull/11881) - When renaming a file, the old extension is now used if there is none provided in the new name. [#11903](https://github.com/JabRef/jabref/issues/11903) +- Added minimum window sizing for windows dedicated to creating new entries [#11944](https://github.com/JabRef/jabref/issues/11944) - We changed the name of the library-based file directory from 'General File Directory' to 'Library-specific File Directory' per issue [#571](https://github.com/koppor/jabref/issues/571) - The CitationKey column is now a default shown column for the entry table. [#10510](https://github.com/JabRef/jabref/issues/10510) diff --git a/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java b/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java index 8884561f2a7..a66e407522c 100644 --- a/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java @@ -16,6 +16,7 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.FlowPane; import javafx.stage.Screen; +import javafx.stage.Stage; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -87,6 +88,10 @@ public EntryTypeView(LibraryTab libraryTab, DialogService dialogService, GuiPref .load() .setAsDialogPane(this); + Stage stage = (Stage) getDialogPane().getScene().getWindow(); + stage.setMinHeight(400); + stage.setMinWidth(500); + ControlHelper.setAction(generateButton, this.getDialogPane(), event -> viewModel.runFetcherWorker()); setOnCloseRequest(e -> viewModel.cancelFetcherWorker()); diff --git a/src/main/java/org/jabref/gui/plaincitationparser/PlainCitationParserDialog.java b/src/main/java/org/jabref/gui/plaincitationparser/PlainCitationParserDialog.java index a72b8c66d3a..f2b21abd906 100644 --- a/src/main/java/org/jabref/gui/plaincitationparser/PlainCitationParserDialog.java +++ b/src/main/java/org/jabref/gui/plaincitationparser/PlainCitationParserDialog.java @@ -9,6 +9,7 @@ import javafx.scene.control.ComboBox; import javafx.scene.control.TextArea; import javafx.scene.control.Tooltip; +import javafx.stage.Stage; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; @@ -53,6 +54,10 @@ public PlainCitationParserDialog() { .setAsDialogPane(this); this.setTitle(Localization.lang("Plain Citations Parser")); + + Stage stage = (Stage) getDialogPane().getScene().getWindow(); + stage.setMinHeight(550); + stage.setMinWidth(500); } @FXML From 2cbbc2bfe28c0488349e2bd1611cefdaf75db055 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 27 Oct 2024 14:32:58 +0100 Subject: [PATCH 019/108] It seems that reviewdog is not working on fork-PRs (#12103) --- .github/ghprcomment.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/ghprcomment.yml b/.github/ghprcomment.yml index f42ddc26b33..96ed0801b1f 100644 --- a/.github/ghprcomment.yml +++ b/.github/ghprcomment.yml @@ -2,12 +2,8 @@ message: | Your code currently does not meet [JabRef's code guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html). We use [Checkstyle](https://checkstyle.sourceforge.io/) to identify issues. - The tool reviewdog already placed comments on GitHub to indicate the places. See the tab "Files" in you PR. Please carefully follow [the setup guide for the codestyle](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html). Afterwards, please [run checkstyle locally](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html#run-checkstyle) and fix the issues. - - - You can check review dog's comments at the tab "Files changed" of your pull request. - jobName: OpenRewrite message: | Your code currently does not meet JabRef's code guidelines. From aa31a58fde151597858c19a123c0c392882591c8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 27 Oct 2024 14:43:22 +0100 Subject: [PATCH 020/108] New Crowdin updates (#12111) * New translations jabref_en.properties (French) * New translations jabref_en.properties (Polish) * New translations jabref_en.properties (Portuguese, Brazilian) * New translations jabref_en.properties (Spanish) * New translations jabref_en.properties (German) * New translations jabref_en.properties (Italian) * New translations jabref_en.properties (Japanese) * New translations jabref_en.properties (Korean) * New translations jabref_en.properties (Dutch) * New translations jabref_en.properties (Russian) * New translations jabref_en.properties (Turkish) * New translations jabref_en.properties (Chinese Simplified) --- src/main/resources/l10n/JabRef_de.properties | 3 --- src/main/resources/l10n/JabRef_es.properties | 1 - src/main/resources/l10n/JabRef_fr.properties | 7 ++++--- src/main/resources/l10n/JabRef_it.properties | 7 ++++--- src/main/resources/l10n/JabRef_ja.properties | 1 - src/main/resources/l10n/JabRef_ko.properties | 1 - src/main/resources/l10n/JabRef_nl.properties | 1 - src/main/resources/l10n/JabRef_pl.properties | 5 ++++- src/main/resources/l10n/JabRef_pt_BR.properties | 3 --- src/main/resources/l10n/JabRef_ru.properties | 1 - src/main/resources/l10n/JabRef_tr.properties | 1 - src/main/resources/l10n/JabRef_zh_CN.properties | 1 - 12 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index e4303880927..39bf80ebe97 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -313,7 +313,6 @@ Extract\ References\ (online)=Referenzen extrahieren (online) Processing...=In Bearbeitung... Processing\ "%0"...=Verarbeite "%0"... Processing\ PDF(s)=Verarbeite PDF(s) -Processing\ file\ %0=Datei %0 wird verarbeitet Processing\ a\ large\ number\ of\ files=Verarbeitung einer großen Anzahl an Dateien You\ are\ about\ to\ process\ %0\ files.\ Continue?=Sie sind dabei %0 Dateien zu verarbeiten. Fortfahren? @@ -484,10 +483,8 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Unabh I\ Agree=Ich stimme zu Indexing\ bib\ fields\ for\ %0=Indizierung von Bib-Feldern für %0 -Indexing\ PDF\ files\ for\ %0=Indizierung von PDF-Dateien für %0 %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 von %1 Einträgen zum Index hinzugefügt. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 von %1 Einträgen aus dem Index entfernt. -Indexing\ %0.\ %1\ of\ %2\ files\ added\ to\ the\ index.=Indizierung von %0. %1 von %2 Dateien zum Index hinzugefügt. Removing\ entries\ from\ index\ for\ %0=Entferne Einträge aus dem Index für %0 Invalid\ URL=Ungültige URL diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 1c796eb65fb..2df9ba9868c 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -302,7 +302,6 @@ Export\ to\ text\ file.=Exportar a un archivo de texto. Processing...=Procesando... -Processing\ file\ %0=Procesando archivo %0 You\ are\ about\ to\ process\ %0\ files.\ Continue?=Está por procesar %0 archivos. ¿Quiere continuar? diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 3d713f27f0a..11ae9099321 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -319,7 +319,8 @@ Extract\ References\ (online)=Extraire les références (en ligne) Processing...=Traitement en cours... Processing\ "%0"...=Traitement de « %0 »... Processing\ PDF(s)=Traitement des PDF(s) en cours -Processing\ file\ %0=Traitement du fichier %0 +Processing\ %0=Traitement de %0 +Importing\ files\ into\ %1\ |\ %2\ of\ %0\ file(s)\ processed.=Indexation des fichiers pour %1 | %2 de %0 fichier(s) indexé(s). Processing\ a\ large\ number\ of\ files=Traitement d'un grand nombre de fichiers You\ are\ about\ to\ process\ %0\ files.\ Continue?=Vous êtes sur le point de traiter %0 fichiers. Continuer ? @@ -490,10 +491,10 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Groupe I\ Agree=J’accepte Indexing\ bib\ fields\ for\ %0=Indexation des champs bib à %0 -Indexing\ PDF\ files\ for\ %0=Indexation des fichiers PDF à %0 +Indexing\ %0=Indexation de %0 +Indexing\ files\ for\ %1\ |\ %2\ of\ %0\ file(s)\ indexed.=Indexation des fichiers pour %1 | %2 de %0 fichier(s) indexé(s). %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 sur %1 entrées ajoutées à l'index. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 of %1 entrées supprimées de l'index. -Indexing\ %0.\ %1\ of\ %2\ files\ added\ to\ the\ index.=Indexation de %0. %1 sur %2 fichiers ajoutés à l'index. Removing\ entries\ from\ index\ for\ %0=Suppression des entrées de l'index à %0 Invalid\ URL=URL invalide diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 81382559097..28f9443c387 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -319,7 +319,8 @@ Extract\ References\ (online)=Estrai Riferimenti (online) Processing...=Elaborazione... Processing\ "%0"...=Elaborazione di %0"... Processing\ PDF(s)=Elaborazione PDF -Processing\ file\ %0=Elaborazione file %0 +Processing\ %0=Elaborazione di %0 +Importing\ files\ into\ %1\ |\ %2\ of\ %0\ file(s)\ processed.=Importazione di file in %1 | %2 di %0 file elaborati. Processing\ a\ large\ number\ of\ files=Elaborazione di un gran numero di file You\ are\ about\ to\ process\ %0\ files.\ Continue?=Stai per elaborare %0 file. Continuare? @@ -490,10 +491,10 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Gruppo I\ Agree=Accetto Indexing\ bib\ fields\ for\ %0=Indicizzazione dei campi bib per %0 -Indexing\ PDF\ files\ for\ %0=Indicizzazione dei file PDF per %0 +Indexing\ %0=Indicizzazione di %0 +Indexing\ files\ for\ %1\ |\ %2\ of\ %0\ file(s)\ indexed.=Indicizzazione di file per %1 | %2 di %0 file indicizzati. %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 di %1 voci aggiunte all'indice. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 di %1 voci rimosse dall'indice. -Indexing\ %0.\ %1\ of\ %2\ files\ added\ to\ the\ index.=Indicizzazione %0. %1 di %2 file aggiunti all'indice. Removing\ entries\ from\ index\ for\ %0=Rimozione delle voci dall'indice per %0 Invalid\ URL=URL non valido diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index e73a20c053f..f5d4bdc77e9 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -288,7 +288,6 @@ Export\ to\ text\ file.=テキストファイルに書き出します. Processing...=処理中... -Processing\ file\ %0=ファイル %0 を処理しています Will\ write\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=選択した項目からリンクされたPDFにメタデータを書き込みます. diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index ef9d996d5d2..d8a8f74b702 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -273,7 +273,6 @@ Export\ to\ clipboard=클립보드로 내보내기 Export\ to\ text\ file.=텍스트 파일로 내보내기 -Processing\ file\ %0=파일 %0 처리중 diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 07b9cd04d8f..6f8133acd5e 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -293,7 +293,6 @@ Export\ to\ text\ file.=Exporteer naar tekstbestand. Processing...=Verwerken... -Processing\ file\ %0=Bestand %0 wordt verwerkt Will\ write\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=Zal metadata schrijven naar de PDF's gelinkt van geselecteerde invoer. diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index 508de30647a..5b79bf595cf 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -300,7 +300,8 @@ Extract\ References\ (online)=Wyodrębnij referencje (online) Processing...=Przetwarzanie... Processing\ "%0"...=Przetwarzanie "%0"... Processing\ PDF(s)=Przetwarzanie PDF(ów) -Processing\ file\ %0=Przetwarzanie pliku %0 +Processing\ %0=Przetwarzanie %0 +Importing\ files\ into\ %1\ |\ %2\ of\ %0\ file(s)\ processed.=Importowanie plików do %1 | %2 z %0 plików przetworzonych. Processing\ a\ large\ number\ of\ files=Przetwarzanie dużej liczby plików You\ are\ about\ to\ process\ %0\ files.\ Continue?=Zamierzasz przetworzyć %0 plików. Czy chcesz kontynuować? @@ -445,6 +446,8 @@ Importer\ class=Klasa importera I\ Agree=Wyrażam zgodę +Indexing\ %0=Indeksowanie %0 +Indexing\ files\ for\ %1\ |\ %2\ of\ %0\ file(s)\ indexed.=Indeksowanie plików do %1 | %2 z %0 plików przetworzonych. Invalid\ URL=Nieprawidłowy URL Online\ help=Pomoc online diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 70de2417b98..a3fe097762e 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -316,7 +316,6 @@ Extract\ References\ (online)=Extrair Referências (online) Processing...=Processando... Processing\ "%0"...=Processando "%0"... Processing\ PDF(s)=Processando PDF(s) -Processing\ file\ %0=Processando arquivo %0 Processing\ a\ large\ number\ of\ files=Processando um grande número de arquivos You\ are\ about\ to\ process\ %0\ files.\ Continue?=Você está prestes a processar %0 arquivos. Continuar? @@ -486,10 +485,8 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Grupo I\ Agree=Concordo Indexing\ bib\ fields\ for\ %0=Indexando campos bib para %0 -Indexing\ PDF\ files\ for\ %0=Indexando arquivos PDF para %0 %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 de %1 entradas adicionadas ao índice. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 dos %1 registros removidos do índice. -Indexing\ %0.\ %1\ of\ %2\ files\ added\ to\ the\ index.=Indexando %0. %1 dos arquivos %2 adicionados ao índice. Removing\ entries\ from\ index\ for\ %0=Removendo entradas do índice %0 Invalid\ URL=URL inválida diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 35d823f31c3..9167363de1c 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -294,7 +294,6 @@ Export\ to\ text\ file.=Экспортировать в текстовый фа Processing\ PDF(s)=Обработка PDF(ов) -Processing\ file\ %0=Обработка файла %0 Processing\ a\ large\ number\ of\ files=Обработка большого количества файлов diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index d2764f53f6b..3941a77a08a 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -294,7 +294,6 @@ Export\ to\ text\ file.=Metin dosyasına aktar. Processing...=İşleniyor... -Processing\ file\ %0=Dosya işleniyor %0 Will\ write\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=Seçili girdilerle bağlantılı PDF'lere metaverisi yazılacak. diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 368355c08a4..6a6182a04cf 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -293,7 +293,6 @@ Export\ to\ text\ file.=导出到文本文件。 Processing...=处理中... -Processing\ file\ %0=正在处理文件 %0 Will\ write\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=将为选中条目链接的PDF文件写入XMP元数据。 From ce27eb3bd7f09f9e50359886b0083d97c21e550b Mon Sep 17 00:00:00 2001 From: Arsh Chawla <150571042+arshchawla21@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:55:20 +1100 Subject: [PATCH 021/108] Fixed Grobid Preference Dialog Logic, Removed Checkbox (#12034) * Modified GROBID dialog logic, allowing users to explicitly save GROBID usage preference. "Do not ask" checkbox replaced with "Save Preference", and GROBID_OPTOUT replaced with GROBID_PREFERENCE to reduce ambiguity. * Removed checkbox entirely, dialog logic updated such that users will only be asked about Grobid permissions once, with the preference being stored for the future. Updated CHANGELOG.md * Updated dialog wording to be 'Send to Grobid' and 'Do not send' to improve clarity. * Refactored grobidPreference boolean to grobidUseAsked to improve clarity. Renamed GrobidPreferenceDialogHelper.java to GrobidUseDialogHelper.java. * Update CHANGELOG.md Co-authored-by: Christoph * Updated JabRef_en.properties to reflect language keys: "Send to Grobid" and "Do not send". * Modified Grobid dialog logic such that users are not prompted to enable Grobid on first use, if Grobid has already been manually enabled. * Update src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java Co-authored-by: Loay Ghreeb --------- Co-authored-by: Christoph Co-authored-by: Oliver Kopp Co-authored-by: Loay Ghreeb --- CHANGELOG.md | 1 + .../jabref/gui/entryeditor/EntryEditor.java | 4 ++-- .../gui/fieldeditors/LinkedFilesEditor.java | 4 ++-- ...Helper.java => GrobidUseDialogHelper.java} | 22 ++++++++++--------- .../jabref/gui/importer/ImportCommand.java | 4 ++-- .../websearch/WebSearchTabViewModel.java | 2 +- .../importer/util/GrobidPreferences.java | 22 +++++++++---------- .../preferences/JabRefCliPreferences.java | 8 +++---- src/main/resources/l10n/JabRef_en.properties | 3 +++ 9 files changed, 38 insertions(+), 32 deletions(-) rename src/main/java/org/jabref/gui/importer/{GrobidOptInDialogHelper.java => GrobidUseDialogHelper.java} (63%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e18f7a6b3..1be5603233f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where recently opened files were not displayed in the main menu properly. [#9042](https://github.com/JabRef/jabref/issues/9042) - We fixed an issue where the DOI lookup would show an error when a DOI was found for an entry. [#11850](https://github.com/JabRef/jabref/issues/11850) - We fixed an issue where Tab cannot be used to jump to next field in some single-line fields. [#11785](https://github.com/JabRef/jabref/issues/11785) +- We fixed an issue where the "Do not ask again" checkbox was not working, when asking for permission to use Grobid [koppor#556](https://github.com/koppor/jabref/issues/566). - We fixed an issue where we display warning message for moving attached open files. [#10121](https://github.com/JabRef/jabref/issues/10121) - We fixed an issue where it was not possible to select selecting content of other user's comments.[#11106](https://github.com/JabRef/jabref/issues/11106) - We fixed an issue where web search preferences "Custom API key" table modifications not discarded. [#11925](https://github.com/JabRef/jabref/issues/11925) diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index d2689b2e8b7..b42152dd9a4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -37,7 +37,7 @@ import org.jabref.gui.entryeditor.fileannotationtab.FulltextSearchResultsTab; import org.jabref.gui.externalfiles.ExternalFilesEntryLinker; import org.jabref.gui.help.HelpAction; -import org.jabref.gui.importer.GrobidOptInDialogHelper; +import org.jabref.gui.importer.GrobidUseDialogHelper; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.menus.ChangeEntryTypeMenu; @@ -423,7 +423,7 @@ private void setupToolBar() { if (fetcher instanceof PdfMergeMetadataImporter.EntryBasedFetcherWrapper) { // Handle Grobid Opt-In in case of the PdfMergeMetadataImporter fetcherMenuItem.setOnAction(event -> { - GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); + GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); PdfMergeMetadataImporter.EntryBasedFetcherWrapper pdfMergeMetadataImporter = new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( preferences.getImportFormatPreferences(), diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 5a95b3e42f2..ea30f18d3c9 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -41,7 +41,7 @@ import org.jabref.gui.copyfiles.CopySingleFileAction; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIconView; -import org.jabref.gui.importer.GrobidOptInDialogHelper; +import org.jabref.gui.importer.GrobidUseDialogHelper; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.linkedfile.DeleteFileAction; import org.jabref.gui.linkedfile.LinkedFileEditDialog; @@ -236,7 +236,7 @@ private Node createFileDisplay(LinkedFileViewModel linkedFile) { parsePdfMetadata.setTooltip(new Tooltip(Localization.lang("Parse Metadata from PDF."))); parsePdfMetadata.visibleProperty().bind(linkedFile.isOfflinePdfProperty()); parsePdfMetadata.setOnAction(event -> { - GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); + GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); linkedFile.parsePdfMetadataAndShowMergeDialog(); }); parsePdfMetadata.getStyleClass().setAll("icon-button"); diff --git a/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java b/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java similarity index 63% rename from src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java rename to src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java index 6ef67bc17a0..352f37b7a85 100644 --- a/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java +++ b/src/main/java/org/jabref/gui/importer/GrobidUseDialogHelper.java @@ -7,29 +7,31 @@ /** * Metadata extraction from PDFs and plaintext works very well using Grobid, but we do not want to enable it by default * due to data privacy concerns. - * To make users aware of the feature, we ask each time before querying Grobid, giving the option to opt-out. + * To make users aware of the feature, we ask before querying for the first time Grobid, saving the users' preference */ -public class GrobidOptInDialogHelper { +public class GrobidUseDialogHelper { /** - * If Grobid is not enabled but the user has not explicitly opted-out of Grobid, we ask for permission to send data - * to Grobid using a dialog and giving an opt-out option. + * If the user has not explicitly opted-in/out of Grobid, we ask for permission to send data to Grobid by using + * a dialog. The users' preference is saved. * * @param dialogService the DialogService to use * @return if the user enabled Grobid, either in the past or after being asked by the dialog. */ public static boolean showAndWaitIfUserIsUndecided(DialogService dialogService, GrobidPreferences preferences) { + if (preferences.isGrobidUseAsked()) { + return preferences.isGrobidEnabled(); + } if (preferences.isGrobidEnabled()) { + preferences.setGrobidUseAsked(true); return true; } - if (preferences.isGrobidOptOut()) { - return false; - } - boolean grobidEnabled = dialogService.showConfirmationDialogWithOptOutAndWait( + boolean grobidEnabled = dialogService.showConfirmationDialogAndWait( Localization.lang("Remote services"), Localization.lang("Allow sending PDF files and raw citation strings to a JabRef online service (Grobid) to determine Metadata. This produces better results."), - Localization.lang("Do not ask again"), - optOut -> preferences.setGrobidOptOut(optOut)); + Localization.lang("Send to Grobid"), + Localization.lang("Do not send")); + preferences.setGrobidUseAsked(true); preferences.setGrobidEnabled(grobidEnabled); return grobidEnabled; } diff --git a/src/main/java/org/jabref/gui/importer/ImportCommand.java b/src/main/java/org/jabref/gui/importer/ImportCommand.java index 55469924eb9..896ef2a6145 100644 --- a/src/main/java/org/jabref/gui/importer/ImportCommand.java +++ b/src/main/java/org/jabref/gui/importer/ImportCommand.java @@ -144,7 +144,7 @@ private ParserResult doImport(List files, Importer importFormat) throws IO if (importer.isEmpty()) { // Unknown format UiTaskExecutor.runAndWaitInJavaFXThread(() -> { - if (FileUtil.isPDFFile(filename) && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { + if (FileUtil.isPDFFile(filename) && GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { importFormatReader.reset(); } dialogService.notify(Localization.lang("Importing file %0 as unknown format", filename.getFileName().toString())); @@ -155,7 +155,7 @@ private ParserResult doImport(List files, Importer importFormat) throws IO UiTaskExecutor.runAndWaitInJavaFXThread(() -> { if (((importer.get() instanceof PdfGrobidImporter) || (importer.get() instanceof PdfMergeMetadataImporter)) - && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { + && GrobidUseDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { importFormatReader.reset(); } dialogService.notify(Localization.lang("Importing in %0 format", importer.get().getName()) + "..."); diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java index 550669211d2..06907a2bcb8 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java @@ -164,7 +164,7 @@ public void storeSettings() { filePreferences.setKeepDownloadUrl(shouldkeepDownloadUrl.getValue()); importerPreferences.setDefaultPlainCitationParser(defaultPlainCitationParser.getValue()); grobidPreferences.setGrobidEnabled(grobidEnabledProperty.getValue()); - grobidPreferences.setGrobidOptOut(grobidPreferences.isGrobidOptOut()); + grobidPreferences.setGrobidUseAsked(grobidPreferences.isGrobidUseAsked()); grobidPreferences.setGrobidURL(grobidURLProperty.getValue()); doiPreferences.setUseCustom(useCustomDOIProperty.get()); doiPreferences.setDefaultBaseURI(useCustomDOINameProperty.getValue().trim()); diff --git a/src/main/java/org/jabref/logic/importer/util/GrobidPreferences.java b/src/main/java/org/jabref/logic/importer/util/GrobidPreferences.java index c58c84b4bcd..04de98b71d9 100644 --- a/src/main/java/org/jabref/logic/importer/util/GrobidPreferences.java +++ b/src/main/java/org/jabref/logic/importer/util/GrobidPreferences.java @@ -7,14 +7,14 @@ public class GrobidPreferences { private final BooleanProperty grobidEnabled; - private final BooleanProperty grobidOptOut; + private final BooleanProperty grobidUseAsked; private final StringProperty grobidURL; public GrobidPreferences(boolean grobidEnabled, - boolean grobidOptOut, + boolean grobidUseAsked, String grobidURL) { this.grobidEnabled = new SimpleBooleanProperty(grobidEnabled); - this.grobidOptOut = new SimpleBooleanProperty(grobidOptOut); + this.grobidUseAsked = new SimpleBooleanProperty(grobidUseAsked); this.grobidURL = new SimpleStringProperty(grobidURL); } @@ -30,19 +30,19 @@ public void setGrobidEnabled(boolean grobidEnabled) { this.grobidEnabled.set(grobidEnabled); } - // region: optout; models "Do not ask again" option - public boolean isGrobidOptOut() { - return grobidOptOut.get(); + // region: GrobidUseAsked; + public boolean isGrobidUseAsked() { + return grobidUseAsked.get(); } - public BooleanProperty grobidOptOutProperty() { - return grobidOptOut; + public BooleanProperty grobidUseAskedProperty() { + return grobidUseAsked; } - public void setGrobidOptOut(boolean grobidOptOut) { - this.grobidOptOut.set(grobidOptOut); + public void setGrobidUseAsked(boolean grobidUseAsked) { + this.grobidUseAsked.set(grobidUseAsked); } - // endregion: optout + // endregion: GrobidUseAsked public String getGrobidURL() { return grobidURL.get(); diff --git a/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java b/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java index 27760c02f70..65a531f1356 100644 --- a/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java +++ b/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java @@ -222,7 +222,7 @@ public class JabRefCliPreferences implements CliPreferences { public static final String IMPORTERS_ENABLED = "importersEnabled"; public static final String GENERATE_KEY_ON_IMPORT = "generateKeyOnImport"; public static final String GROBID_ENABLED = "grobidEnabled"; - public static final String GROBID_OPT_OUT = "grobidOptOut"; + public static final String GROBID_PREFERENCE = "grobidPreference"; public static final String GROBID_URL = "grobidURL"; public static final String DEFAULT_CITATION_KEY_PATTERN = "defaultBibtexKeyPattern"; @@ -457,7 +457,7 @@ protected JabRefCliPreferences() { // region: Grobid defaults.put(GROBID_ENABLED, Boolean.FALSE); - defaults.put(GROBID_OPT_OUT, Boolean.FALSE); + defaults.put(GROBID_PREFERENCE, Boolean.FALSE); defaults.put(GROBID_URL, "http://grobid.jabref.org:8070"); // endregion @@ -2189,11 +2189,11 @@ public GrobidPreferences getGrobidPreferences() { grobidPreferences = new GrobidPreferences( getBoolean(GROBID_ENABLED), - getBoolean(GROBID_OPT_OUT), + getBoolean(GROBID_PREFERENCE), get(GROBID_URL)); EasyBind.listen(grobidPreferences.grobidEnabledProperty(), (obs, oldValue, newValue) -> putBoolean(GROBID_ENABLED, newValue)); - EasyBind.listen(grobidPreferences.grobidOptOutProperty(), (obs, oldValue, newValue) -> putBoolean(GROBID_OPT_OUT, newValue)); + EasyBind.listen(grobidPreferences.grobidUseAskedProperty(), (obs, oldValue, newValue) -> putBoolean(GROBID_PREFERENCE, newValue)); EasyBind.listen(grobidPreferences.grobidURLProperty(), (obs, oldValue, newValue) -> put(GROBID_URL, newValue)); return grobidPreferences; diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 94119350460..20c0d3f015a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2804,3 +2804,6 @@ Citation\ Entry=Citation Entry File\ Move\ Errors=File Move Errors Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Could not move file %0. Please close this file and retry. + +Send\ to\ Grobid=Send to Grobid +Do\ not\ send=Do not send From 28fb91e31f45594e3f0b31a269efe5cfdc6d60ab Mon Sep 17 00:00:00 2001 From: "Shun.L" Date: Mon, 28 Oct 2024 09:32:39 +1100 Subject: [PATCH 022/108] Add junit test for text fields check (#12057) * try implement check * add FieldEditorTextAreaTest for check if FieldEditor handle FieldProperty.MULTILINE_TEXT properly * delete unused code * fix issue 11939: Check FieldEditorFX interface, Check new EditorTextArea creation, Check TextInputControl field * try fixes code style issue * try fixes code style issue * try fixes code style issue use check style plugin * try fixes open rewrite issue * 1. Optimize error msg. 2. Rename the test method and file. 3. Use normal exceptions 4. Remove logger * 1. Add additional Javadoc to indicate potential test fragility 2. Optimize indentation and some comments --------- Co-authored-by: Ziying Ye --- build.gradle | 1 + .../FieldEditorsMultilinePropertyTest.java | 236 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 src/test/java/org/jabref/model/entry/field/FieldEditorsMultilinePropertyTest.java diff --git a/build.gradle b/build.gradle index 116feba772b..99d6063c702 100644 --- a/build.gradle +++ b/build.gradle @@ -385,6 +385,7 @@ dependencies { testImplementation "org.testfx:testfx-core:4.0.16-alpha" testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" testImplementation "org.hamcrest:hamcrest-library:3.0" + testImplementation "com.github.javaparser:javaparser-symbol-solver-core:3.26.2" // recommended by https://github.com/wiremock/wiremock/issues/2149#issuecomment-1835775954 testImplementation 'org.wiremock:wiremock-standalone:3.3.1' diff --git a/src/test/java/org/jabref/model/entry/field/FieldEditorsMultilinePropertyTest.java b/src/test/java/org/jabref/model/entry/field/FieldEditorsMultilinePropertyTest.java new file mode 100644 index 00000000000..f41d94549f3 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/field/FieldEditorsMultilinePropertyTest.java @@ -0,0 +1,236 @@ +package org.jabref.model.entry.field; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.stmt.IfStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FieldEditorsMultilinePropertyTest { + + private static final Pattern FIELD_PROPERTY_PATTERN = Pattern.compile("fieldProperties\\.contains\\s*\\(\\s*FieldProperty\\.(\\w+)\\s*\\)"); + private static final Pattern STANDARD_FIELD_PATTERN = Pattern.compile("==\\s*StandardField\\.(\\w+)"); + private static final Pattern INTERNAL_FIELD_PATTERN = Pattern.compile("==\\s*InternalField\\.(\\w+)"); + private static JavaParser PARSER; + + @BeforeAll + public static void setUp() { + ParserConfiguration configuration = new ParserConfiguration(); + configuration.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); + PARSER = new JavaParser(configuration); + } + + /** + * This test is somewhat fragile, as it depends on the structure of FieldEditors.java. + * If the structure of FieldEditors.java is changed, this test might fail. + * This test performs the following steps: + * 1. Use Java parser to parse FieldEditors.java and check all if statements in the getForField method. + * 2. Match the conditions of if statements to extract the field properties. + * 3. Match the created FieldEditor class name with field properties extracted from step 2. This creates a map where: + * - The key is the file path of the FieldEditor class (for example: ....UrlEditor.java) + * - The value is the list of properties of the FieldEditor class (for example: [FieldProperty.EXTERNAL]) + * 4. For every class in the map, when its properties contain MULTILINE_TEXT, check whether it: + * a) Holds a TextInputControl field + * b) Has an EditorTextArea object creation + */ + @Test + public void fieldEditorsMatchMultilineProperty() throws Exception { + Map> result = getEditorsWithPropertiesInFieldEditors(); + for (Map.Entry> entry : result.entrySet()) { + // Now we have the file path and its properties, going to analyze the target Editor class + Path filePath = entry.getKey(); + List properties = entry.getValue(); + CompilationUnit cu = PARSER.parse(filePath) + .getResult() + .orElseThrow(() -> + new NullPointerException("Failed to parse " + + filePath + + ", java parser returned null CompilationUnit" + + ", please check if the file exists")); + + if (!implementedFieldEditorFX(cu)) { + continue; // Make sure the class implements FieldEditorFX interface + } + + if (properties.contains(FieldProperty.MULTILINE_TEXT)) { + // If the editor has MULTILINE_TEXT property, we are going to check if the class holds a `TextInputControl` field + // and have performed Text Area creation + assertTrue(holdTextInputControlField(cu) && hasEditorTextAreaCreationExisted(cu), + "Class " + filePath + " should hold a TextInputControl field and have EditorTextArea creation"); + } + } + } + + /** + * Parse FieldEditors.java to get all field editors and their properties in function getForField + * + * @return a map of field editor file path and its properties + */ + private static Map> getEditorsWithPropertiesInFieldEditors() throws Exception { + final String filePath = "src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java"; + Map> result = new HashMap<>(); + CompilationUnit cu = PARSER.parse(Paths.get(filePath)) + .getResult() + .orElseThrow(() -> + new NullPointerException("Failed to parse FieldEditors.java")); + + // Locate getForField method in FieldEditors.java + MethodDeclaration getForFieldCall = cu.findAll(MethodDeclaration.class).stream() + .filter(methodDeclaration -> "getForField".equals(methodDeclaration.getNameAsString())) + .findFirst() + .orElseThrow(() -> new Exception("Failed to find getForField method in FieldEditors.java")); + + // Analyze all if statements in getForField method + getForFieldCall.findAll(IfStmt.class).forEach(ifStmt -> { + String condition = ifStmt.getCondition().toString(); + List properties = new ArrayList<>(); + // Match `fieldProperties.contains(FieldProperty.XXX)` + Matcher propertyMatcher = FIELD_PROPERTY_PATTERN.matcher(condition); + while (propertyMatcher.find()) { + String propertyName = propertyMatcher.group(1); + FieldProperty property = FieldProperty.valueOf(propertyName); + properties.add(property); + } + // Match `== StandardField.XXX` + Matcher standardFieldMatcher = STANDARD_FIELD_PATTERN.matcher(condition); + if (standardFieldMatcher.find()) { + String fieldName = standardFieldMatcher.group(1); + StandardField standardField = StandardField.valueOf(fieldName); + properties.addAll(standardField.getProperties()); + } + // Match `== InternalField.XXX` + Matcher internalFieldMatcher = INTERNAL_FIELD_PATTERN.matcher(condition); + if (internalFieldMatcher.find()) { + String fieldName = internalFieldMatcher.group(1); + InternalField internalField = InternalField.valueOf(fieldName); + properties.addAll(internalField.getProperties()); + } + + // Check if the return statement contains an object creation + // If so, extract the created class name and its path + ifStmt.getThenStmt().stream() + .filter(ReturnStmt.class::isInstance) + .map(ReturnStmt.class::cast) + .findFirst() + .flatMap(returnStmt -> + // Try to find the object creation in the return statement + returnStmt.stream() + .filter(ObjectCreationExpr.class::isInstance) + .map(ObjectCreationExpr.class::cast) + .findFirst()).ifPresent(creationExpr -> { + String createdClassName = creationExpr.getTypeAsString().replace("<>", ""); + cu.findAll(ImportDeclaration.class) + .stream() + .filter(importDeclaration -> importDeclaration.getNameAsString().endsWith(createdClassName)) + .findFirst() + .ifPresentOrElse(importDeclaration -> { + String classPath = importDeclaration.getNameAsString(); + Path classFilePath = Paths.get("src/main/java/" + classPath.replace(".", "/") + ".java"); + result.put(classFilePath, properties); + }, () -> { + Path classFilePath = Paths.get("src/main/java/org/jabref/gui/fieldeditors/" + createdClassName + ".java"); + result.put(classFilePath, properties); + }); + }); + }); + + return result; + } + + /** + * Check if the class implements FieldEditorFX interface + * + * @param cu CompilationUnit + * @return true if the class implements FieldEditorFX interface + */ + private static boolean implementedFieldEditorFX(CompilationUnit cu) { + return cu.findAll(ClassOrInterfaceDeclaration.class).stream() + .anyMatch(classDecl -> classDecl.getImplementedTypes().stream() + .anyMatch(type -> Objects.equals("FieldEditorFX", type.getNameAsString()))); + } + + /** + * Check if the class has a new EditorTextArea creation + * + * @param cu CompilationUnit + * @return true if the class has a new EditorTextArea creation + */ + private static boolean hasEditorTextAreaCreationExisted(CompilationUnit cu) { + return cu.findAll(ObjectCreationExpr.class).stream() + .anyMatch(creation -> Objects.equals("EditorTextArea", creation.getType().toString())); + } + + /** + * Check if the class holds a TextInputControl field + * + * @param cu CompilationUnit + * @return true if the class holds a TextInputControl field + */ + private static boolean holdTextInputControlField(CompilationUnit cu) { + // Since the class implements FieldEditorFX, we are going to check the first parameter when call + // establishBinding method, which should be a TextInputControl + AtomicBoolean hasTextInputControlField = new AtomicBoolean(false); + cu.findAll(MethodCallExpr.class) + .stream() + .filter(methodCallExpr -> "establishBinding".equals(methodCallExpr.getNameAsString())) + .findFirst() + .ifPresent(methodCallExpr -> { + if (!methodCallExpr.getArguments().isEmpty()) { + String firstArgument = methodCallExpr.getArgument(0).toString(); + cu.findAll(FieldDeclaration.class) + .stream() + .filter(fieldDeclaration -> fieldDeclaration.getVariables().stream() + .anyMatch(variableDeclarator -> variableDeclarator.getNameAsString().equals(firstArgument))) + .findFirst() + .ifPresent(fieldDeclaration -> { + String classType = fieldDeclaration.getElementType().asString(); + if ("TextInputControl".equals(classType)) { + hasTextInputControlField.set(true); + } + }); + } + }); + return hasTextInputControlField.get(); + } + + private static boolean holdEditorTextField(CompilationUnit compilationUnit) { + AtomicBoolean hasEditorTextField = new AtomicBoolean(false); + compilationUnit.findAll(MethodCallExpr.class).stream() + .filter(methodCallExpr -> "establishBinding".equals(methodCallExpr.getNameAsString())) + .findFirst() + .ifPresent(establishBindingCall -> { + String firstArg = establishBindingCall.getArgument(0).toString(); + compilationUnit.findAll(FieldDeclaration.class).stream() + .filter(field -> field.getVariable(0).getNameAsString().equals(firstArg)) + .findFirst() + .ifPresent(fieldDeclaration -> { + String fieldType = fieldDeclaration.getElementType().asString(); + if ("EditorTextField".equals(fieldType)) { + hasEditorTextField.set(true); + } + }); + }); + return hasEditorTextField.get(); + } +} From b9f395118eaa21168855a9bc6eab27198bacef7d Mon Sep 17 00:00:00 2001 From: u7676493 Date: Mon, 28 Oct 2024 10:05:36 +1100 Subject: [PATCH 023/108] Auto relativize PDF file paths when selecting "Search for unlinked local files" (#12088) * When using "Find Unlinked Files" to import files, they will now be relativized. * Removes debug lines * Removes debug lines * Uses the strict return type on pdfImporterResult Removes unnecessary comments * Renamed preferences to filePreferences in importDatabase and importPDFContent * Update CHANGELOG.md to include changes * Update description of PdfMergeMetadataImporter.importDatabase It now correctly states which function is called to produce the return value * Update PdfMergeMetadataImporter.importDatabase Removed unnecessary if statement --------- Co-authored-by: Oliver Kopp Co-authored-by: Subhramit Basu Bhowmick --- CHANGELOG.md | 5 ++-- .../gui/externalfiles/ImportHandler.java | 3 ++- .../ExternalFilesContentImporter.java | 6 +++-- .../fileformat/PdfMergeMetadataImporter.java | 19 +++++++++++++++ .../PdfMergeMetadataImporterTest.java | 24 +++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be5603233f..8f4e08081bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,8 +69,9 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We changed instances of 'Search Selected' to 'Search Pre-configured' in Web Search Preferences UI. [#11871](https://github.com/JabRef/jabref/pull/11871) - We added a new CSS style class `main-table` for the main table. [#11881](https://github.com/JabRef/jabref/pull/11881) - When renaming a file, the old extension is now used if there is none provided in the new name. [#11903](https://github.com/JabRef/jabref/issues/11903) -- Added minimum window sizing for windows dedicated to creating new entries [#11944](https://github.com/JabRef/jabref/issues/11944) -- We changed the name of the library-based file directory from 'General File Directory' to 'Library-specific File Directory' per issue [#571](https://github.com/koppor/jabref/issues/571) +- When importing a file using "Find Unlinked Files", when one or more file directories are available, the file path will be relativized where possible [koppor#549](https://github.com/koppor/jabref/issues/549) +- We added minimum window sizing for windows dedicated to creating new entries [#11944](https://github.com/JabRef/jabref/issues/11944) +- We changed the name of the library-based file directory from 'General File Directory' to 'Library-specific File Directory' per issue. [#571](https://github.com/koppor/jabref/issues/571) - The CitationKey column is now a default shown column for the entry table. [#10510](https://github.com/JabRef/jabref/issues/10510) ### Fixed diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index 9be3e860390..ef6774c358d 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -35,6 +35,7 @@ import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.importer.ImportFormatReader.UnknownFormatImport; import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BackgroundTask; @@ -127,7 +128,7 @@ public List call() { try { if (FileUtil.isPDFFile(file)) { - var pdfImporterResult = contentImporter.importPDFContent(file); + ParserResult pdfImporterResult = contentImporter.importPDFContent(file, bibDatabaseContext, filePreferences); List pdfEntriesInFile = pdfImporterResult.getDatabase().getEntries(); if (pdfImporterResult.hasWarnings()) { diff --git a/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java b/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java index e75b1df2683..7a868577c42 100644 --- a/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java +++ b/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java @@ -3,10 +3,12 @@ import java.io.IOException; import java.nio.file.Path; +import org.jabref.logic.FilePreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.PdfMergeMetadataImporter; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; public class ExternalFilesContentImporter { @@ -17,9 +19,9 @@ public ExternalFilesContentImporter(ImportFormatPreferences importFormatPreferen this.importFormatPreferences = importFormatPreferences; } - public ParserResult importPDFContent(Path file) { + public ParserResult importPDFContent(Path file, BibDatabaseContext context, FilePreferences filePreferences) { try { - return new PdfMergeMetadataImporter(importFormatPreferences).importDatabase(file); + return new PdfMergeMetadataImporter(importFormatPreferences).importDatabase(file, context, filePreferences); } catch (IOException e) { return ParserResult.fromError(e); } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporter.java index da023360ad0..21b18e27f84 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporter.java @@ -136,6 +136,25 @@ public ParserResult importDatabase(Path filePath) throws IOException { return new ParserResult(List.of(entry)); } + /** + * A modified version of {@link PdfMergeMetadataImporter#importDatabase(Path)}, but it + * relativizes the {@code filePath} if there are working directories before parsing it + * into {@link PdfMergeMetadataImporter#importDatabase(Path)} + * (Otherwise no path modification happens). + * + * @param filePath The unrelativized {@code filePath}. + */ + public ParserResult importDatabase(Path filePath, BibDatabaseContext context, FilePreferences filePreferences) throws IOException { + Objects.requireNonNull(context); + Objects.requireNonNull(filePreferences); + + List directories = context.getFileDirectories(filePreferences); + + filePath = FileUtil.relativize(filePath, directories); + + return importDatabase(filePath); + } + @Override public String getName() { return "PDF meta data merger"; diff --git a/src/test/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporterTest.java index 3b2f561edd6..ca6c3d42dd5 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/PdfMergeMetadataImporterTest.java @@ -6,9 +6,11 @@ import javafx.collections.FXCollections; +import org.jabref.logic.FilePreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.util.GrobidPreferences; import org.jabref.logic.util.StandardFileType; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; @@ -84,4 +86,26 @@ void importWorksAsExpected() throws Exception { assertEquals(Collections.singletonList(expected), result); } + + @Test + void importRelativizesFilePath() throws Exception { + // Initialize database and preferences + FilePreferences preferences = mock(FilePreferences.class); + BibDatabaseContext database = new BibDatabaseContext(); + + // Initialize file and working directory + Path file = Path.of(PdfMergeMetadataImporter.class.getResource("/pdfs/minimal.pdf").toURI()); + Path directory = Path.of(PdfMergeMetadataImporter.class.getResource("/pdfs/").toURI()); + preferences.setWorkingDirectory(directory); + + List result = importer.importDatabase(file, database, preferences).getDatabase().getEntries(); + + BibEntry expected = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "1 ") + .withField(StandardField.TITLE, "Hello World") + // Expecting relative path + .withField(StandardField.FILE, ":minimal.pdf:PDF"); + + assertEquals(List.of(expected), result); + } } From 3140414a67b24267fee7e1b795959c6967e48c95 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 28 Oct 2024 14:46:15 +0100 Subject: [PATCH 024/108] New Crowdin updates (#12113) * New translations jabref_en.properties (French) * New translations jabref_en.properties (Polish) * New translations jabref_en.properties (Portuguese, Brazilian) * New translations jabref_en.properties (Spanish) * New translations jabref_en.properties (Arabic) * New translations jabref_en.properties (Danish) * New translations jabref_en.properties (German) * New translations jabref_en.properties (Greek) * New translations jabref_en.properties (Finnish) * New translations jabref_en.properties (Italian) * New translations jabref_en.properties (Japanese) * New translations jabref_en.properties (Korean) * New translations jabref_en.properties (Dutch) * New translations jabref_en.properties (Norwegian) * New translations jabref_en.properties (Portuguese) * New translations jabref_en.properties (Russian) * New translations jabref_en.properties (Swedish) * New translations jabref_en.properties (Turkish) * New translations jabref_en.properties (Ukrainian) * New translations jabref_en.properties (Chinese Simplified) * New translations jabref_en.properties (Chinese Traditional) * New translations jabref_en.properties (Vietnamese) * New translations jabref_en.properties (Indonesian) * New translations jabref_en.properties (Persian) * New translations jabref_en.properties (Tagalog) --- src/main/resources/l10n/JabRef_ar.properties | 1 + src/main/resources/l10n/JabRef_da.properties | 1 + src/main/resources/l10n/JabRef_de.properties | 1 + src/main/resources/l10n/JabRef_el.properties | 1 + src/main/resources/l10n/JabRef_es.properties | 1 + src/main/resources/l10n/JabRef_fa.properties | 1 + src/main/resources/l10n/JabRef_fi.properties | 1 + src/main/resources/l10n/JabRef_fr.properties | 1 + src/main/resources/l10n/JabRef_id.properties | 1 + src/main/resources/l10n/JabRef_it.properties | 3 +++ src/main/resources/l10n/JabRef_ja.properties | 1 + src/main/resources/l10n/JabRef_ko.properties | 1 + src/main/resources/l10n/JabRef_nl.properties | 1 + src/main/resources/l10n/JabRef_no.properties | 1 + src/main/resources/l10n/JabRef_pl.properties | 3 +++ src/main/resources/l10n/JabRef_pt.properties | 1 + src/main/resources/l10n/JabRef_pt_BR.properties | 16 ++++++++++++++++ src/main/resources/l10n/JabRef_ru.properties | 1 + src/main/resources/l10n/JabRef_sv.properties | 1 + src/main/resources/l10n/JabRef_tl.properties | 1 + src/main/resources/l10n/JabRef_tr.properties | 1 + src/main/resources/l10n/JabRef_uk.properties | 1 + src/main/resources/l10n/JabRef_vi.properties | 1 + src/main/resources/l10n/JabRef_zh_CN.properties | 1 + src/main/resources/l10n/JabRef_zh_TW.properties | 1 + 25 files changed, 44 insertions(+) diff --git a/src/main/resources/l10n/JabRef_ar.properties b/src/main/resources/l10n/JabRef_ar.properties index d94abe33f65..232cd19ac81 100644 --- a/src/main/resources/l10n/JabRef_ar.properties +++ b/src/main/resources/l10n/JabRef_ar.properties @@ -722,5 +722,6 @@ File\ not\ found=لم يتم العثور على الملف + diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index b7b4662f889..305b53e8601 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -1013,5 +1013,6 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 39bf80ebe97..507b89965db 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2761,3 +2761,4 @@ Currently\ selected\ CSL\ Style\:\ '%0' = Derzeit ausgewählter CSL-Stil\: '%0' Store\ url\ for\ downloaded\ file=Url für heruntergeladene Datei speichern + diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 022f17f0a1b..a56213187e2 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -1793,3 +1793,4 @@ Related\ articles=Σχετικά άρθρα + diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 2df9ba9868c..95624d65a33 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2485,3 +2485,4 @@ Warning\:\ The\ selected\ directory\ is\ not\ a\ valid\ directory.=Atención\: e + diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 6baac9fb5eb..25db5559bb3 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -658,5 +658,6 @@ Auto\ complete\ enabled.=تکمیل خودکار غیرفعال شد. + diff --git a/src/main/resources/l10n/JabRef_fi.properties b/src/main/resources/l10n/JabRef_fi.properties index d79921567de..9afae081d9f 100644 --- a/src/main/resources/l10n/JabRef_fi.properties +++ b/src/main/resources/l10n/JabRef_fi.properties @@ -602,5 +602,6 @@ Proxy\ requires\ password=Välityspalvelin vaatii salasanan + diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 11ae9099321..266db0f759b 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2802,3 +2802,4 @@ Citation\ Entry=Entrée de citation File\ Move\ Errors=Erreurs de déplacement de fichier Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Impossible de déplacer le fichier %0. Veuillez fermer ce fichier et réessayer. + diff --git a/src/main/resources/l10n/JabRef_id.properties b/src/main/resources/l10n/JabRef_id.properties index b59b717dadd..ada89f21b92 100644 --- a/src/main/resources/l10n/JabRef_id.properties +++ b/src/main/resources/l10n/JabRef_id.properties @@ -1557,3 +1557,4 @@ Related\ articles=Artikel terkait + diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 28f9443c387..35318a868a7 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2773,3 +2773,6 @@ Citation\ Entry=Voce della Citazione File\ Move\ Errors=Errori di spostamente dei file Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Impossibile spostare il file %0. Chiudere questo file e riprovare. + +Send\ to\ Grobid=Invia a Grobid +Do\ not\ send=Non inviare diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index f5d4bdc77e9..7a157deaa0d 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2359,3 +2359,4 @@ Related\ articles=関連文献 + diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index d8a8f74b702..acf72675c6a 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -2193,3 +2193,4 @@ Related\ articles=관련 글 + diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 6f8133acd5e..698971977de 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2458,3 +2458,4 @@ Related\ articles=Gerelateerde artikelen + diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 797de9bac31..96b6b902b6d 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -1124,5 +1124,6 @@ Path\ to\ %0=Sti til %0 + diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index 5b79bf595cf..af09af0c07c 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -1895,3 +1895,6 @@ Citation\ Entry=Wpis cytatowania File\ Move\ Errors=Błąd przenoszenia pliku Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Nie można przenieść pliku %0. Zamknij ten plik i spróbuj ponownie. + +Send\ to\ Grobid=Wyślij do Grobid +Do\ not\ send=Nie wysyłaj diff --git a/src/main/resources/l10n/JabRef_pt.properties b/src/main/resources/l10n/JabRef_pt.properties index c48f6b7dcaf..c236fe67580 100644 --- a/src/main/resources/l10n/JabRef_pt.properties +++ b/src/main/resources/l10n/JabRef_pt.properties @@ -1374,3 +1374,4 @@ Related\ articles=Artigos relacionados + diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index a3fe097762e..b9c3105331f 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -103,8 +103,11 @@ Available\ import\ formats=Formatos de importação disponíveis Show\ BibTeX\ source=Mostrar fonte BibTeX Show/edit\ %0\ source=Exibir/editar fonte %0 +Background\ tasks=Tarefas em segundo plano +Background\ tasks\ are\ running=Tarefas em segundo plano estão sendo executadas +Background\ tasks\ are\ finished=Tarefas em segundo plano finalizadas Browse=Explorar @@ -316,6 +319,8 @@ Extract\ References\ (online)=Extrair Referências (online) Processing...=Processando... Processing\ "%0"...=Processando "%0"... Processing\ PDF(s)=Processando PDF(s) +Processing\ %0=Processando %0 +Importing\ files\ into\ %1\ |\ %2\ of\ %0\ file(s)\ processed.=Importando arquivos para %1 | %2 arquivo(s) de %0 processado(s). Processing\ a\ large\ number\ of\ files=Processando um grande número de arquivos You\ are\ about\ to\ process\ %0\ files.\ Continue?=Você está prestes a processar %0 arquivos. Continuar? @@ -485,6 +490,8 @@ Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Grupo I\ Agree=Concordo Indexing\ bib\ fields\ for\ %0=Indexando campos bib para %0 +Indexing\ %0=Indexando %0 +Indexing\ files\ for\ %1\ |\ %2\ of\ %0\ file(s)\ indexed.=Indexando arquivos para %1 | %2 arquivo(s) de %0 indexado. %0\ of\ %1\ entries\ added\ to\ the\ index.=%0 de %1 entradas adicionadas ao índice. %0\ of\ %1\ entries\ removed\ from\ the\ index.=%0 dos %1 registros removidos do índice. Removing\ entries\ from\ index\ for\ %0=Removendo entradas do índice %0 @@ -819,6 +826,7 @@ Searching\ for\ files=Procurando por arquivos... Use\ regular\ expression\ search=Utilizar pesquisa por expressão regular search\ expression=expressão de pesquisa Free\ search\ expression=Expressão de busca livre +Illegal\ search\ expression=Expressão de busca ilegal No\ search\ matches.=Sem correspondências. Web\ search=Pesquisa na Web Search\ results=Resultados da pesquisa @@ -833,6 +841,7 @@ Clear\ search=Limpar busca Search\ document\ identifier\ online=Pesquisar identificador de documento na web Search\ for\ unlinked\ local\ files=Procurar por arquivos locais desvinculados Search\ full\ text\ documents\ online=Pesquisar textos completos on-line +Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\=Smith\ AND\ title\=electrical=Dica\:\n\nPara pesquisar em todos os campos por Smith, entre\:\nSmith\n\nPara pesquisar no campo autor por Smith e no campo título porelétrico, Enter\:\nautor\=Smith AND título\=elétrico Search\ term\ is\ empty.=O termo de pesquisa está vazio. Invalid\ regular\ expression.=Expressão regular inválida. Searching\ for\ a\ keyword=Procurando palavras-chave @@ -993,6 +1002,7 @@ Could\ not\ move\ file\ '%0'.=Não foi possível mover o arquivo '%0'. Could\ not\ find\ file\ '%0'.=Não foi possível encontrar o arquivo '%0'. Error\ opening\ file=Erro ao abrir o arquivo Error\ opening\ file\ '%0'=Erro ao abrir arquivo '%0' +File\ '%0'\ already\ linked=Arquivo '%0' já vinculado Number\ of\ entries\ successfully\ imported=Número de referências importadas com sucesso Error\ while\ fetching\ from\ %0=Erro ao recuperar do %0 @@ -1044,6 +1054,7 @@ Waiting\ for\ background\ tasks\ to\ finish.\ Quit\ anyway?=Aguardando a finaliz Find\ and\ remove\ duplicate\ citation\ keys=Localizar e remover chaves de citação duplicadas Expected\ syntax\ for\ --fetch\='\:'=Sintaxe esperada para --fetch\='\:' +Library-specific\ file\ directory=Diretório de arquivos específicos da biblioteca User-specific\ file\ directory=Diretório de arquivo específico do usuário LaTeX\ file\ directory=Diretório de arquivos LaTeX @@ -2788,3 +2799,8 @@ Compare\ with\ existing\ entry=Comparar com entrada existente Library\ Entry=Registro de Biblioteca Citation\ Entry=Chave de citação +File\ Move\ Errors=Erro ao Mover Arquivo +Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Não foi possível mover o arquivo %0. Por favor, feche este arquivo e tente novamente. + +Send\ to\ Grobid=Enviar para Grobid +Do\ not\ send=Não enviar diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 9167363de1c..aee9cf4fad0 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2327,3 +2327,4 @@ Related\ articles=Связанные статьи + diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 5f4e23f1788..470dc3577f6 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -1521,3 +1521,4 @@ Warning\:\ The\ selected\ directory\ is\ not\ a\ valid\ directory.=Varning\: Den + diff --git a/src/main/resources/l10n/JabRef_tl.properties b/src/main/resources/l10n/JabRef_tl.properties index 8c8201598b9..17ec5f285a7 100644 --- a/src/main/resources/l10n/JabRef_tl.properties +++ b/src/main/resources/l10n/JabRef_tl.properties @@ -1254,3 +1254,4 @@ Related\ articles=Kaugnay na mga artikulo + diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 3941a77a08a..82ecc75e33d 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2512,3 +2512,4 @@ More\ options...=Daha fazla seçenekler... + diff --git a/src/main/resources/l10n/JabRef_uk.properties b/src/main/resources/l10n/JabRef_uk.properties index d06e34795db..5cc234c1557 100644 --- a/src/main/resources/l10n/JabRef_uk.properties +++ b/src/main/resources/l10n/JabRef_uk.properties @@ -669,5 +669,6 @@ Proxy\ requires\ password=Потрібен пароль проксі-серве + diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 38240e5c7f2..bbb77af2c96 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -1058,5 +1058,6 @@ Path\ to\ %0=Đường dẫn đến %0 + diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 6a6182a04cf..05322f51a14 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -2445,3 +2445,4 @@ Related\ articles=相关文章 + diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties index 7dabbae2b0c..9df6e435673 100644 --- a/src/main/resources/l10n/JabRef_zh_TW.properties +++ b/src/main/resources/l10n/JabRef_zh_TW.properties @@ -1047,3 +1047,4 @@ Related\ articles=相關文章 + From a6dd892f051d0a12f1b00de31088b0cd848c286b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:34:48 +0000 Subject: [PATCH 025/108] Bump org.apache.logging.log4j:log4j-to-slf4j from 2.24.0 to 2.24.1 (#12116) Bumps org.apache.logging.log4j:log4j-to-slf4j from 2.24.0 to 2.24.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-to-slf4j dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 99d6063c702..b71357e823d 100644 --- a/build.gradle +++ b/build.gradle @@ -273,7 +273,7 @@ dependencies { // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) implementation 'org.slf4j:jul-to-slf4j:2.0.16' // route all requests to log4j to SLF4J - implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.0' + implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.1' implementation('de.undercouch:citeproc-java:3.1.0') { exclude group: 'org.antlr' From 2b41d78da0b8a6c3df44dec33fbdea6480dcc3d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:37:01 +0000 Subject: [PATCH 026/108] Bump com.dlsc.gemsfx:gemsfx from 2.60.0 to 2.63.0 (#12117) Bumps [com.dlsc.gemsfx:gemsfx](https://github.com/dlsc-software-consulting-gmbh/GemsFX) from 2.60.0 to 2.63.0. - [Release notes](https://github.com/dlsc-software-consulting-gmbh/GemsFX/releases) - [Changelog](https://github.com/dlsc-software-consulting-gmbh/GemsFX/blob/master/jreleaser.yml) - [Commits](https://github.com/dlsc-software-consulting-gmbh/GemsFX/compare/v2.60.0...v2.63.0) --- updated-dependencies: - dependency-name: com.dlsc.gemsfx:gemsfx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b71357e823d..15d0f6ff228 100644 --- a/build.gradle +++ b/build.gradle @@ -238,7 +238,7 @@ dependencies { } implementation 'org.fxmisc.flowless:flowless:0.7.3' implementation 'org.fxmisc.richtext:richtextfx:0.11.3' - implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.60.0') { + implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.63.0') { exclude module: 'javax.inject' // Split package, use only jakarta.inject exclude module: 'commons-lang3' exclude group: 'org.apache.commons.validator' From f48283765740d8b0db14f9084642591fa6982d2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:46:04 +0000 Subject: [PATCH 027/108] Bump org.apache.commons:commons-csv from 1.11.0 to 1.12.0 (#12118) Bumps [org.apache.commons:commons-csv](https://github.com/apache/commons-csv) from 1.11.0 to 1.12.0. - [Changelog](https://github.com/apache/commons-csv/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-csv/compare/rel/commons-csv-1.11.0...rel/commons-csv-1.12.0) --- updated-dependencies: - dependency-name: org.apache.commons:commons-csv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 15d0f6ff228..284763d3869 100644 --- a/build.gradle +++ b/build.gradle @@ -173,7 +173,7 @@ dependencies { implementation "org.apache.lucene:lucene-analysis-common:$luceneVersion" implementation "org.apache.lucene:lucene-highlighter:$luceneVersion" - implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.11.0' + implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.12.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0' implementation group: 'org.apache.commons', name: 'commons-text', version: '1.12.0' implementation 'commons-logging:commons-logging:1.3.4' From 1f839bb3f69dbe167ea0181f9a034b533c7de7d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:46:49 +0000 Subject: [PATCH 028/108] Bump com.fasterxml.jackson.dataformat:jackson-dataformat-yaml (#12119) Bumps [com.fasterxml.jackson.dataformat:jackson-dataformat-yaml](https://github.com/FasterXML/jackson-dataformats-text) from 2.17.2 to 2.18.0. - [Commits](https://github.com/FasterXML/jackson-dataformats-text/compare/jackson-dataformats-text-2.17.2...jackson-dataformats-text-2.18.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 284763d3869..5a364835b36 100644 --- a/build.gradle +++ b/build.gradle @@ -200,7 +200,7 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '7.0.0.202409031743-r' - implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.17.2' + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.18.0' implementation 'com.fasterxml:aalto-xml:1.3.3' From 6ae0c2a36ac16d70cf2d26b98e01b59176d9b09b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:52:00 +0000 Subject: [PATCH 029/108] Bump src/main/resources/csl-styles from `5cfc7ae` to `1931353` (#12121) Bumps [src/main/resources/csl-styles](https://github.com/citation-style-language/styles) from `5cfc7ae` to `1931353`. - [Release notes](https://github.com/citation-style-language/styles/releases) - [Commits](https://github.com/citation-style-language/styles/compare/5cfc7ae46c1d596351d75aaa11ab06e98e0aeeaf...1931353cec337cb62ef3f9049a78e4f91a3834b9) --- updated-dependencies: - dependency-name: src/main/resources/csl-styles dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/main/resources/csl-styles | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 5cfc7ae46c1..1931353cec3 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 5cfc7ae46c1d596351d75aaa11ab06e98e0aeeaf +Subproject commit 1931353cec337cb62ef3f9049a78e4f91a3834b9 From fdf8685658a10bff8209b4cfb787398b4dd669d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:14:30 +0100 Subject: [PATCH 030/108] Bump buildres/abbrv.jabref.org from `50edbd5` to `1ad9773` (#12120) Bumps [buildres/abbrv.jabref.org](https://github.com/JabRef/abbrv.jabref.org) from `50edbd5` to `1ad9773`. - [Release notes](https://github.com/JabRef/abbrv.jabref.org/releases) - [Commits](https://github.com/JabRef/abbrv.jabref.org/compare/50edbd56165d8efa27be7d06ff1df5d20aa8d2c7...1ad9773b13c37a919207a0e412c6615f42453e99) --- updated-dependencies: - dependency-name: buildres/abbrv.jabref.org dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- buildres/abbrv.jabref.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index 50edbd56165..1ad9773b13c 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit 50edbd56165d8efa27be7d06ff1df5d20aa8d2c7 +Subproject commit 1ad9773b13c37a919207a0e412c6615f42453e99 From 108a6d1b15667595b52f02df69833cc31479de91 Mon Sep 17 00:00:00 2001 From: Loay Ghreeb Date: Tue, 29 Oct 2024 21:16:06 +0300 Subject: [PATCH 031/108] Fix adding new entry (#12123) --- src/main/java/org/jabref/gui/externalfiles/ImportHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index ef6774c358d..b6c7bb7c5f3 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -215,7 +215,6 @@ public void importEntries(List entries) { } public void importCleanedEntries(List entries) { - entries = entries.stream().map(entry -> (BibEntry) entry.clone()).toList(); bibDatabaseContext.getDatabase().insertEntries(entries); generateKeys(entries); setAutomaticFields(entries); From d5861022e8572661e4ca4e36d97e53ca9b43ffa0 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 29 Oct 2024 23:26:40 +0100 Subject: [PATCH 032/108] Refine hint on pasting keys. (#12102) * Refine hint on pasting keys. * Update src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- .../jabref/logic/l10n/LocalizationConsistencyTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java b/src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java index e88a90f5c5c..a7a64fa1157 100644 --- a/src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java +++ b/src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java @@ -133,8 +133,10 @@ void findMissingLocalizationKeys() throws IOException { .collect(Collectors.joining("\n", """ - DETECTED LANGUAGE KEYS WHICH ARE NOT IN THE ENGLISH LANGUAGE FILE - PASTE THESE INTO THE ENGLISH LANGUAGE FILE "JabRef_en.properties" + DETECTED LANGUAGE KEYS WHICH ARE NOT IN THE ENGLISH LANGUAGE FILE. + PASTE THESE INTO THE ENGLISH LANGUAGE FILE "JabRef_en.properties". + Search for a proper place; typically related keys are grouped together. + If a similar key is already present, please adapt your language instead of adding load to translators by adding a new key. """, "\n\n"))); @@ -148,8 +150,8 @@ void findObsoleteLocalizationKeys() throws IOException { "Obsolete keys found in language properties file: \n\n", """ - 1. CHECK IF THE KEY IS REALLY NOT USED ANYMORE - 2. REMOVE THESE FROM THE ENGLISH LANGUAGE FILE "JabRef_en.properties" + 1. CHECK IF THE KEY IS REALLY NOT USED ANYMORE. + 2. REMOVE THESE FROM THE ENGLISH LANGUAGE FILE "JabRef_en.properties". """)) ); From aaff6f5e56cd6c1ece463da95dd9acb516cb19f5 Mon Sep 17 00:00:00 2001 From: Loay Ghreeb Date: Wed, 30 Oct 2024 11:59:12 +0300 Subject: [PATCH 033/108] Improve `MainTable#findEntry` (#12126) * Improve `MainTable#findEntry` * Return findEntry method --- src/main/java/org/jabref/gui/maintable/MainTable.java | 5 +---- .../org/jabref/gui/maintable/MainTableDataModel.java | 11 +++++++++++ .../java/org/jabref/model/database/BibDatabase.java | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 4d66704856f..b0bcb2c70a9 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -490,10 +490,7 @@ public List getSelectedEntries() { } private Optional findEntry(BibEntry entry) { - return model.getEntriesFilteredAndSorted() - .stream() - .filter(viewModel -> viewModel.getEntry().equals(entry)) - .findFirst(); + return model.getViewModelByIndex(database.getDatabase().indexOf(entry)); } public void setCitationMergeMode(boolean citationMerge) { diff --git a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index 4c4d6a11c75..e758516ade9 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -38,10 +38,13 @@ import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.Subscription; import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.jabref.model.search.PostgreConstants.ENTRY_ID; public class MainTableDataModel { + private final Logger LOGGER = LoggerFactory.getLogger(MainTableDataModel.class); private final ObservableList entriesViewModel; private final FilteredList entriesFiltered; @@ -195,6 +198,14 @@ public SortedList getEntriesFilteredAndSorted() { return entriesFilteredAndSorted; } + public Optional getViewModelByIndex(int index) { + if (index < 0 || index >= entriesViewModel.size()) { + LOGGER.warn("Tried to access out of bounds index {} in entriesViewModel", index); + return Optional.empty(); + } + return Optional.of(entriesViewModel.get(index)); + } + public void resetFieldFormatter() { this.fieldValueFormatter.setValue(new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext)); } diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 56f650e8893..911b5bed2a0 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -637,7 +637,11 @@ public String getNewLineSeparator() { */ public int indexOf(BibEntry bibEntry) { int index = Collections.binarySearch(entries, bibEntry, Comparator.comparing(BibEntry::getId)); - return index >= 0 ? index : -1; + if (index >= 0) { + return index; + } + LOGGER.warn("Could not find entry with ID {} in the database", bibEntry.getId()); + return -1; } public BibEntry getEntryById(String id) { From 709386ad1700ee6ce59864d630be047f4b515d88 Mon Sep 17 00:00:00 2001 From: Ruslan Date: Wed, 30 Oct 2024 11:12:21 +0200 Subject: [PATCH 034/108] Customizable AI templates (#11884) * Start to work * Fix runtime problems and checkers * Merge with main * Fix from code review * Update from merge * Fix compiler errors * Fix from code review --- build.gradle | 2 +- src/main/java/module-info.java | 1 + .../org/jabref/gui/preferences/ai/AiTab.fxml | 39 ++++- .../org/jabref/gui/preferences/ai/AiTab.java | 29 ++-- .../gui/preferences/ai/AiTabViewModel.java | 42 +++--- .../jabref/logic/ai/AiDefaultPreferences.java | 41 ++++++ .../org/jabref/logic/ai/AiPreferences.java | 26 +++- .../java/org/jabref/logic/ai/AiService.java | 5 +- .../jabref/logic/ai/chatting/AiChatLogic.java | 136 ++++++++++-------- .../logic/ai/chatting/AiChatService.java | 11 +- .../GenerateSummaryForSeveralTask.java | 5 + .../ai/summarization/GenerateSummaryTask.java | 50 ++----- .../ai/summarization/SummariesService.java | 8 +- .../jabref/logic/ai/templates/AiTemplate.java | 30 ++++ .../logic/ai/templates/PaperExcerpt.java | 3 + .../logic/ai/templates/TemplatesService.java | 62 ++++++++ .../java/org/jabref/logic/help/HelpFile.java | 3 +- .../preferences/JabRefCliPreferences.java | 27 +++- src/main/resources/l10n/JabRef_en.properties | 7 +- 19 files changed, 386 insertions(+), 141 deletions(-) create mode 100644 src/main/java/org/jabref/logic/ai/templates/AiTemplate.java create mode 100644 src/main/java/org/jabref/logic/ai/templates/PaperExcerpt.java create mode 100644 src/main/java/org/jabref/logic/ai/templates/TemplatesService.java diff --git a/build.gradle b/build.gradle index 5a364835b36..b71603a1056 100644 --- a/build.gradle +++ b/build.gradle @@ -351,7 +351,7 @@ dependencies { exclude group: 'org.jetbrains.kotlin' } - + implementation 'org.apache.velocity:velocity-engine-core:2.3' implementation platform('ai.djl:bom:0.30.0') implementation 'ai.djl:api' implementation 'ai.djl.huggingface:tokenizers' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 49906ad2625..f0151b8988e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -160,6 +160,7 @@ uses ai.djl.repository.RepositoryFactory; uses ai.djl.repository.zoo.ZooProvider; uses dev.langchain4j.spi.prompt.PromptTemplateFactory; + requires velocity.engine.core; // endregion // region: Lucene diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTab.fxml b/src/main/java/org/jabref/gui/preferences/ai/AiTab.fxml index b81bd96ba06..64e737747f5 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTab.fxml @@ -16,6 +16,9 @@ + + + - - @@ -235,5 +234,37 @@ glyph="REFRESH"/> + + +