-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement book cover feature #12198
base: main
Are you sure you want to change the base?
Implement book cover feature #12198
Changes from 23 commits
bf9388b
6aaa9af
aa64848
b74e022
2100210
c89f3c8
d7c559b
7c82123
fd3750a
0e04bbf
5e9d764
0f0d2fa
ab2d3ab
da906f7
eb06002
23c04d5
c902323
6b789f5
34647c8
42ac2c9
c561303
a9c5dbd
aacffed
4119b7d
8b1a961
871fcaa
6d2e3b1
a7e9b10
2cf00f3
28a762d
23a691f
b1b39d7
edd101e
015a14c
7198ce8
75ad6ee
8684dbd
a94dac7
eb0a1b7
d0ffcc1
c7b71d4
f377939
4f3df63
87cc072
80cdf05
b85aab9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,14 +218,28 @@ private void setPreviewText(String text) { | |
layoutText = """ | ||
<html> | ||
<body id="previewBody"> | ||
<div id="content"> %s </div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. See general review comments. |
||
<div style="display: flex;"> | ||
<div id="content" style="flex: 1;"> | ||
%s | ||
</div> | ||
<div id="bookCover" style="flex: 1;"> | ||
<img src="%s" style="width: auto; height: 100vh;" align="right"> | ||
</div> | ||
</div> | ||
</body> | ||
</html> | ||
""".formatted(text); | ||
""".formatted(text, getBookCoverURI()); | ||
highlightLayoutText(); | ||
this.setHvalue(0); | ||
} | ||
|
||
private String getBookCoverURI() { | ||
return entry | ||
.flatMap(e -> e.getCoverImageFile() | ||
.map(file -> "file:///" + file.getLink())) | ||
.orElse(""); | ||
} | ||
|
||
private void highlightLayoutText() { | ||
if (layoutText == null) { | ||
return; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ | |
import org.jspecify.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import scala.annotation.meta.field; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a wrong import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong import sorry, will remove |
||
|
||
/** | ||
* Represents a Bib(La)TeX entry, which can be BibTeX or BibLaTeX. | ||
|
@@ -99,6 +100,10 @@ | |
public class BibEntry implements Cloneable { | ||
|
||
public static final EntryType DEFAULT_TYPE = StandardEntryType.Misc; | ||
|
||
private static final List<EntryType> COVERABLE_TYPES = List.of(StandardEntryType.Book, StandardEntryType.InBook, StandardEntryType.Booklet); | ||
private static final String COVER_TAG = "cover"; | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(BibEntry.class); | ||
private final SharedBibEntryData sharedBibEntryData; | ||
|
||
|
@@ -1124,6 +1129,26 @@ public Optional<FieldChange> addFiles(List<LinkedFile> filesToAdd) { | |
currentFiles.addAll(filesToAdd); | ||
return setFiles(currentFiles); | ||
} | ||
|
||
/** | ||
* @return <code>LinkedFile</code> that contains the cover image | ||
* if the <code>BibEntry</code> is a Book or other <code>COVERABLE_TYPES</code> | ||
*/ | ||
public Optional<LinkedFile> getCoverImageFile() { | ||
if (!isCoverable()) { | ||
return Optional.empty(); | ||
} | ||
|
||
for (LinkedFile file : getFiles()) { | ||
if (file.getDescription().equalsIgnoreCase(COVER_TAG)) { | ||
if (file.isImage()) { | ||
return Optional.of(file); | ||
} | ||
} | ||
} | ||
|
||
return Optional.empty(); | ||
} | ||
// endregion | ||
|
||
/** | ||
|
@@ -1156,7 +1181,8 @@ public Optional<Month> getMonth() { | |
return getFieldOrAlias(StandardField.MONTH).flatMap(Month::parse); | ||
} | ||
|
||
public OptionalBinding<String> getFieldBinding(Field field) { | ||
public OptionalBinding<String> getFieldBinding(Field | ||
field) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert newline |
||
if ((field == InternalField.TYPE_HEADER) || (field == InternalField.OBSOLETE_TYPE_HEADER)) { | ||
return EasyBind.wrapNullable(type).mapOpt(EntryType::getDisplayName); | ||
} | ||
|
@@ -1253,4 +1279,11 @@ public boolean isEmpty() { | |
} | ||
return StandardField.AUTOMATIC_FIELDS.containsAll(this.getFields()); | ||
} | ||
|
||
/** | ||
* @return <code>true</code> if this entry's <code>type</code> is a Book or one of <code>COVERABLE_TYPES</code> | ||
*/ | ||
private boolean isCoverable() { | ||
return COVERABLE_TYPES.contains(this.type.get()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import java.nio.file.Files; | ||
import java.nio.file.InvalidPathException; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
|
@@ -40,6 +41,8 @@ public class LinkedFile implements Serializable { | |
|
||
private static final LinkedFile NULL_OBJECT = new LinkedFile("", Path.of(""), ""); | ||
|
||
private static final List<String> IMAGE_EXTENSIONS = new ArrayList<>(List.of("jpeg", "jpg", "png", "gif")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a hashset for more efficient lookup and simpler code (next review comment) |
||
|
||
// We have to mark these properties as transient because they can't be serialized directly | ||
private transient StringProperty description = new SimpleStringProperty(); | ||
private transient StringProperty link = new SimpleStringProperty(); | ||
|
@@ -220,6 +223,10 @@ public boolean isOnlineLink() { | |
return isOnlineLink(link.get()); | ||
} | ||
|
||
public boolean isImage() { | ||
return IMAGE_EXTENSIONS.stream().anyMatch(getFileType().toLowerCase()::contains); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you use a hashset, can be simplified to: return IMAGE_EXTENSIONS.contains(getFileType().toLowerCase()); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed this to check for the string "image" in the |
||
} | ||
|
||
public Optional<Path> findIn(BibDatabaseContext databaseContext, FilePreferences filePreferences) { | ||
List<Path> dirs = databaseContext.getFileDirectories(filePreferences); | ||
return findIn(dirs); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
import java.net.URI; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
|
@@ -813,6 +814,100 @@ void mergeEntriesWithOverlapAndPriorityGivenToOverlappingField() { | |
assertEquals(expected.getFields(), copyEntry.getFields()); | ||
} | ||
|
||
@Test | ||
void getCoverImageReturnsEmptyIfNoFiles() { | ||
entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
assertEquals(Optional.empty(), entry.getCoverImageFile()); | ||
} | ||
|
||
@Test | ||
void getCoverImageReturnsEmptyIfNoImageFiles() { | ||
LinkedFile pdf = new LinkedFile("", Paths.get("Baldoni2002.pdf").toAbsolutePath().toString(), "pdf"); | ||
LinkedFile markdown = new LinkedFile("", "readme.md", "md"); | ||
entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
|
||
entry.addFile(markdown); | ||
entry.addFile(pdf); | ||
|
||
assertEquals(Optional.empty(), entry.getCoverImageFile()); | ||
} | ||
|
||
@Test | ||
void getCoverImageReturnsEmptyIfEntryIsNotCoverable() { | ||
entry = new BibEntry(StandardEntryType.Proceedings).withField(StandardField.AUTHOR, "value"); | ||
assertEquals(Optional.empty(), entry.getCoverImageFile()); | ||
|
||
entry = new BibEntry(StandardEntryType.Dataset).withField(StandardField.AUTHOR, "value"); | ||
assertEquals(Optional.empty(), entry.getCoverImageFile()); | ||
|
||
entry = new BibEntry(StandardEntryType.Software).withField(StandardField.AUTHOR, "value"); | ||
assertEquals(Optional.empty(), entry.getCoverImageFile()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can convert to parameterized test to avoid repetition |
||
} | ||
|
||
@Test | ||
void getCoverImageDoesNotReturnImagesWithoutCoverDescription() { | ||
LinkedFile cover1 = new LinkedFile("", Paths.get("JabRef-icon-128.png"), "png"); | ||
LinkedFile cover2 = new LinkedFile("", Paths.get("JabRef-icon-64.png"), "png"); | ||
LinkedFile cover3 = new LinkedFile("", Paths.get("JabRef-icon-32.png"), "png"); | ||
entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can keep this here and parameterize the covers |
||
|
||
entry.addFile(cover1); | ||
entry.addFile(cover2); | ||
entry.addFile(cover3); | ||
|
||
assertNotEquals(Optional.of(cover1), entry.getCoverImageFile()); | ||
assertNotEquals(Optional.of(cover2), entry.getCoverImageFile()); | ||
assertNotEquals(Optional.of(cover3), entry.getCoverImageFile()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it does not return images, can this be converted to |
||
} | ||
|
||
@Test | ||
void getCoverImageDoesNotReturnDocumentsWithCoverDescription() { | ||
LinkedFile pdf = new LinkedFile("cover", Paths.get("Baldoni2002.pdf"), "pdf"); | ||
LinkedFile md = new LinkedFile("cover", Paths.get("readme.md"), "md"); | ||
entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
|
||
entry.addFile(pdf); | ||
entry.addFile(md); | ||
|
||
assertNotEquals(Optional.of(md), entry.getCoverImageFile()); | ||
assertNotEquals(Optional.of(pdf), entry.getCoverImageFile()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar comments as above |
||
} | ||
|
||
@Test | ||
void getCoverImageReturnsCorrectImage() { | ||
LinkedFile cover1 = new LinkedFile("", Paths.get("JabRef-icon-128.png"), "png"); | ||
LinkedFile cover2 = new LinkedFile("", Paths.get("JabRef-icon-64.png"), "png"); | ||
LinkedFile cover3 = new LinkedFile("cover", Paths.get("JabRef-icon-32.png"), "png"); | ||
BibEntry entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
|
||
entry.addFile(cover1); | ||
entry.addFile(cover2); | ||
entry.addFile(cover3); | ||
|
||
assertEquals(Optional.of(cover3), entry.getCoverImageFile()); | ||
} | ||
|
||
@Test | ||
void getCoverImageUpdatesWithChangeToDescription() { | ||
List<LinkedFile> files = new ArrayList<>(); | ||
|
||
files.add(new LinkedFile("cover", Paths.get("JabRef-icon-128.png"), "png")); | ||
files.add(new LinkedFile("", Paths.get("JabRef-icon-64.png"), "png")); | ||
LinkedFile cover1 = files.get(0); | ||
LinkedFile cover2 = files.get(1); | ||
|
||
BibEntry entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); | ||
entry.setFiles(files); | ||
|
||
assertEquals(Optional.of(cover1), entry.getCoverImageFile()); | ||
|
||
cover1.setDescription(""); | ||
cover2.setDescription("cover"); | ||
entry.setFiles(files); | ||
|
||
assertEquals(Optional.of(cover2), entry.getCoverImageFile()); | ||
} | ||
|
||
public static Stream<BibEntry> isEmpty() { | ||
return Stream.of( | ||
new BibEntry(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.