diff --git a/komga-webui/src/components/ReadMore.vue b/komga-webui/src/components/ReadMore.vue
index b18f95ae..cb1075ad 100644
--- a/komga-webui/src/components/ReadMore.vue
+++ b/komga-webui/src/components/ReadMore.vue
@@ -5,7 +5,7 @@
- {{ value.open ? $t('read_more.less') : $t('read_more.more') }}
+ {{ value.open ? $t(i18nLess) : $t(i18nMore) }}
mdi-chevron-{{ value.open ? 'up' : 'down' }}
@@ -20,6 +20,16 @@ import Vue from 'vue'
export default Vue.extend({
name: 'ReadMore',
components: { VueReadMoreSmooth },
+ props: {
+ i18nMore: {
+ type: String,
+ default: 'read_more.more',
+ },
+ i18nLess: {
+ type: String,
+ default: 'read_more.less',
+ },
+ },
})
diff --git a/komga-webui/src/components/dialogs/EditSeriesDialog.vue b/komga-webui/src/components/dialogs/EditSeriesDialog.vue
index f0a682e3..fd49d350 100644
--- a/komga-webui/src/components/dialogs/EditSeriesDialog.vue
+++ b/komga-webui/src/components/dialogs/EditSeriesDialog.vue
@@ -27,6 +27,10 @@
mdi-format-align-center
{{ $t('dialog.edit_series.tab_general') }}
+
+ mdi-format-title
+ {{ $t('dialog.edit_series.tab_titles') }}
+
mdi-tag-multiple
{{ $t('dialog.edit_series.tab_tags') }}
@@ -258,6 +262,77 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ form.linksLock ? 'mdi-lock' : 'mdi-lock-open' }}
+
+
+
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
+
+
+
+ mdi-plus
+
+
+
+
+
+
+
@@ -500,6 +575,7 @@ export default Vue.extend({
modal: false,
tab: 0,
linksValid: false,
+ titlesValid: false,
form: {
status: '',
statusLock: false,
@@ -527,6 +603,8 @@ export default Vue.extend({
sharingLabelsLock: false,
links: [],
linksLock: false,
+ alternateTitles: [],
+ alternateTitlesLock: false,
},
mixed: {
status: false,
@@ -601,6 +679,7 @@ export default Vue.extend({
publisher: {},
totalBookCount: {minValue: minValue(1)},
links: {},
+ alternateTitles: {},
},
},
computed: {
@@ -645,6 +724,10 @@ export default Vue.extend({
},
},
methods: {
+ alternateTitleRules(text: string): boolean | string {
+ if (!!this.$_.trim(text)) return true
+ return this.$t('common.required').toString()
+ },
linksLabelRules(label: string): boolean | string {
if (!!this.$_.trim(label)) return true
return this.$t('common.required').toString()
@@ -678,7 +761,8 @@ export default Vue.extend({
dialogReset(series: SeriesDto | SeriesDto[]) {
this.tab = 0
this.$v.$reset();
- (this.$refs.linksForm as any)?.resetValidation()
+ (this.$refs.linksForm as any)?.resetValidation();
+ (this.$refs.titlesForm as any)?.resetValidation()
if (Array.isArray(series) && series.length === 0) return
if (Array.isArray(series) && series.length > 0) {
const status = this.$_.uniq(series.map(x => x.metadata.status))
@@ -732,11 +816,13 @@ export default Vue.extend({
this.form.sharingLabelsLock = sharingLabelsLock.length > 1 ? false : sharingLabelsLock[0]
this.form.links = []
+ this.form.alternateTitles = []
} else {
this.form.genres = []
this.form.tags = []
this.form.sharingLabels = []
this.form.links = []
+ this.form.alternateTitles = []
this.$_.merge(this.form, (series as SeriesDto).metadata)
this.poster.selectedThumbnail = ''
this.poster.deleteQueue = []
@@ -754,7 +840,10 @@ export default Vue.extend({
}
},
validateForm(): any {
- if (!this.$v.$invalid && (!this.single || !this.$refs.linksForm || (this.$refs.linksForm as any).validate())) {
+ if (!this.$v.$invalid
+ && (!this.single || !this.$refs.linksForm || (this.$refs.linksForm as any).validate())
+ && (!this.single || !this.$refs.titlesForm || (this.$refs.titlesForm as any).validate())
+ ) {
const metadata = {
statusLock: this.form.statusLock,
readingDirectionLock: this.form.readingDirectionLock,
@@ -766,6 +855,7 @@ export default Vue.extend({
totalBookCountLock: this.form.totalBookCountLock,
sharingLabelsLock: this.form.sharingLabelsLock,
linksLock: this.form.linksLock,
+ alternateTitlesLock: this.form.alternateTitlesLock,
}
if (this.$v.form?.status?.$dirty) {
@@ -827,6 +917,10 @@ export default Vue.extend({
if (this.$v.form?.links?.$dirty || this.form.links.length != (this.series as SeriesDto).metadata.links?.length) {
this.$_.merge(metadata, {links: this.form.links})
}
+
+ if (this.$v.form?.alternateTitles?.$dirty || this.form.alternateTitles.length != (this.series as SeriesDto).metadata.alternateTitles?.length) {
+ this.$_.merge(metadata, {alternateTitles: this.form.alternateTitles})
+ }
}
return metadata
diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json
index b1a6dd8a..63ccb52e 100644
--- a/komga-webui/src/locales/en.json
+++ b/komga-webui/src/locales/en.json
@@ -347,6 +347,7 @@
"button_confirm": "Save changes",
"dialog_title_multiple": "Edit {count} book | Edit {count} books",
"dialog_title_single": "Edit {book}",
+ "field_alternate_title": "Alternate title",
"field_isbn": "ISBN",
"field_isbn_error": "Must be a valid ISBN 13",
"field_link_label": "Label",
@@ -457,6 +458,7 @@
"tab_poster": "Poster",
"tab_sharing": "Sharing",
"tab_tags": "Tags",
+ "tab_titles": "Alternate Titles",
"tags_notice_multiple_edit": "You are editing tags for multiple series. This will override existing tags of each series."
},
"edit_user": {
@@ -812,6 +814,10 @@
"tooltip_too_big": "File too big!",
"tooltip_user_uploaded": "User uploaded"
},
+ "titles_more": {
+ "less": "Less titles",
+ "more": "More titles"
+ },
"user_roles": {
"ADMIN": "Administrator",
"FILE_DOWNLOAD": "File download",
diff --git a/komga-webui/src/types/komga-series.ts b/komga-webui/src/types/komga-series.ts
index 83523e49..bc2962c9 100644
--- a/komga-webui/src/types/komga-series.ts
+++ b/komga-webui/src/types/komga-series.ts
@@ -47,8 +47,10 @@ export interface SeriesMetadataDto {
totalBookCountLock: boolean,
sharingLabels: string[],
sharingLabelsLock: boolean,
- links?: WebLinkDto[],
- linksLock?: boolean
+ links: WebLinkDto[],
+ linksLock: boolean,
+ alternateTitles: AlternateTitleDto[],
+ alternateTitlesLock: boolean,
}
export interface SeriesBooksMetadataDto {
@@ -88,6 +90,8 @@ export interface SeriesMetadataUpdateDto {
sharingLabelsLock: boolean,
links?: WebLinkDto[],
linksLock?: boolean,
+ alternateTitles?: AlternateTitleDto[],
+ alternateTitlesLock?: boolean,
}
export interface GroupCountDto {
@@ -101,3 +105,8 @@ export interface SeriesThumbnailDto {
type: string,
selected: boolean
}
+
+export interface AlternateTitleDto {
+ label: string,
+ title: string
+}
diff --git a/komga-webui/src/views/BrowseSeries.vue b/komga-webui/src/views/BrowseSeries.vue
index 61e1e7f5..6387cd79 100644
--- a/komga-webui/src/views/BrowseSeries.vue
+++ b/komga-webui/src/views/BrowseSeries.vue
@@ -113,7 +113,10 @@
{{
- new Intl.DateTimeFormat($i18n.locale, {year: 'numeric', timeZone: 'UTC'}).format(new Date(series.booksMetadata.releaseDate))
+ new Intl.DateTimeFormat($i18n.locale, {
+ year: 'numeric',
+ timeZone: 'UTC'
+ }).format(new Date(series.booksMetadata.releaseDate))
}}
{{ $t('browse_series.earliest_year_from_release_dates') }}
@@ -166,6 +169,17 @@
+
+
+
+ {{ a.label }}
+ {{ a.title }}
+
+
+
+
+
+
+ {{ a.label }}
+ {{ a.title }}
+
+
+
@@ -529,9 +554,9 @@ export default Vue.extend({
},
computed: {
itemContext(): ItemContext[] {
- if(this.sortActive.key === 'metadata.releaseDate') return [ItemContext.RELEASE_DATE]
- if(this.sortActive.key === 'createdDate') return [ItemContext.DATE_ADDED]
- if(this.sortActive.key === 'fileSize') return [ItemContext.FILE_SIZE]
+ if (this.sortActive.key === 'metadata.releaseDate') return [ItemContext.RELEASE_DATE]
+ if (this.sortActive.key === 'createdDate') return [ItemContext.DATE_ADDED]
+ if (this.sortActive.key === 'fileSize') return [ItemContext.FILE_SIZE]
return []
},
sortOptions(): SortOption[] {
diff --git a/komga/src/flyway/resources/db/migration/sqlite/V20230116112647__series_alternate_title.sql b/komga/src/flyway/resources/db/migration/sqlite/V20230116112647__series_alternate_title.sql
new file mode 100644
index 00000000..996a2b4e
--- /dev/null
+++ b/komga/src/flyway/resources/db/migration/sqlite/V20230116112647__series_alternate_title.sql
@@ -0,0 +1,13 @@
+CREATE TABLE SERIES_METADATA_ALTERNATE_TITLE
+(
+ LABEL varchar NOT NULL,
+ TITLE varchar NOT NULL,
+ SERIES_ID varchar NOT NULL,
+ FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID)
+);
+
+alter table series_metadata
+ add column ALTERNATE_TITLES_LOCK boolean NOT NULL DEFAULT 0;
+
+create index idx__series_metadata_alternate_title__series_id
+ on SERIES_METADATA_ALTERNATE_TITLE (SERIES_ID);
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/AlternateTitle.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/AlternateTitle.kt
new file mode 100644
index 00000000..f781fd1f
--- /dev/null
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/AlternateTitle.kt
@@ -0,0 +1,6 @@
+package org.gotson.komga.domain.model
+
+data class AlternateTitle(
+ val label: String,
+ val title: String,
+)
diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt
index 7f853292..5067d3b4 100644
--- a/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesMetadata.kt
@@ -17,6 +17,7 @@ class SeriesMetadata(
val totalBookCount: Int? = null,
sharingLabels: Set = emptySet(),
val links: List = emptyList(),
+ val alternateTitles: List = emptyList(),
val statusLock: Boolean = false,
val titleLock: Boolean = false,
@@ -31,6 +32,7 @@ class SeriesMetadata(
val totalBookCountLock: Boolean = false,
val sharingLabelsLock: Boolean = false,
val linksLock: Boolean = false,
+ val alternateTitlesLock: Boolean = false,
val seriesId: String = "",
@@ -60,6 +62,7 @@ class SeriesMetadata(
totalBookCount: Int? = this.totalBookCount,
sharingLabels: Set = this.sharingLabels,
links: List = this.links,
+ alternateTitles: List = this.alternateTitles,
statusLock: Boolean = this.statusLock,
titleLock: Boolean = this.titleLock,
titleSortLock: Boolean = this.titleSortLock,
@@ -73,6 +76,7 @@ class SeriesMetadata(
totalBookCountLock: Boolean = this.totalBookCountLock,
sharingLabelsLock: Boolean = this.sharingLabelsLock,
linksLock: Boolean = this.linksLock,
+ alternateTitlesLock: Boolean = this.alternateTitlesLock,
seriesId: String = this.seriesId,
createdDate: LocalDateTime = this.createdDate,
lastModifiedDate: LocalDateTime = this.lastModifiedDate,
@@ -91,6 +95,7 @@ class SeriesMetadata(
totalBookCount = totalBookCount,
sharingLabels = sharingLabels,
links = links,
+ alternateTitles = alternateTitles,
statusLock = statusLock,
titleLock = titleLock,
titleSortLock = titleSortLock,
@@ -104,6 +109,7 @@ class SeriesMetadata(
totalBookCountLock = totalBookCountLock,
sharingLabelsLock = sharingLabelsLock,
linksLock = linksLock,
+ alternateTitlesLock = alternateTitlesLock,
seriesId = seriesId,
createdDate = createdDate,
lastModifiedDate = lastModifiedDate,
@@ -121,5 +127,5 @@ class SeriesMetadata(
}
override fun toString(): String =
- "SeriesMetadata(status=$status, readingDirection=$readingDirection, ageRating=$ageRating, totalBookCount=$totalBookCount, links=$links, statusLock=$statusLock, titleLock=$titleLock, titleSortLock=$titleSortLock, summaryLock=$summaryLock, readingDirectionLock=$readingDirectionLock, publisherLock=$publisherLock, ageRatingLock=$ageRatingLock, languageLock=$languageLock, genresLock=$genresLock, tagsLock=$tagsLock, totalBookCountLock=$totalBookCountLock, sharingLabelsLock=$sharingLabelsLock, linksLock=$linksLock, seriesId='$seriesId', createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', titleSort='$titleSort', summary='$summary', publisher='$publisher', language='$language', tags=$tags, genres=$genres, sharingLabels=$sharingLabels)"
+ "SeriesMetadata(status=$status, readingDirection=$readingDirection, ageRating=$ageRating, totalBookCount=$totalBookCount, links=$links, alternateTitles=$alternateTitles, statusLock=$statusLock, titleLock=$titleLock, titleSortLock=$titleSortLock, summaryLock=$summaryLock, readingDirectionLock=$readingDirectionLock, publisherLock=$publisherLock, ageRatingLock=$ageRatingLock, languageLock=$languageLock, genresLock=$genresLock, tagsLock=$tagsLock, totalBookCountLock=$totalBookCountLock, sharingLabelsLock=$sharingLabelsLock, linksLock=$linksLock, alternateTitlesLock=$alternateTitlesLock, seriesId='$seriesId', createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', titleSort='$titleSort', summary='$summary', publisher='$publisher', language='$language', tags=$tags, genres=$genres, sharingLabels=$sharingLabels)"
}
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt
index d7a8c755..fc086f37 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt
@@ -10,6 +10,7 @@ import org.gotson.komga.infrastructure.search.LuceneEntity
import org.gotson.komga.infrastructure.search.LuceneHelper
import org.gotson.komga.infrastructure.web.toFilePath
import org.gotson.komga.interfaces.api.persistence.SeriesDtoRepository
+import org.gotson.komga.interfaces.api.rest.dto.AlternateTitleDto
import org.gotson.komga.interfaces.api.rest.dto.AuthorDto
import org.gotson.komga.interfaces.api.rest.dto.BookMetadataAggregationDto
import org.gotson.komga.interfaces.api.rest.dto.GroupCountDto
@@ -62,6 +63,7 @@ class SeriesDtoDao(
private val st = Tables.SERIES_METADATA_TAG
private val sl = Tables.SERIES_METADATA_SHARING
private val slk = Tables.SERIES_METADATA_LINK
+ private val sat = Tables.SERIES_METADATA_ALTERNATE_TITLE
private val bma = Tables.BOOK_METADATA_AGGREGATION
private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
private val bmat = Tables.BOOK_METADATA_AGGREGATION_TAG
@@ -230,6 +232,7 @@ class SeriesDtoDao(
lateinit var tags: Map>
lateinit var sharingLabels: Map>
lateinit var links: Map>
+ lateinit var alternateTitles: Map>
lateinit var aggregatedAuthors: Map>
lateinit var aggregatedTags: Map>
transactionTemplate.executeWithoutResult {
@@ -250,6 +253,10 @@ class SeriesDtoDao(
.where(slk.SERIES_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.seriesId }, { WebLinkDto(it.label, it.url) })
+ alternateTitles = dsl.selectFrom(sat)
+ .where(sat.SERIES_ID.`in`(dsl.selectTempStrings()))
+ .groupBy({ it.seriesId }, { AlternateTitleDto(it.label, it.title) })
+
aggregatedAuthors = dsl.selectFrom(bmaa)
.where(bmaa.SERIES_ID.`in`(dsl.selectTempStrings()))
.filter { it.name != null }
@@ -275,7 +282,7 @@ class SeriesDtoDao(
booksReadCount,
booksUnreadCount,
booksInProgressCount,
- dr.toDto(genres[sr.id].orEmpty().toSet(), tags[sr.id].orEmpty().toSet(), sharingLabels[sr.id].orEmpty().toSet(), links[sr.id].orEmpty()),
+ dr.toDto(genres[sr.id].orEmpty().toSet(), tags[sr.id].orEmpty().toSet(), sharingLabels[sr.id].orEmpty().toSet(), links[sr.id].orEmpty(), alternateTitles[sr.id].orEmpty()),
bmar.toDto(aggregatedAuthors[sr.id].orEmpty(), aggregatedTags[sr.id].orEmpty().toSet()),
)
}
@@ -372,7 +379,7 @@ class SeriesDtoDao(
deleted = deletedDate != null,
)
- private fun SeriesMetadataRecord.toDto(genres: Set, tags: Set, sharingLabels: Set, links: List) =
+ private fun SeriesMetadataRecord.toDto(genres: Set, tags: Set, sharingLabels: Set, links: List, alternateTitles: List) =
SeriesMetadataDto(
status = status,
statusLock = statusLock,
@@ -402,6 +409,8 @@ class SeriesDtoDao(
sharingLabelsLock = sharingLabelsLock,
links = links,
linksLock = linksLock,
+ alternateTitles = alternateTitles,
+ alternateTitlesLock = alternateTitlesLock,
)
private fun BookMetadataAggregationRecord.toDto(authors: List, tags: Set) =
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt
index 90297f08..b16aea70 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDao.kt
@@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq
+import org.gotson.komga.domain.model.AlternateTitle
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.WebLink
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
@@ -24,12 +25,13 @@ class SeriesMetadataDao(
private val st = Tables.SERIES_METADATA_TAG
private val sl = Tables.SERIES_METADATA_SHARING
private val slk = Tables.SERIES_METADATA_LINK
+ private val sat = Tables.SERIES_METADATA_ALTERNATE_TITLE
override fun findById(seriesId: String): SeriesMetadata =
- findOne(seriesId)!!.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId))
+ findOne(seriesId)!!.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId), findAlternateTitles(seriesId))
override fun findByIdOrNull(seriesId: String): SeriesMetadata? =
- findOne(seriesId)?.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId))
+ findOne(seriesId)?.toDomain(findGenres(seriesId), findTags(seriesId), findSharingLabels(seriesId), findLinks(seriesId), findAlternateTitles(seriesId))
private fun findOne(seriesId: String) =
dsl.selectFrom(d)
@@ -53,6 +55,7 @@ class SeriesMetadataDao(
.from(sl)
.where(sl.SERIES_ID.eq(seriesId))
.fetchSet(sl.LABEL)
+
private fun findLinks(seriesId: String) =
dsl.select(slk.LABEL, slk.URL)
.from(slk)
@@ -60,6 +63,13 @@ class SeriesMetadataDao(
.fetchInto(slk)
.map { WebLink(it.label, URI(it.url)) }
+ private fun findAlternateTitles(seriesId: String) =
+ dsl.select(sat.LABEL, sat.TITLE)
+ .from(sat)
+ .where(sat.SERIES_ID.eq(seriesId))
+ .fetchInto(sat)
+ .map { AlternateTitle(it.label, it.title) }
+
@Transactional
override fun insert(metadata: SeriesMetadata) {
dsl.insertInto(d)
@@ -86,12 +96,14 @@ class SeriesMetadataDao(
.set(d.TOTAL_BOOK_COUNT_LOCK, metadata.totalBookCountLock)
.set(d.SHARING_LABELS_LOCK, metadata.sharingLabelsLock)
.set(d.LINKS_LOCK, metadata.linksLock)
+ .set(d.ALTERNATE_TITLES_LOCK, metadata.alternateTitlesLock)
.execute()
insertGenres(metadata)
insertTags(metadata)
insertSharingLabels(metadata)
insertLinks(metadata)
+ insertAlternateTitles(metadata)
}
@Transactional
@@ -119,6 +131,7 @@ class SeriesMetadataDao(
.set(d.TOTAL_BOOK_COUNT_LOCK, metadata.totalBookCountLock)
.set(d.SHARING_LABELS_LOCK, metadata.sharingLabelsLock)
.set(d.LINKS_LOCK, metadata.linksLock)
+ .set(d.ALTERNATE_TITLES_LOCK, metadata.alternateTitlesLock)
.set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
.where(d.SERIES_ID.eq(metadata.seriesId))
.execute()
@@ -139,10 +152,15 @@ class SeriesMetadataDao(
.where(slk.SERIES_ID.eq(metadata.seriesId))
.execute()
+ dsl.deleteFrom(sat)
+ .where(sat.SERIES_ID.eq(metadata.seriesId))
+ .execute()
+
insertGenres(metadata)
insertTags(metadata)
insertSharingLabels(metadata)
insertLinks(metadata)
+ insertAlternateTitles(metadata)
}
private fun insertGenres(metadata: SeriesMetadata) {
@@ -205,12 +223,28 @@ class SeriesMetadataDao(
}
}
+ private fun insertAlternateTitles(metadata: SeriesMetadata) {
+ if (metadata.alternateTitles.isNotEmpty()) {
+ metadata.alternateTitles.chunked(batchSize).forEach { chunk ->
+ dsl.batch(
+ dsl.insertInto(sat, sat.SERIES_ID, sat.LABEL, sat.TITLE)
+ .values(null as String?, null, null),
+ ).also { step ->
+ chunk.forEach {
+ step.bind(metadata.seriesId, it.label, it.title)
+ }
+ }.execute()
+ }
+ }
+ }
+
@Transactional
override fun delete(seriesId: String) {
dsl.deleteFrom(g).where(g.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(st).where(st.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(sl).where(sl.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(slk).where(slk.SERIES_ID.eq(seriesId)).execute()
+ dsl.deleteFrom(sat).where(sat.SERIES_ID.eq(seriesId)).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
}
@@ -222,12 +256,13 @@ class SeriesMetadataDao(
dsl.deleteFrom(st).where(st.SERIES_ID.`in`(dsl.selectTempStrings())).execute()
dsl.deleteFrom(sl).where(sl.SERIES_ID.`in`(dsl.selectTempStrings())).execute()
dsl.deleteFrom(slk).where(slk.SERIES_ID.`in`(dsl.selectTempStrings())).execute()
+ dsl.deleteFrom(sat).where(sat.SERIES_ID.`in`(dsl.selectTempStrings())).execute()
dsl.deleteFrom(d).where(d.SERIES_ID.`in`(dsl.selectTempStrings())).execute()
}
override fun count(): Long = dsl.fetchCount(d).toLong()
- private fun SeriesMetadataRecord.toDomain(genres: Set, tags: Set, sharingLabels: Set, links: List) =
+ private fun SeriesMetadataRecord.toDomain(genres: Set, tags: Set, sharingLabels: Set, links: List, alternateTitles: List) =
SeriesMetadata(
status = SeriesMetadata.Status.valueOf(status),
title = title,
@@ -244,6 +279,7 @@ class SeriesMetadataDao(
totalBookCount = totalBookCount,
sharingLabels = sharingLabels,
links = links,
+ alternateTitles = alternateTitles,
statusLock = statusLock,
titleLock = titleLock,
@@ -258,6 +294,7 @@ class SeriesMetadataDao(
totalBookCountLock = totalBookCountLock,
sharingLabelsLock = sharingLabelsLock,
linksLock = linksLock,
+ alternateTitlesLock = alternateTitlesLock,
seriesId = seriesId,
diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneEntity.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneEntity.kt
index 93ad6ad1..9d42eaaf 100644
--- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneEntity.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/search/LuceneEntity.kt
@@ -45,6 +45,7 @@ fun SeriesDto.toDocument() =
Document().apply {
add(TextField("title", metadata.title, Field.Store.NO))
if (metadata.titleSort != metadata.title) add(TextField("title", metadata.titleSort, Field.Store.NO))
+ metadata.alternateTitles.forEach { add(TextField("title", it.title, Field.Store.NO)) }
add(TextField("publisher", metadata.publisher, Field.Store.NO))
add(TextField("status", metadata.status, Field.Store.NO))
add(TextField("reading_direction", metadata.readingDirection, Field.Store.NO))
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt
index 746d0063..a76a3075 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/SeriesController.kt
@@ -16,6 +16,7 @@ import org.gotson.komga.application.events.EventPublisher
import org.gotson.komga.application.tasks.HIGHEST_PRIORITY
import org.gotson.komga.application.tasks.HIGH_PRIORITY
import org.gotson.komga.application.tasks.TaskEmitter
+import org.gotson.komga.domain.model.AlternateTitle
import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.model.BookSearchWithReadProgress
import org.gotson.komga.domain.model.DomainEvent
@@ -550,6 +551,10 @@ class SeriesController(
if (links != null) links!!.map { WebLink(it.label!!, URI(it.url!!)) } else emptyList()
} else existing.links,
linksLock = linksLock ?: existing.linksLock,
+ alternateTitles = if (isSet("alternateTitles")) {
+ if (alternateTitles != null) alternateTitles!!.map { AlternateTitle(it.label!!, it.title!!) } else emptyList()
+ } else existing.alternateTitles,
+ alternateTitlesLock = alternateTitlesLock ?: existing.alternateTitlesLock,
)
}
seriesMetadataRepository.update(updated)
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleDto.kt
new file mode 100644
index 00000000..4756e49d
--- /dev/null
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleDto.kt
@@ -0,0 +1,10 @@
+package org.gotson.komga.interfaces.api.rest.dto
+
+import org.gotson.komga.domain.model.AlternateTitle
+
+data class AlternateTitleDto(
+ val label: String,
+ val title: String,
+)
+
+fun AlternateTitle.toDto() = AlternateTitleDto(label, title)
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleUpdateDto.kt
new file mode 100644
index 00000000..8665a79c
--- /dev/null
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/AlternateTitleUpdateDto.kt
@@ -0,0 +1,11 @@
+package org.gotson.komga.interfaces.api.rest.dto
+
+import javax.validation.constraints.NotBlank
+
+class AlternateTitleUpdateDto {
+ @get:NotBlank
+ val label: String? = null
+
+ @get:NotBlank
+ val title: String? = null
+}
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt
index 8c154f18..ede89862 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesDto.kt
@@ -54,6 +54,8 @@ data class SeriesMetadataDto(
val sharingLabelsLock: Boolean,
val links: List,
val linksLock: Boolean,
+ val alternateTitles: List,
+ val alternateTitlesLock: Boolean,
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
val created: LocalDateTime,
diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt
index 83323bdf..8c9f3133 100644
--- a/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt
+++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/api/rest/dto/SeriesMetadataUpdateDto.kt
@@ -90,4 +90,12 @@ class SeriesMetadataUpdateDto {
}
var linksLock: Boolean? = null
+
+ @get:Valid
+ var alternateTitles: List?
+ by Delegates.observable(null) { prop, _, _ ->
+ isSet[prop.name] = true
+ }
+
+ var alternateTitlesLock: Boolean? = null
}
diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt
index c0728352..019ceab8 100644
--- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt
+++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/SeriesMetadataDaoTest.kt
@@ -2,6 +2,7 @@ package org.gotson.komga.infrastructure.jooq
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
+import org.gotson.komga.domain.model.AlternateTitle
import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.WebLink
import org.gotson.komga.domain.model.makeLibrary
@@ -66,6 +67,7 @@ class SeriesMetadataDaoTest(
totalBookCount = 5,
sharingLabels = setOf("kids"),
links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))),
+ alternateTitles = listOf(AlternateTitle("fr", "La Series")),
titleLock = true,
titleSortLock = true,
summaryLock = true,
@@ -78,6 +80,7 @@ class SeriesMetadataDaoTest(
totalBookCountLock = true,
sharingLabelsLock = true,
linksLock = true,
+ alternateTitlesLock = true,
seriesId = series.id,
)
@@ -104,6 +107,10 @@ class SeriesMetadataDaoTest(
assertThat(label).isEqualTo(metadata.links.first().label)
assertThat(url).isEqualTo(metadata.links.first().url)
}
+ with(created.alternateTitles.first()) {
+ assertThat(label).isEqualTo(metadata.alternateTitles.first().label)
+ assertThat(title).isEqualTo(metadata.alternateTitles.first().title)
+ }
assertThat(created.titleLock).isEqualTo(metadata.titleLock)
assertThat(created.titleSortLock).isEqualTo(metadata.titleSortLock)
@@ -118,6 +125,7 @@ class SeriesMetadataDaoTest(
assertThat(created.totalBookCountLock).isEqualTo(metadata.totalBookCountLock)
assertThat(created.sharingLabelsLock).isEqualTo(metadata.sharingLabelsLock)
assertThat(created.linksLock).isEqualTo(metadata.linksLock)
+ assertThat(created.alternateTitlesLock).isEqualTo(metadata.alternateTitlesLock)
}
@Test
@@ -150,6 +158,7 @@ class SeriesMetadataDaoTest(
assertThat(created.totalBookCount).isNull()
assertThat(created.sharingLabels).isEmpty()
assertThat(created.links).isEmpty()
+ assertThat(created.alternateTitles).isEmpty()
assertThat(created.titleLock).isFalse
assertThat(created.titleSortLock).isFalse
@@ -164,6 +173,7 @@ class SeriesMetadataDaoTest(
assertThat(created.totalBookCountLock).isFalse
assertThat(created.sharingLabelsLock).isFalse
assertThat(created.linksLock).isFalse
+ assertThat(created.alternateTitlesLock).isFalse
}
@Test
@@ -217,6 +227,7 @@ class SeriesMetadataDaoTest(
totalBookCount = 3,
sharingLabels = setOf("kids"),
links = listOf(WebLink("Comicvine", URI("https://comicvine.gamespot.com/doctor-strange/4050-2676/"))),
+ alternateTitles = listOf(AlternateTitle("fr", "La Series")),
seriesId = series.id,
)
seriesMetadataDao.insert(metadata)
@@ -239,6 +250,7 @@ class SeriesMetadataDaoTest(
totalBookCount = 8,
sharingLabels = setOf("adult"),
links = emptyList(),
+ alternateTitles = emptyList(),
statusLock = true,
titleLock = true,
titleSortLock = true,
@@ -252,6 +264,7 @@ class SeriesMetadataDaoTest(
totalBookCountLock = true,
sharingLabelsLock = true,
linksLock = true,
+ alternateTitlesLock = true,
)
}
@@ -276,6 +289,7 @@ class SeriesMetadataDaoTest(
assertThat(modified.totalBookCount).isEqualTo(updated.totalBookCount)
assertThat(modified.sharingLabels).containsAll(updated.sharingLabels)
assertThat(modified.links).isEmpty()
+ assertThat(modified.alternateTitles).isEmpty()
assertThat(modified.titleLock).isTrue
assertThat(modified.titleSortLock).isTrue
@@ -290,5 +304,6 @@ class SeriesMetadataDaoTest(
assertThat(modified.totalBookCountLock).isTrue
assertThat(modified.sharingLabelsLock).isTrue
assertThat(modified.linksLock).isTrue
+ assertThat(modified.alternateTitlesLock).isTrue
}
}