Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/client/src/components/MagazineItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const sortLabelMap: Record<SORT_VALUE, string> = {
'CONNECTED_AT': 'Published',
'VOTE_SCORE': 'Created',
'COLLECTED_AT': 'Collected',
'UNSORTED_SAVED_AT': 'Collected',
'UNSORTED_SAVED_AT': 'Created',
};

type MagazineItemProps = {
Expand Down
2 changes: 1 addition & 1 deletion app/client/src/pages/CollectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const CollectionList = () => {
const [collection, setCollection] = useState<Collection | null>(null);
const isUnsorted = id === 'unsorted';

// Use UNSORTED_SAVED_AT for unsorted (fallback: collectedAt -> savedAt -> archivedAt), COLLECTED_AT for regular collections
// Use UNSORTED_SAVED_AT for unsorted (uses createdAt), COLLECTED_AT for regular collections
const [pageFilterOptions, setPageFilterOptions] = useState<PageFilterOptions>(() => ({
defaultSortValue: isUnsorted ? 'UNSORTED_SAVED_AT' : 'COLLECTED_AT',
sortFields: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,21 @@ public enum PageListSort {
COLLECTED_AT("collectedAt"),

/**
* For unsorted pages only: sorts by collectedAt, savedAt, archivedAt in order.
* Uses multiple sort fields for proper ordering.
* For unsorted pages only: sorts by createdAt which always exists.
*/
UNSORTED_SAVED_AT("collectedAt", "savedAt", "archivedAt");
UNSORTED_SAVED_AT("createdAt");

private final String[] sortFields;
private final String sortField;

PageListSort(String... sortFields) {
this.sortFields = sortFields;
PageListSort(String sortField) {
this.sortField = sortField;
}

/**
* Returns the primary sort field.
* Returns the sort field.
*/
public String getSortField() {
return sortFields[0];
}

/**
* Returns all sort fields for multi-field sorting.
*/
public String[] getSortFields() {
return sortFields;
}

/**
* Returns true if this sort uses multiple fields.
*/
public boolean isMultiFieldSort() {
return sortFields.length > 1;
return sortField;
}

static PageListSort valueOfSort(String sort) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ default PageItem updateRecordAt(@MappingTarget PageItem item, Page page, PageLis
item.setRecordAt(page.getCollectedAt());
break;
case UNSORTED_SAVED_AT:
// Fallback: collectedAt -> savedAt -> archivedAt
item.setRecordAt(coalesce(page.getCollectedAt(), page.getSavedAt(), page.getArchivedAt()));
// Use createdAt which always exists
item.setRecordAt(page.getCreatedAt());
break;
case LAST_READ_AT:
default:
Expand All @@ -51,15 +51,6 @@ default PageItem updateRecordAt(@MappingTarget PageItem item, Page page, PageLis
return item;
}

default java.time.Instant coalesce(java.time.Instant... values) {
for (java.time.Instant value : values) {
if (value != null) {
return value;
}
}
return null;
}

default PageItem updateFromSource(@MappingTarget PageItem pageItem, Source source) {
pageItem.setSiteName(source.getSiteName());
pageItem.setDomain(source.getDomain());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,26 @@ List<Page> findByCollectionIdAndLibrarySaveStatusGreaterThanOrderBySavedAtDesc(L
List<Page> findUnsortedLibraryPages(Pageable pageable);

/**
* Batch update collection only, keeping original collectedAt.
* Batch update collection and ensure collectedAt is set.
* Keeps original collectedAt if exists, otherwise uses createdAt as fallback.
* Used for pages already in library.
*/
@Transactional
@Modifying(clearAutomatically = true)
@Query("UPDATE Page p SET p.collectionId = :collectionId WHERE p.id IN :ids")
@Query("UPDATE Page p SET p.collectionId = :collectionId, " +
"p.collectedAt = COALESCE(p.collectedAt, p.createdAt) " +
"WHERE p.id IN :ids")
int batchUpdateCollection(@Param("ids") List<Long> ids, @Param("collectionId") Long collectionId);

/**
* Batch update collection and set collectedAt to connectedAt (publish time).
* Fallback to original collectedAt if connectedAt is null.
* Fallback to original collectedAt, then createdAt if connectedAt is null.
* Used for pages already in library.
*/
@Transactional
@Modifying(clearAutomatically = true)
@Query("UPDATE Page p SET p.collectionId = :collectionId, " +
"p.collectedAt = COALESCE(p.connectedAt, p.collectedAt) " +
"p.collectedAt = COALESCE(p.connectedAt, p.collectedAt, p.createdAt) " +
"WHERE p.id IN :ids")
int batchUpdateCollectionWithPublishTime(@Param("ids") List<Long> ids, @Param("collectionId") Long collectionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,17 @@ private BatchMoveResult batchMoveByFilter(BatchMoveRequest request) {

// Handle collectedAt based on mode
if ("USE_PUBLISH_TIME".equals(mode)) {
// Set collectedAt to publish time (connectedAt), fallback to original collectedAt if null
// Set collectedAt to publish time (connectedAt), fallback to original collectedAt, then createdAt
update.set(root.<Instant>get("collectedAt"),
cb.coalesce(root.<Instant>get("connectedAt"), root.<Instant>get("collectedAt")).as(Instant.class));
cb.coalesce(
cb.coalesce(root.<Instant>get("connectedAt"), root.<Instant>get("collectedAt")),
root.<Instant>get("createdAt")
).as(Instant.class));
} else {
// KEEP mode: keep original collectedAt, fallback to createdAt if null
update.set(root.<Instant>get("collectedAt"),
cb.coalesce(root.<Instant>get("collectedAt"), root.<Instant>get("createdAt")).as(Instant.class));
}
// KEEP mode: don't modify collectedAt

int updated = entityManager.createQuery(update).executeUpdate();
return BatchMoveResult.of(updated, updated);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,16 @@ else if (isLike && hasSetting(setting.getLikeToLibraryType(), setting.getLikeToC
default:
break;
}
if(page.getCollectedAt() == null) {
page.setCollectedAt(Instant.now());
}
}

// Set collection if configured
if (collectionId != null && page.getCollectionId() == null) {
if (collectionId != null) {
page.setCollectionId(collectionId);
// Ensure collectedAt is set when collectionId is set
if (page.getCollectedAt() == null) {
page.setCollectedAt(Instant.now());
page.setCollectedAt(page.getCreatedAt() != null ? page.getCreatedAt() : Instant.now());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ public List<PageItem> getPageItems(PageListQuery listQuery) {
: PageListSort.CREATED_AT.getSortField();
}
var specs = Specifications.<Page>and()
// Skip this filter for multi-field sort (UNSORTED_SAVED_AT) since filterUnsorted handles it
.ne(StringUtils.isNotBlank(sortField) && !listSort.isMultiFieldSort(), sortField, (Object) null)
.ne(StringUtils.isNotBlank(sortField), sortField, (Object) null)
.gt(listQuery.getLastRecordAt() != null && listQuery.isAsc(), sortField, listQuery.getLastRecordAt())
.lt(listQuery.getLastRecordAt() != null && !listQuery.isAsc(), sortField, listQuery.getLastRecordAt())
.lt(listQuery.getFirstRecordAt() != null && listQuery.isAsc(), sortField, listQuery.getFirstRecordAt())
Expand Down Expand Up @@ -131,21 +130,7 @@ public List<PageItem> getPageItems(PageListQuery listQuery) {
.eq("collectionId", (Object) null)
.build())
.build();
// Build sort - handle multi-field sorting for UNSORTED_SAVED_AT
org.springframework.data.domain.Sort sort;
if (listSort.isMultiFieldSort()) {
var sortBuilder = Sorts.builder();
for (String field : listSort.getSortFields()) {
if (listQuery.isAsc()) {
sortBuilder.asc(field);
} else {
sortBuilder.desc(field);
}
}
sort = sortBuilder.build();
} else {
sort = (listQuery.isAsc() ? Sorts.builder().asc(sortField) : Sorts.builder().desc(sortField)).build();
}
org.springframework.data.domain.Sort sort = (listQuery.isAsc() ? Sorts.builder().asc(sortField) : Sorts.builder().desc(sortField)).build();
var size = PageSizeUtils.getPageSize(listQuery.getCount());
List<Page> pages = pageRepository.findAll(specs, size, sort);
// todo enhance query
Expand Down
Loading