diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index fc055df043f..c52221619ed 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,10 +1,5 @@
-
+Describe the changes you have made here: what, why, ...
+Link the issue that will be closed, e.g., "Closes #333". If your PR closes a koppor issue, link it using its URL, e.g., "Closes https://github.com/koppor/jabref/issues/47".
### Mandatory checks
diff --git a/.github/workflows/assign-issue.yml b/.github/workflows/assign-issue.yml
index 70c89775742..e88f6b762d2 100644
--- a/.github/workflows/assign-issue.yml
+++ b/.github/workflows/assign-issue.yml
@@ -10,25 +10,47 @@ on:
jobs:
assign:
runs-on: ubuntu-latest
+ permissions:
+ issues: write
steps:
- name: Assign the user or unassign stale assignments
- uses: takanome-dev/assign-issue-action@v2.1.1
+ id: assign
+ uses: takanome-dev/assign-issue-action@beta
with:
github_token: '${{ secrets.GITHUB_TOKEN }}'
days_until_unassign: 30
+ maintainers: koppor, Siedlerchr, ThiloteE, calixtus, HoussemNasri
assigned_comment: |
- 👋 Hey @{{ comment.user.login }},
+ 👋 Hey @{{ handle }}, thank you for your interest in this issue! 🎉
- Thanks for your interest in this issue! 🎉
-
- Newcomers, we're excited to have you on board. Start by exploring our [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) guidelines, and don't forget to check out our [workspace setup guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) to get started smoothly.
+ We're excited to have you on board. Start by exploring our [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) guidelines, and don't forget to check out our [workspace setup guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) to get started smoothly.
In case you encounter failing tests during development, please check our [developer FAQs](https://devdocs.jabref.org/code-howtos/faq.html)!
Having any questions or issues? Feel free to ask here on GitHub. Need help setting up your local workspace? Join the conversation on [JabRef's Gitter chat](https://gitter.im/JabRef/jabref). And don't hesitate to open a (draft) pull request early on to show the direction it is heading towards. This way, you will receive valuable feedback.
- ⚠ Note that this issue will become unassigned if it isn't closed within **{{ totalDays }} days**.
-
- 🔧 A maintainer can also add the **{{ inputs.pin_label }}** label to prevent it from being unassigned automatically.
-
Happy coding! 🚀
+
+ ⏳ Please note, you will be automatically unassigned if the issue isn't closed within **{{ total_days }} days** (by **{{ unassigned_date }}**). A maintainer can also add the "**{{ pin_label }}**"" label to prevent automatic unassignment.
+ - name: Move Issue to "Assigned" Column in "Candidates for University Projects"
+ if: steps.assign.outputs.assigned == 'yes'
+ 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: "Assigned"
+ ignored-columns: ""
+ default-column: "Free to take"
+ skip-if-not-in-project: true
+ - name: Move Issue to "Assigned" Column in "Good First Issues"
+ if: steps.assign.outputs.assigned == 'yes'
+ 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: "Assigned"
+ ignored-columns: ""
+ default-column: "Free to take"
+ skip-if-not-in-project: true
diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml
index 1e5c7011f23..856b3f66b57 100644
--- a/.github/workflows/automerge.yml
+++ b/.github/workflows/automerge.yml
@@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest
# Run only if PR is inside JabRef's main repository and created by dependabot or by an update workflow
if: >
- (github.repository == 'JabRef/jabref') &&
+ (github.repository == 'JabRef/jabref') &&
(github.event.pull_request.head.repo.full_name == 'JabRef/jabref') &&
(
(github.actor == 'dependabot[bot]') ||
(
- startsWith(github.event.pull_request.title, '[Bot] ') ||
- startsWith(github.event.pull_request.title, 'Bump ') ||
+ startsWith(github.event.pull_request.title, '[Bot] ') ||
+ startsWith(github.event.pull_request.title, 'Bump ') ||
startsWith(github.event.pull_request.title, 'New Crowdin updates') ||
startsWith(github.event.pull_request.title, 'Update Gradle Wrapper from')
)
@@ -26,9 +26,9 @@ jobs:
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
- GITHUB_TOKEN: ${{secrets.GH_TOKEN_JABREF_MACHINE_PR_APPROVE}}
+ GH_TOKEN: ${{secrets.GH_TOKEN_JABREF_MACHINE_PR_APPROVE}}
- name: Merge PR
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
- GITHUB_TOKEN: ${{secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER}}
+ GH_TOKEN: ${{secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER}}
diff --git a/.github/workflows/cleanup-pr.yml b/.github/workflows/cleanup-pr.yml
index 5c816f920fa..bd38e11ea16 100644
--- a/.github/workflows/cleanup-pr.yml
+++ b/.github/workflows/cleanup-pr.yml
@@ -39,6 +39,6 @@ jobs:
if: steps.checksecrets.outputs.secretspresent == 'YES'
uses: thollander/actions-comment-pull-request@v3
with:
- comment_tag: download-link
+ comment-tag: download-link
message: The build for this PR is no longer available. Please visit for the latest build.
mode: upsert
diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml
index ba200bcb843..e6796073e08 100644
--- a/.github/workflows/deployment.yml
+++ b/.github/workflows/deployment.yml
@@ -291,7 +291,7 @@ jobs:
with:
message: |
The build of this PR is available at .
- comment_tag: download-link
+ comment-tag: download-link
mode: recreate
notarize: # outsourced in a separate job to be able to rerun if this fails for timeouts
name: macOS notarization
diff --git a/.github/workflows/on-labeled-issue.yml b/.github/workflows/on-labeled-issue.yml
index 6d2600da58b..8839eca84b7 100644
--- a/.github/workflows/on-labeled-issue.yml
+++ b/.github/workflows/on-labeled-issue.yml
@@ -31,44 +31,25 @@ jobs:
Happy coding! 🚀
- name: Move Issue to "Assigned" Column in "Candidates for University Projects"
- uses: m7kvqbe1/github-action-move-issues@v1.1.1
+ 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: "FirstTimeCodeContribution"
- target-column: "Assigned"
- ignored-columns: ""
+ github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
+ project-url: "https://github.com/orgs/JabRef/projects/3"
+ target-labels: "📍 Assigned"
+ target-column: "Assigned"
+ ignored-columns: ""
+ default-column: "Free to take"
+ skip-if-not-in-project: true
- name: Move Issue to "Assigned" Column in "Good First Issues"
- uses: m7kvqbe1/github-action-move-issues@v1.1.1
+ 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: "FirstTimeCodeContribution"
- target-column: "Assigned"
- ignored-columns: ""
- Assigned:
- name: "📍 Assigned"
- if: ${{ github.event.label.name == '📍 Assigned' }}
- runs-on: ubuntu-latest
- permissions:
- issues: write
- steps:
- - name: Move Issue to "Assigned" Column in "Candidates for University Projects"
- uses: m7kvqbe1/github-action-move-issues@v1.1.1
- with:
- github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
- project-url: "https://github.com/orgs/JabRef/projects/3"
- target-labels: "📍 Assigned"
- target-column: "Assigned"
- ignored-columns: ""
- - name: Move Issue to "Assigned" Column in "Good First Issues"
- uses: m7kvqbe1/github-action-move-issues@v1.1.1
- with:
- github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
- project-url: "https://github.com/orgs/JabRef/projects/5"
- target-labels: "📍 Assigned"
- target-column: "Assigned"
- ignored-columns: ""
+ github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
+ project-url: "https://github.com/orgs/JabRef/projects/5"
+ target-labels: "📍 Assigned"
+ 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' }}"
diff --git a/.github/workflows/on-review-submitted.yml b/.github/workflows/on-review-submitted.yml
new file mode 100644
index 00000000000..1dc9d7b4c59
--- /dev/null
+++ b/.github/workflows/on-review-submitted.yml
@@ -0,0 +1,19 @@
+name: On reviewed PR
+
+on:
+ pull_request_review:
+ types:
+ - submitted
+
+jobs:
+ add-label-changes-required:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo ${{ github.event.review.state }}
+ - if: ${{ github.event.review.state == 'changes_requested' }}
+ run: |
+ gh pr edit "$PR_URL" --remove-label "status: ready-for-review"
+ gh pr edit "$PR_URL" --add-label "status: changes required"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GH_TOKEN: ${{ github.token }}
\ No newline at end of file
diff --git a/.github/workflows/on-unlabeled-issue.yml b/.github/workflows/on-unlabeled-issue.yml
index 0947fd05c2a..9c5870066ca 100644
--- a/.github/workflows/on-unlabeled-issue.yml
+++ b/.github/workflows/on-unlabeled-issue.yml
@@ -13,20 +13,22 @@ jobs:
issues: write
steps:
- name: Move Issue to "Free to take" Column in "Candidates for University Projects"
- uses: m7kvqbe1/github-action-move-issues@v1.1.1
+ 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: "Assigned"
- ignored-columns: ""
- default-column: "Free to take"
+ github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
+ project-url: "https://github.com/orgs/JabRef/projects/3"
+ target-labels: "📍 Assigned"
+ target-column: "Assigned"
+ 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@v1.1.1
+ 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: "Assigned"
- ignored-columns: ""
- default-column: "Free to take"
+ github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
+ project-url: "https://github.com/orgs/JabRef/projects/5"
+ target-labels: "📍 Assigned"
+ target-column: "Assigned"
+ ignored-columns: ""
+ default-column: "Free to take"
+ skip-if-not-in-project: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 789d8ec6968..2f5ba62e7df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658)
- We added a setting which always adds the literal "Cited on pages" text before each JStyle citation. [#11691](https://github.com/JabRef/jabref/pull/11732)
- We added a new plain citation parser that uses LLMs. [#11825](https://github.com/JabRef/jabref/issues/11825)
+- We added an importer for SSRN URLs. [#12021](https://github.com/JabRef/jabref/pull/12021)
- We added a compare button to the duplicates in the citation relations tab to open the "Possible duplicate entries" window. [#11192](https://github.com/JabRef/jabref/issues/11192)
- 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)
@@ -97,6 +98,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 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)
### Removed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5f6a209fdc5..e0efbdd98f2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -130,18 +130,22 @@ We reserve the right to reject pull requests that contain little or no genuine a
### After submission of a pull request
After you submitted a pull request, automated checks will run.
-You will maybe see "Some checks were not successful".
+You may see "Some checks were not successful".
+You can click on failing checks to see more information about why they failed.
Then, please look into them and handle accordingly.
-Afterwards, you will receive comments on it.
-Maybe, the pull request is approved immediatly, maybe changes are requested.
-In case changes are requested, please implement them.
+Afterwards, you will receive comments on your pull request.
+The pull request may be approved immediatly, or a reviewer may request changes.
+In that case, please implement the requested changes.
-After changing your code, commit on the branch and push.
-The pull request will update automatically.
+After implementing changes, commit to the branch your pull request is *from* and push.
+The pull request will automatically be updated with your changes.
+Your commits will also be automatically squashed upon the pull request being accepted.
-Never ever close the pull request and open a new one.
-This causes much load on our side and is not the style of the GitHub open source community.
+Please – **Never ever close a pull request and open a new one** -
+This causes unessesary work on our side, and is not in the the style of the GitHub open source community.
+You can push any changes you need to make to the branch your pull request is *from*.
+These changes will be automatically added to your pull request.
### Development hints
diff --git a/build.gradle b/build.gradle
index 65b5d22bb38..0c7558981c8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -142,7 +142,7 @@ dependencyLocking {
}
javafx {
- version = "23"
+ version = "23.0.1"
modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web', 'javafx.swing' ]
}
diff --git a/docs/decisions/0000-use-markdown-any-decision-records.md b/docs/decisions/0000-use-markdown-architectural-decision-records.md
similarity index 68%
rename from docs/decisions/0000-use-markdown-any-decision-records.md
rename to docs/decisions/0000-use-markdown-architectural-decision-records.md
index 508c6461700..b8caf9eee02 100644
--- a/docs/decisions/0000-use-markdown-any-decision-records.md
+++ b/docs/decisions/0000-use-markdown-architectural-decision-records.md
@@ -2,15 +2,16 @@
parent: Decision Records
nav_order: 0
---
-# Use Markdown Any Decision Records
+# Use Markdown Architectural Decision Records
## Context and Problem Statement
-We want to record any decisions made in this project independent whether decisions concern the architecture ("architectural decision record"), the code, or other fields.
+We want to record architectural decisions made in this project independent whether decisions concern the architecture ("architectural decision record"), the code, or other fields.
Which format and structure should these records follow?
## Considered Options
+* [MADR](https://adr.github.io/madr/) 4.0.0 – The Markdown Architectural Decision Records
* [Michael Nygard's template](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) – The first incarnation of the term "ADR"
* [Sustainable Architectural Decisions](https://www.infoq.com/articles/sustainable-architectural-design-decisions) – The Y-Statements
* Other templates listed at
@@ -18,11 +19,11 @@ Which format and structure should these records follow?
## Decision Outcome
-Chosen option: "MADR", because
+Chosen option: "MADR 4.0.0", because
* Implicit assumptions should be made explicit.
Design documentation is important to enable people understanding the decisions later on.
- See also [A rational design process: How and why to fake it](https://doi.org/10.1109/TSE.1986.6312940).
+ See also ["A rational design process: How and why to fake it"](https://doi.org/10.1109/TSE.1986.6312940).
* MADR allows for structured capturing of any decision.
* The MADR format is lean and fits our development style.
* The MADR structure is comprehensible and facilitates usage & maintenance.
diff --git a/docs/decisions/0006-only-translated-strings-in-language-file.md b/docs/decisions/0006-only-translated-strings-in-language-file.md
index 609af7ef1ca..8234c481f16 100644
--- a/docs/decisions/0006-only-translated-strings-in-language-file.md
+++ b/docs/decisions/0006-only-translated-strings-in-language-file.md
@@ -22,7 +22,7 @@ JabRef has translation files `JabRef_it.properties`, ... There are translated an
## Decision Outcome
-Chosen option: "Only translated strings in language file", because comes out best \(see below.
+Chosen option: "Only translated strings in language file", because comes out best (see below).
## Pros and Cons of the Options
diff --git a/docs/decisions/0007-human-readable-changelog.md b/docs/decisions/0007-human-readable-changelog.md
index c0fbf857f9a..9753bc28b5f 100644
--- a/docs/decisions/0007-human-readable-changelog.md
+++ b/docs/decisions/0007-human-readable-changelog.md
@@ -6,7 +6,7 @@ nav_order: 7
## Context and Problem Statement
-Changes of a release have to be communicated. How and which stile to use?
+Changes of a release have to be communicated. How and which style to use?
## Considered Options
diff --git a/docs/decisions/0019-implement-special-fields-as-separate-fields.md b/docs/decisions/0019-implement-special-fields-as-separate-fields.md
index 77188d32ed3..3e4d6b5ec82 100644
--- a/docs/decisions/0019-implement-special-fields-as-separate-fields.md
+++ b/docs/decisions/0019-implement-special-fields-as-separate-fields.md
@@ -17,7 +17,7 @@ How to implement special fields in BibTeX databases?
## Decision Outcome
-Chosen option: "Special fields as separate fields", because comes out best (see below)
+Chosen option: "Special fields as separate fields", because comes out best (see below).
## Pros and Cons of the Options
diff --git a/docs/decisions/0020-use-Jackson-to-parse-study-yml.md b/docs/decisions/0020-use-Jackson-to-parse-study-yml.md
index 6f0dbee85d7..4f083e8d7d7 100644
--- a/docs/decisions/0020-use-Jackson-to-parse-study-yml.md
+++ b/docs/decisions/0020-use-Jackson-to-parse-study-yml.md
@@ -20,7 +20,7 @@ What parser should be used to parse YAML files?
## Decision Outcome
-Chosen option: Jackson, because as it is a dedicated library for parsing YAML. `yamlbeans` also seem to be viable. They all offer similar functionality.
+Chosen option: "Jackson", because as it is a dedicated library for parsing YAML. `yamlbeans` also seem to be viable. They all offer similar functionality.
## Pros and Cons of the Options
diff --git a/docs/decisions/0036-use-textarea-for-chat-content.md b/docs/decisions/0036-use-textarea-for-chat-content.md
index 1acfe73b665..5f01bebf35f 100644
--- a/docs/decisions/0036-use-textarea-for-chat-content.md
+++ b/docs/decisions/0036-use-textarea-for-chat-content.md
@@ -25,7 +25,7 @@ This decision record concerns the UI component that is used for rendering the co
## Decision Outcome
-Chosen option: "Use TextArea".
+Chosen option: "Use `TextArea`".
All other options require more time to implement.
Some of the options do not support text selection and copying,
which for now we value more than Markdown rendering.
diff --git a/docs/decisions/adr-template.md b/docs/decisions/adr-template.md
index 47b62f5a815..d92254f8eb2 100644
--- a/docs/decisions/adr-template.md
+++ b/docs/decisions/adr-template.md
@@ -9,16 +9,16 @@ nav_order: 100
# Keep this
parent: Decision Records
-# These are optional elements. Feel free to remove any of them.
-# status: {proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)}
-# date: {YYYY-MM-DD when the decision was last updated}
-# deciders: {list everyone involved in the decision}
-# consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication}
-# informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication}
+# These are optional metadata elements. Feel free to remove any of them.
+status: "{proposed | rejected | accepted | deprecated | … | superseded by ADR-0123"
+date: {YYYY-MM-DD when the decision was last updated}
+decision-makers: {list everyone involved in the decision}
+consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication}
+informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication}
---
-# {short title of solved problem and solution}
+# {short title, representative of solved problem and found solution}
## Context and Problem Statement
@@ -41,25 +41,19 @@ parent: Decision Records
## Decision Outcome
-Chosen option: "{title of option 1}", because
-{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
+Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
-### Positive Consequences
+### Consequences
-* {e.g., improvement of one or more desired qualities, …}
-* …
-
-
-### Negative Consequences
-
-* {e.g., compromising one or more desired qualities, …}
-* …
+* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
+* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
+* …
-## Validation
+### Confirmation
-{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test}
+{Describe how the implementation of/compliance with the ADR can/will be confirmed. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.}
## Pros and Cons of the Options
@@ -89,8 +83,4 @@ Chosen option: "{title of option 1}", because
## More Information
-{You might want to provide additional evidence/confidence for the decision outcome here and/or
- document the team agreement on the decision and/or
- define when this decision when and how the decision should be realized and if/when it should be re-visited and/or
- how the decision is validated.
- Links to other decisions and resources might here appear as well.}
+{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.}
diff --git a/docs/decisions/index.md b/docs/decisions/index.md
index bab7c46aec3..54c0c329a00 100644
--- a/docs/decisions/index.md
+++ b/docs/decisions/index.md
@@ -4,7 +4,7 @@ has_children: true
---
# Decision Records
-Below, all "any decision records" for JabRef are listed.
+Below, all "architectural decision records" for JabRef are listed.
This list uses the TOC functionality of the [Just the Docs Jekyll template](https://just-the-docs.github.io/just-the-docs/).
For new ADRs, please use [adr-template.md](adr-template.md) as basis.
diff --git a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java
index 428052847bf..95e2b927dd9 100644
--- a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java
+++ b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java
@@ -20,6 +20,7 @@
import org.jabref.gui.autocompleter.SuggestionProviders;
import org.jabref.gui.fieldeditors.FieldEditorFX;
import org.jabref.gui.fieldeditors.FieldNameLabel;
+import org.jabref.gui.fieldeditors.MarkdownEditor;
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.theme.ThemeManager;
@@ -135,12 +136,12 @@ protected void setupPanel(BibEntry entry, boolean compressed) {
Optional fieldEditorForUserDefinedComment = editors.entrySet().stream().filter(f -> f.getKey().getName().contains(defaultOwner)).map(Map.Entry::getValue).findFirst();
for (Map.Entry fieldEditorEntry : editors.entrySet()) {
Field field = fieldEditorEntry.getKey();
- FieldEditorFX editor = fieldEditorEntry.getValue();
+ MarkdownEditor editor = (MarkdownEditor) fieldEditorEntry.getValue().getNode();
boolean isStandardBibtexComment = field == StandardField.COMMENT;
boolean isDefaultOwnerComment = field.equals(userSpecificCommentField);
boolean shouldBeEnabled = isStandardBibtexComment || isDefaultOwnerComment;
- editor.getNode().setDisable(!shouldBeEnabled);
+ editor.setEditable(shouldBeEnabled);
}
// Show "Hide" button only if user-specific comment field is empty. Otherwise, it is a strange UI, because the
diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java
index d3b621fe16e..00e4124bc76 100644
--- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java
+++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java
@@ -32,9 +32,6 @@
import org.jabref.logic.importer.ImportFormatReader;
import org.jabref.logic.importer.ImportFormatReader.UnknownFormatImport;
import org.jabref.logic.importer.ParseException;
-import org.jabref.logic.importer.fetcher.ArXivFetcher;
-import org.jabref.logic.importer.fetcher.DoiFetcher;
-import org.jabref.logic.importer.fetcher.isbntobibtex.IsbnFetcher;
import org.jabref.logic.importer.fileformat.BibtexParser;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackgroundTask;
@@ -49,9 +46,6 @@
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.entry.field.StandardField;
-import org.jabref.model.entry.identifier.ArXivIdentifier;
-import org.jabref.model.entry.identifier.DOI;
-import org.jabref.model.entry.identifier.ISBN;
import org.jabref.model.groups.GroupEntryChanger;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.util.FileUpdateMonitor;
@@ -385,24 +379,6 @@ private List tryImportFormats(String data) {
}
}
- private List fetchByDOI(DOI doi) throws FetcherException {
- LOGGER.info("Found DOI identifier in clipboard");
- Optional entry = new DoiFetcher(preferences.getImportFormatPreferences()).performSearchById(doi.getDOI());
- return OptionalUtil.toList(entry);
- }
-
- private List fetchByArXiv(ArXivIdentifier arXivIdentifier) throws FetcherException {
- LOGGER.info("Found arxiv identifier in clipboard");
- Optional entry = new ArXivFetcher(preferences.getImportFormatPreferences()).performSearchById(arXivIdentifier.getNormalizedWithoutVersion());
- return OptionalUtil.toList(entry);
- }
-
- private List fetchByISBN(ISBN isbn) throws FetcherException {
- LOGGER.info("Found ISBN identifier in clipboard");
- Optional entry = new IsbnFetcher(preferences.getImportFormatPreferences()).performSearchById(isbn.getNormalized());
- return OptionalUtil.toList(entry);
- }
-
public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List entriesToAdd) {
boolean firstEntry = true;
for (BibEntry entry : entriesToAdd) {
diff --git a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java
index 891757d43ca..7d230d3db2a 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java
+++ b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java
@@ -15,7 +15,6 @@
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
public class MarkdownEditor extends SimpleEditor {
-
private final FlexmarkHtmlConverter flexmarkHtmlConverter = FlexmarkHtmlConverter.builder().build();
public MarkdownEditor(Field field, SuggestionProvider> suggestionProvider, FieldCheckers fieldCheckers, GuiPreferences preferences, UndoManager undoManager, UndoAction undoAction, RedoAction redoAction) {
@@ -37,4 +36,8 @@ public void paste() {
}
};
}
+
+ public void setEditable(boolean isEditable) {
+ getTextInput().setEditable(isEditable);
+ }
}
diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java
index 89a6464d843..9b9537c967e 100644
--- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java
+++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java
@@ -72,4 +72,8 @@ public Parent getNode() {
public void requestFocus() {
textInput.requestFocus();
}
+
+ protected TextInputControl getTextInput() {
+ return textInput;
+ }
}
diff --git a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java
index dbe205a24c4..c14e0770c15 100644
--- a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java
+++ b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java
@@ -40,6 +40,7 @@
*/
public class FetchAndMergeEntry {
+ // All identifiers listed here should also appear at {@link org.jabref.logic.importer.CompositeIdFetcher#performSearchById}
public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN);
private static final Logger LOGGER = LoggerFactory.getLogger(FetchAndMergeEntry.class);
diff --git a/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java b/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java
index 83d939a3085..b1630a12468 100644
--- a/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java
+++ b/src/main/java/org/jabref/logic/importer/CompositeIdFetcher.java
@@ -10,6 +10,7 @@
import org.jabref.model.entry.identifier.ArXivIdentifier;
import org.jabref.model.entry.identifier.DOI;
import org.jabref.model.entry.identifier.ISBN;
+import org.jabref.model.entry.identifier.SSRN;
public class CompositeIdFetcher {
@@ -20,6 +21,8 @@ public CompositeIdFetcher(ImportFormatPreferences importFormatPreferences) {
}
public Optional performSearchById(String identifier) throws FetcherException {
+ // All identifiers listed here should also be appear at {@link org.jabref.gui.mergeentries.FetchAndMergeEntry.SUPPORTED_FIELDS} and vice versa.
+
Optional doi = DOI.findInText(identifier);
if (doi.isPresent()) {
return new DoiFetcher(importFormatPreferences).performSearchById(doi.get().getNormalized());
@@ -41,6 +44,11 @@ public Optional performSearchById(String identifier) throws FetcherExc
return new IacrEprintFetcher(importFormatPreferences).performSearchById(iacrEprint.get().getNormalized());
}*/
+ Optional ssrn = SSRN.parse(identifier);
+ if (ssrn.isPresent()) {
+ return new DoiFetcher(importFormatPreferences).performSearchById(ssrn.get().toDoi().getNormalized());
+ }
+
return Optional.empty();
}
@@ -52,7 +60,8 @@ public static boolean containsValidId(String identifier) {
Optional doi = DOI.findInText(identifier);
Optional arXivIdentifier = ArXivIdentifier.parse(identifier);
Optional isbn = ISBN.parse(identifier);
+ Optional ssrn = SSRN.parse(identifier);
- return Stream.of(doi, arXivIdentifier, isbn).anyMatch(Optional::isPresent);
+ return Stream.of(doi, arXivIdentifier, isbn, ssrn).anyMatch(Optional::isPresent);
}
}
diff --git a/src/main/java/org/jabref/model/entry/field/FieldFactory.java b/src/main/java/org/jabref/model/entry/field/FieldFactory.java
index ff660ad4ab6..1d0eb8ccfcf 100644
--- a/src/main/java/org/jabref/model/entry/field/FieldFactory.java
+++ b/src/main/java/org/jabref/model/entry/field/FieldFactory.java
@@ -222,7 +222,11 @@ public static List getDefaultGeneralFields() {
return defaultGeneralFields;
}
- // TODO: This should ideally be user configurable! (https://github.com/JabRef/jabref/issues/9840)
+ /**
+ * Note: User configurability is discussed at #9840.
+ *
+ * @param nonWrappableFields This comes from the preferences - and introduces user configuration.
+ */
// TODO: Move somewhere more appropriate in the future
public static boolean isMultiLineField(final Field field, List nonWrappableFields) {
return field.getProperties().contains(FieldProperty.MULTILINE_TEXT) || nonWrappableFields.contains(field);
diff --git a/src/main/java/org/jabref/model/entry/identifier/SSRN.java b/src/main/java/org/jabref/model/entry/identifier/SSRN.java
new file mode 100644
index 00000000000..3ff7c7dcf1c
--- /dev/null
+++ b/src/main/java/org/jabref/model/entry/identifier/SSRN.java
@@ -0,0 +1,78 @@
+package org.jabref.model.entry.identifier;
+
+import java.net.URI;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents an SSRN article, identified by its abstract ID.
+ */
+public class SSRN extends EprintIdentifier {
+
+ private static final String SSRN_URL_REGEX = "(https?://)?(papers\\.)?ssrn\\.com/(sol3/papers.cfm\\?)?abstract(_id)?=(?\\d+)";
+ private static final Pattern SSRN_URL_FULL_MATCH = Pattern.compile("^" + SSRN_URL_REGEX + "$", Pattern.CASE_INSENSITIVE);
+ private static final Pattern SSRN_URL_MATCH = Pattern.compile(SSRN_URL_REGEX, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+
+ private final Integer abstractId;
+
+ /**
+ * Tries to parse an SSRN identifier
+ *
+ * @param string Either a number or a SSRN url that has the abstract ID in it
+ * @throws NullPointerException If you pass a null to the constructor
+ * @throws IllegalArgumentException Invalid string passed to the constructor
+ */
+ public SSRN(String string) {
+ Objects.requireNonNull(string);
+ string = string.trim();
+
+ if (string.chars().allMatch(Character::isDigit)) {
+ abstractId = Integer.parseInt(string);
+ return;
+ }
+
+ Matcher matcher = SSRN_URL_FULL_MATCH.matcher(string);
+ if (matcher.find()) {
+ abstractId = Integer.parseInt(matcher.group("id"));
+ return;
+ }
+
+ throw new IllegalArgumentException(string + " is not a valid SSRN identifier");
+ }
+
+ public SSRN(Integer abstractId) {
+ this.abstractId = abstractId;
+ }
+
+ public static Optional parse(String data) {
+ Matcher matcher = SSRN_URL_MATCH.matcher(data);
+ if (matcher.find()) {
+ int abstractId = Integer.parseInt(matcher.group("id"));
+ return Optional.of(new SSRN(abstractId));
+ }
+
+ return Optional.empty();
+ }
+
+ @Override
+ public String getNormalized() {
+ return String.valueOf(abstractId);
+ }
+
+ @Override
+ public Optional getExternalURI() {
+ URI uri = URI.create("https://ssrn.com/abstract=" + abstractId);
+ return Optional.of(uri);
+ }
+
+ /**
+ * Generate the DOI based on the SSRN
+ *
+ * @return a DOI formatted as 10.2139/ssrn.[article]
+ */
+ public DOI toDoi() {
+ return new DOI("10.2139/ssrn." + abstractId);
+ }
+}
diff --git a/src/test/java/org/jabref/logic/util/FileNameCleanerTest.java b/src/test/java/org/jabref/logic/util/FileNameCleanerTest.java
index d4ae98057b2..1193075cd8b 100644
--- a/src/test/java/org/jabref/logic/util/FileNameCleanerTest.java
+++ b/src/test/java/org/jabref/logic/util/FileNameCleanerTest.java
@@ -2,35 +2,42 @@
import org.jabref.logic.util.io.FileNameCleaner;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FileNameCleanerTest {
- @Test
- void cleanFileName() {
- assertEquals("legalFilename.txt", FileNameCleaner.cleanFileName("legalFilename.txt"));
- assertEquals("illegalFilename______.txt", FileNameCleaner.cleanFileName("illegalFilename/?*<>|.txt"));
- assertEquals("illegalFileName_.txt", FileNameCleaner.cleanFileName("illegalFileName{.txt"));
+ @ParameterizedTest
+ @CsvSource({
+ "legalFilename.txt, legalFilename.txt",
+ "illegalFilename______.txt, illegalFilename/?*<>|.txt",
+ "illegalFileName_.txt, illegalFileName{.txt",
+ "_The Evolution of Sentiment_ Analysis_.PDF, ?The Evolution of Sentiment} Analysis}.PDF",
+ "'The Evolution of Sentiment_ Analysis_A Review of Research Topics, Venues, and Top Cited Papers.PDF', 'The Evolution of Sentiment} Analysis}A Review of Research Topics, Venues, and Top Cited Papers.PDF'"
+ })
+ void cleanFileName(String expected, String input) {
+ assertEquals(expected, FileNameCleaner.cleanFileName(input));
}
- @Test
- void cleanDirectoryName() {
- assertEquals("legalFilename.txt", FileNameCleaner.cleanDirectoryName("legalFilename.txt"));
- assertEquals("subdir/legalFilename.txt", FileNameCleaner.cleanDirectoryName("subdir/legalFilename.txt"));
- assertEquals("illegalFilename/_____.txt", FileNameCleaner.cleanDirectoryName("illegalFilename/?*<>|.txt"));
+ @ParameterizedTest
+ @CsvSource({
+ "legalFilename.txt, legalFilename.txt",
+ "subdir/legalFilename.txt, subdir/legalFilename.txt",
+ "illegalFilename/_____.txt, illegalFilename/?*<>|.txt"
+ })
+ void cleanDirectoryName(String expected, String input) {
+ assertEquals(expected, FileNameCleaner.cleanDirectoryName(input));
}
- @Test
- void cleanDirectoryNameForWindows() {
- assertEquals("legalFilename.txt", FileNameCleaner.cleanDirectoryName("legalFilename.txt"));
- assertEquals("subdir\\legalFilename.txt", FileNameCleaner.cleanDirectoryName("subdir\\legalFilename.txt"));
- assertEquals("illegalFilename\\_____.txt", FileNameCleaner.cleanDirectoryName("illegalFilename\\?*<>|.txt"));
- }
-
- @Test
- void cleanCurlyBracesAsWell() {
- assertEquals("The Evolution of Sentiment_ Analysis_A Review of Research Topics, Venues, and Top Cited Papers.PDF", FileNameCleaner.cleanFileName("The Evolution of Sentiment} Analysis}A Review of Research Topics, Venues, and Top Cited Papers.PDF"));
+ @ParameterizedTest
+ @CsvSource({
+ "legalFilename.txt, legalFilename.txt",
+ "subdir\\legalFilename.txt, subdir\\legalFilename.txt",
+ "illegalFilename\\_____.txt, illegalFilename\\?*<>|.txt"
+ })
+ void cleanDirectoryNameForWindows(String expected, String input) {
+ assertEquals(expected, FileNameCleaner.cleanDirectoryName(input));
}
}
diff --git a/src/test/java/org/jabref/model/entry/identifier/SSRNTest.java b/src/test/java/org/jabref/model/entry/identifier/SSRNTest.java
new file mode 100644
index 00000000000..c7dbe3abfcd
--- /dev/null
+++ b/src/test/java/org/jabref/model/entry/identifier/SSRNTest.java
@@ -0,0 +1,67 @@
+package org.jabref.model.entry.identifier;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class SSRNTest {
+ private static Stream provideTestData() {
+ return Stream.of(
+ // Basic string
+ Arguments.of(false, "4904445"),
+ Arguments.of(false, " 4904445 "),
+
+ // URLs
+ Arguments.of(true, "https://ssrn.com/abstract=4904445"),
+ Arguments.of(true, "https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4904445"),
+ Arguments.of(true, " https://ssrn.com/abstract=4904445 "),
+ Arguments.of(true, " https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4904445 "),
+ Arguments.of(true, "http://ssrn.com/abstract=4904445")
+ );
+ }
+
+ /**
+ * @param findInText if the input should be found when passing through "find in text"
+ * @param input the input to be checked
+ */
+ @ParameterizedTest
+ @MethodSource("provideTestData")
+ public void acceptCorrectSSRNAbstracts(boolean findInText, String input) {
+ assertEquals("4904445", new SSRN(input).getNormalized());
+ Optional parsed = SSRN.parse(input);
+
+ if (findInText) {
+ assertTrue(parsed.isPresent());
+ assertEquals("4904445", parsed.get().getNormalized());
+ } else {
+ assertTrue(parsed.isEmpty());
+ }
+ }
+
+ @Test
+ public void findInText() {
+ Optional parsed = SSRN.parse("The example paper (https://ssrn.com/abstract=4904445) should be found within this text");
+ assertTrue(parsed.isPresent());
+ assertEquals("4904445", parsed.get().getNormalized());
+ }
+
+ @Test
+ public void identifierNormalisation() {
+ assertEquals("123456", new SSRN(123456).getNormalized());
+ }
+
+ @Test
+ public void identifierExternalUrl() {
+ SSRN ssrnIdentifier = new SSRN(123456);
+ URI uri = URI.create("https://ssrn.com/abstract=123456");
+ assertEquals(Optional.of(uri), ssrnIdentifier.getExternalURI());
+ }
+}