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) 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..a4caf50b257 100644 --- a/.github/workflows/cleanup-pr.yml +++ b/.github/workflows/cleanup-pr.yml @@ -6,6 +6,7 @@ on: jobs: cleanup: + if: github.repository_owner == 'JabRef' runs-on: ubuntu-latest steps: - name: Cancel deployment run 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 8839eca84b7..93e65e1cf66 100644 --- a/.github/workflows/on-labeled-issue.yml +++ b/.github/workflows/on-labeled-issue.yml @@ -6,8 +6,36 @@ 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' && github.repository_owner == 'JabRef' }} + 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' }} + if: ${{ github.event.label.name == 'FirstTimeCodeContribution' && github.repository_owner == 'JabRef' }} runs-on: ubuntu-latest permissions: issues: write @@ -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,14 +73,14 @@ 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" 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" @@ -62,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 @@ -73,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" @@ -83,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 f8ad213aea3..e6fe90e40f9 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 @@ -37,22 +37,23 @@ 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 }} + pr_number: ${{ steps.read-pr_number.outputs.pr_number }} - 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 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: diff --git a/CHANGELOG.md b/CHANGELOG.md index dc911099c31..0c7d259a82e 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 @@ -104,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/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/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/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/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)}; + } +} 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/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/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/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/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_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/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=相關文章 + 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")); + } } 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); + } +} 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)); + } } 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()) ); }