Skip to content

Commit

Permalink
feat: series metadata supports alternate titles
Browse files Browse the repository at this point in the history
Closes: #878
  • Loading branch information
gotson committed Jan 16, 2023
1 parent f9f02a3 commit 8e0655f
Show file tree
Hide file tree
Showing 17 changed files with 282 additions and 15 deletions.
12 changes: 11 additions & 1 deletion komga-webui/src/components/ReadMore.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</div>
<template v-slot:more="value">
<v-btn text small color="grey darken-1">
{{ value.open ? $t('read_more.less') : $t('read_more.more') }}
{{ value.open ? $t(i18nLess) : $t(i18nMore) }}
<v-icon right>mdi-chevron-{{ value.open ? 'up' : 'down' }}</v-icon>
</v-btn>
</template>
Expand All @@ -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',
},
},
})
</script>

Expand Down
98 changes: 96 additions & 2 deletions komga-webui/src/components/dialogs/EditSeriesDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<v-icon left class="hidden-xs-only">mdi-format-align-center</v-icon>
{{ $t('dialog.edit_series.tab_general') }}
</v-tab>
<v-tab class="justify-start">
<v-icon left class="hidden-xs-only">mdi-format-title</v-icon>
{{ $t('dialog.edit_series.tab_titles') }}
</v-tab>
<v-tab class="justify-start">
<v-icon left class="hidden-xs-only">mdi-tag-multiple</v-icon>
{{ $t('dialog.edit_series.tab_tags') }}
Expand Down Expand Up @@ -258,6 +262,77 @@
</v-card>
</v-tab-item>

<!-- Tab: Alternate Titles -->
<v-tab-item v-if="single">
<v-card flat min-height="100">
<v-container fluid>
<!-- Titles -->
<v-form
v-model="titlesValid"
ref="titlesForm"
>
<v-row
v-for="(title, i) in form.alternateTitles"
:key="i"
>
<v-col cols="4" class="py-0">
<v-text-field v-model="form.alternateTitles[i].label"
:label="$t('dialog.edit_books.field_link_label')"
filled
dense
:rules="[alternateTitleRules]"
@input="$v.form.alternateTitles.$touch()"
@blur="$v.form.alternateTitles.$touch()"
@change="form.alternateTitlesLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.alternateTitlesLock ? 'secondary' : ''"
@click="form.alternateTitlesLock = !form.alternateTitlesLock"
>
{{ form.linksLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>

<v-col cols="8" class="py-0">
<v-text-field v-model="form.alternateTitles[i].title"
:label="$t('dialog.edit_books.field_alternate_title')"
filled
dense
:rules="[alternateTitleRules]"
@input="$v.form.alternateTitles.$touch()"
@blur="$v.form.alternateTitles.$touch()"
@change="form.alternateTitlesLock = true"
>
<template v-slot:append-outer>
<v-icon @click="form.alternateTitles.splice(i, 1)">mdi-delete</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
</v-form>

<v-row>
<v-spacer/>
<v-col cols="auto">
<v-btn
elevation="2"
fab
small
bottom
right
color="primary"
@click="form.alternateTitles.push({label:'', title:''})"
>
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
</v-card>
</v-tab-item>

<!-- Tab: Tags -->
<v-tab-item>
<v-card flat>
Expand Down Expand Up @@ -500,6 +575,7 @@ export default Vue.extend({
modal: false,
tab: 0,
linksValid: false,
titlesValid: false,
form: {
status: '',
statusLock: false,
Expand Down Expand Up @@ -527,6 +603,8 @@ export default Vue.extend({
sharingLabelsLock: false,
links: [],
linksLock: false,
alternateTitles: [],
alternateTitlesLock: false,
},
mixed: {
status: false,
Expand Down Expand Up @@ -601,6 +679,7 @@ export default Vue.extend({
publisher: {},
totalBookCount: {minValue: minValue(1)},
links: {},
alternateTitles: {},
},
},
computed: {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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 = []
Expand All @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions komga-webui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
Expand Down
13 changes: 11 additions & 2 deletions komga-webui/src/types/komga-series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -88,6 +90,8 @@ export interface SeriesMetadataUpdateDto {
sharingLabelsLock: boolean,
links?: WebLinkDto[],
linksLock?: boolean,
alternateTitles?: AlternateTitleDto[],
alternateTitlesLock?: boolean,
}

export interface GroupCountDto {
Expand All @@ -101,3 +105,8 @@ export interface SeriesThumbnailDto {
type: string,
selected: boolean
}

export interface AlternateTitleDto {
label: string,
title: string
}
33 changes: 29 additions & 4 deletions komga-webui/src/views/BrowseSeries.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@
<v-tooltip right>
<template v-slot:activator="{ on }">
<span v-on="on">{{
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))
}}</span>
</template>
{{ $t('browse_series.earliest_year_from_release_dates') }}
Expand Down Expand Up @@ -166,6 +169,17 @@
</v-row>

<template v-if="$vuetify.breakpoint.smAndUp">
<!-- Alternate titles -->
<read-more class="mb-4" i18n-less="titles_more.less" i18n-more="titles_more.more">
<v-row v-for="(a, i) in series.metadata.alternateTitles"
:key="i"
class="align-center text-caption"
>
<v-col cols="4" sm="3" md="2" xl="1" class="py-0 text-uppercase" :class="i===0 ? 'pt-4' : i === series.metadata.alternateTitles.length - 1 ? 'pb-4' : ''">{{ a.label }}</v-col>
<v-col cols="8" sm="9" md="10" xl="11" class="py-0" :class="i===0 ? 'pt-4' : i === series.metadata.alternateTitles.length - 1 ? 'pb-4' : ''">{{ a.title }}</v-col>
</v-row>
</read-more>

<v-row class="align-center">
<v-col cols="auto">
<v-btn :title="$t('menu.download_series')"
Expand Down Expand Up @@ -203,6 +217,17 @@
</v-row>

<template v-if="$vuetify.breakpoint.xsOnly">
<!-- Alternate titles -->
<read-more class="mb-4" i18n-less="titles_more.less" i18n-more="titles_more.more">
<v-row v-for="(a, i) in series.metadata.alternateTitles"
:key="i"
class="align-center text-caption"
>
<v-col cols="4" class="py-0 text-uppercase" :class="i===0 ? 'pt-4' : i === series.metadata.alternateTitles.length - 1 ? 'pb-4' : ''">{{ a.label }}</v-col>
<v-col cols="8" class="py-0" :class="i===0 ? 'pt-4' : i === series.metadata.alternateTitles.length - 1 ? 'pb-4' : ''">{{ a.title }}</v-col>
</v-row>
</read-more>

<!-- Download button -->
<v-row class="align-center">
<v-col cols="auto">
Expand Down Expand Up @@ -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[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.gotson.komga.domain.model

data class AlternateTitle(
val label: String,
val title: String,
)
Loading

0 comments on commit 8e0655f

Please sign in to comment.