+
+
+ @if (selectedItem.canSync) {
+
+
{{t('last-synced', {date: (selectedItem.lastSyncedUtc | utcToLocalDate | timeAgo)})}}
+
{{t('last-checked', {date: (selectedItem.lastSyncCheckUtc | utcToLocalDate | timeAgo)})}}
@if (selectedItem.canSync) {
-
-
{{t('last-synced', {date: (selectedItem.lastSyncedUtc | utcToLocalDate | timeAgo)})}}
-
{{t('last-checked', {date: (selectedItem.lastSyncCheckUtc | utcToLocalDate | timeAgo)})}}
- @if (selectedItem.canSync) {
-
- }
+
}
-
+ }
diff --git a/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.scss b/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.scss
index e23e813d3..fa8802a1b 100644
--- a/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.scss
+++ b/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.scss
@@ -1,23 +1,11 @@
-
-ngx-file-drop ::ng-deep > div {
- // styling for the outer drop box
- width: 100%;
- border: 0.125rem solid var(--primary-color);
- border-radius: 0.3125rem;
- height: 6.25rem;
- margin: auto;
-
- > div {
- // styling for the inner box (template)
- width: 100%;
- display: inline-block;
-
- }
-}
-
.custom-position {
right: 0.9375rem;
top: -2.625rem;
+ @media (max-width: 576px) {
+ position: static !important;
+ justify-content: flex-end;
+ padding: 0 0 0.5rem;
+ }
}
.section-header {
@@ -31,7 +19,8 @@ ngx-file-drop ::ng-deep > div {
flex-direction: column;
@media (max-width: 576px) {
- max-height: calc(100dvh - 30rem);
+ max-height: 50dvh;
+ overflow-y: auto;
}
> div {
@@ -59,8 +48,20 @@ ngx-file-drop ::ng-deep > div {
color: var(--primary-color);
}
-.btn-group .btn {
- font-size: 0.8rem;
+.filter-row {
+ @media (max-width: 576px) {
+ flex-direction: column;
+ align-items: flex-start !important;
+ gap: 0.5rem !important;
+ }
+}
+
+.detail-panel {
+ @media (max-width: 767px) {
+ &.is-hidden-mobile {
+ display: none;
+ }
+ }
}
.detail-cover {
@@ -75,3 +76,13 @@ ngx-file-drop ::ng-deep > div {
border-radius: 0.25rem;
}
}
+
+.btn-group .btn {
+ font-size: 0.8rem;
+}
+
+.btn-group > .btn.dropdown-toggle-split {
+ border-top-right-radius: var(--bs-border-radius) !important;
+ border-bottom-right-radius: var(--bs-border-radius) !important;
+ border-width: 1px 1px 1px 0 !important;
+}
diff --git a/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.ts b/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.ts
index aa4951b2e..d029069e9 100644
--- a/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.ts
+++ b/UI/Web/src/app/user-settings/cbl-manager/cbl-manager.component.ts
@@ -32,7 +32,7 @@ import {TimeAgoPipe} from "../../_pipes/time-ago.pipe";
import {AgeRatingImageComponent} from "../../_single-module/age-rating-image/age-rating-image.component";
import {DateYearRangePipe} from "../../_pipes/date-year-range.pipe";
import {SafeUrlPipe} from "../../_pipes/safe-url.pipe";
-import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
+import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
@Component({
selector: 'app-cbl-manager',
@@ -52,7 +52,11 @@ import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
TimeAgoPipe,
AgeRatingImageComponent,
SafeUrlPipe,
- NgbTooltip
+ NgbTooltip,
+ NgbDropdown,
+ NgbDropdownItem,
+ NgbDropdownMenu,
+ NgbDropdownToggle
],
templateUrl: './cbl-manager.component.html',
styleUrl: './cbl-manager.component.scss',
@@ -185,6 +189,12 @@ export class CblManagerComponent implements OnInit {
});
}
+ manualSyncReadingList(list: ReadingList) {
+ this.cblService.importFromUrl(list.downloadUrl!).subscribe((savedFile) => {
+ this.openImportModal([savedFile]);
+ });
+ }
+
getDateRangeLabel(rl: ReadingList) {
if (!rl || rl.startingYear === 0) return null;
@@ -192,9 +202,9 @@ export class CblManagerComponent implements OnInit {
const startMonth = rl.startingMonth > 0 ? rl.startingMonth - 1 : undefined;
const endMonth = rl.startingMonth > 0 ? rl.endingMonth - 1 : undefined;
- const startDate = startMonth !== undefined ? new Date(rl.startingYear, startMonth) : new Date(rl.startingYear);
+ const startDate = startMonth !== undefined ? new Date(rl.startingYear, startMonth) : new Date(rl.startingYear, 0);
const endDate = rl.endingYear <= 0 ? null :
- (endMonth !== undefined ? new Date(rl.endingYear, endMonth) : new Date(rl.endingYear));
+ (endMonth !== undefined ? new Date(rl.endingYear, endMonth) : new Date(rl.endingYear, 0));
return this.dateYearRangePipe.transform(startDate, endDate, !!endMonth);
}
diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.html b/UI/Web/src/app/volume-detail/volume-detail.component.html
index 0cfd51b0e..636d1d88c 100644
--- a/UI/Web/src/app/volume-detail/volume-detail.component.html
+++ b/UI/Web/src/app/volume-detail/volume-detail.component.html
@@ -232,6 +232,7 @@
[genres]="genres()"
[tags]="tags()"
[files]="files()"
+ [basicMetadata]="volumeBasicMetadata()"
/>
}
diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.ts b/UI/Web/src/app/volume-detail/volume-detail.component.ts
index 8d276ebcc..d4a21517f 100644
--- a/UI/Web/src/app/volume-detail/volume-detail.component.ts
+++ b/UI/Web/src/app/volume-detail/volume-detail.component.ts
@@ -45,7 +45,7 @@ import {AgeRating} from '../_models/metadata/age-rating';
import {Volume} from "../_models/volume";
import {VolumeService} from "../_services/volume.service";
import {LoadingComponent} from "../shared/loading/loading.component";
-import {DetailsTabComponent} from "../_single-module/details-tab/details-tab.component";
+import {BasicMetadataInfo, DetailsTabComponent} from "../_single-module/details-tab/details-tab.component";
import {ReadMoreComponent} from "../shared/read-more/read-more.component";
import {Person} from "../_models/metadata/person";
import {IHasCast} from "../_models/common/i-has-cast";
@@ -289,6 +289,18 @@ export class VolumeDetailComponent implements OnInit {
return translate(chapterLocaleKey, {num: currentlyReadingChapter.minNumber});
})
+ volumeBasicMetadata = computed
(() => {
+ const v = this.volume();
+ return {
+ readingTime: v,
+ pages: v.pages,
+ words: v.wordCount,
+ addedAt: v.createdUtc,
+ updatedAt: v.lastModifiedUtc,
+ kavitaId: v.id,
+ };
+ });
+
volumeCast = computed(() => {
const chapters = this.volume()?.chapters || [];
return {
diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json
index b9a7cbfb7..1c8b1f4bf 100644
--- a/UI/Web/src/assets/langs/en.json
+++ b/UI/Web/src/assets/langs/en.json
@@ -1560,9 +1560,27 @@
"format-title": "{{metadata-filter.format-label}}",
"length-title": "{{edit-chapter-modal.words-label}}",
"age-rating-title": "{{metadata-fields.age-rating-title}}",
- "folder-path-title": "Folder path",
+ "folder-path-title": "{{edit-series-modal.folder-path-title}}",
"file-path-title": "Files",
- "external-metadata-title": "External Metadata"
+ "external-metadata-title": "External Metadata",
+ "basic-metadata-title": "Basic Metadata",
+ "read-time-label": "Read time",
+ "pages-label": "{{edit-chapter-modal.pages-label}}",
+ "words-label": "{{edit-chapter-modal.words-label}}",
+ "added-label": "Added",
+ "updated-label": "Updated",
+ "kavita-id-label": "Kavita ID",
+ "sort-order-label": "{{edit-chapter-modal.sort-order-label}}",
+ "language-label": "Language",
+ "is-special-label": "Special",
+ "pub-status-label": "{{metadata-filter.publication-status-label}}",
+ "yes-label": "Yes",
+ "no-label": "No",
+ "file-info-title": "File info",
+ "not-set-label": "Not set",
+ "pages-count": "{{series-detail.pages-count}}",
+ "words-count": "{{series-detail.words-count}}",
+ "bytes-count": "{{num}} bytes"
},
"related-tab": {
@@ -2124,6 +2142,7 @@
"no-results": "No matching reading lists",
"add": "{{common.add}}",
"sync": "Sync",
+ "sync-manual": "Sync (Manual)",
"delete": "{{common.delete}}",
"view-list": "View Reading List",
"last-synced": "Last Synced: {{date}}",
@@ -2173,7 +2192,9 @@
"volume-num": "{{common.volume-num-shorthand}}",
"issue-num": "{{common.issue-num-shorthand}}",
"refresh": "Refresh",
- "wiki": "Wiki"
+ "wiki": "Wiki",
+ "promote": "{{actionable.promote}}",
+ "promote-tooltip": "{{actionable.promote-tooltip}}"
},
"manage-remap-rules-modal": {
@@ -3545,7 +3566,9 @@
"readinglist-item-count": "{{sort-field-pipe.readinglist-item-count}}",
"readinglist-tags": "{{metadata-fields.tags-title}}",
"readinglist-writer": "{{person-role-pipe.writer}}",
- "readinglist-artist": "{{person-role-pipe.artist}}"
+ "readinglist-artist": "{{person-role-pipe.artist}}",
+ "readinglist-provider": "Provider",
+ "readinglist-missing-item-count": "Missing Item Count"
},
@@ -3801,7 +3824,9 @@
"export-v1": "CBL v1",
"export-v1-tooltip": "Use CBL v1 (XML)",
"export-v2": "CBL v2",
- "export-v2-tooltip": "Use CBL v2 (JSON)"
+ "export-v2-tooltip": "Use CBL v2 (JSON)",
+ "cbl-manager": "CBL Manager",
+ "cbl-manager-tooltip": "Manage/Sync CBLs"
},
"preferences": {
@@ -3944,7 +3969,9 @@
"mangaBakaId-label": "MangaBaka Id",
"hardcoverId-label": "Hardcover Id",
"comicVineId-label": "Comic Vine Id",
- "metronId-label": "Metron Id"
+ "metronId-label": "Metron Id",
+ "not-set-label": "Not set",
+ "isbn-label": "{{edit-chapter-modal.isbn-label}}"
},
diff --git a/UI/Web/src/styles.scss b/UI/Web/src/styles.scss
index ebca2c1ee..e4b942381 100644
--- a/UI/Web/src/styles.scss
+++ b/UI/Web/src/styles.scss
@@ -52,6 +52,7 @@
@use './theme/components/typeahead';
@use './theme/components/tooltip';
@use './theme/components/stat-card';
+@use './theme/components/headers';
@use './theme/utilities/headings';
@use './theme/utilities/utilities';
diff --git a/UI/Web/src/theme/components/_headers.scss b/UI/Web/src/theme/components/_headers.scss
new file mode 100644
index 000000000..9562789ae
--- /dev/null
+++ b/UI/Web/src/theme/components/_headers.scss
@@ -0,0 +1,10 @@
+/** Needed for Details page to scope into carousel */
+.kv-section-header {
+ font-size: 0.68rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-muted-color);
+ margin-bottom: 0.625rem;
+ margin-top: 0.625rem;
+}
diff --git a/UI/Web/src/theme/themes/dark.scss b/UI/Web/src/theme/themes/dark.scss
index b7b44e3cb..a742df7ef 100644
--- a/UI/Web/src/theme/themes/dark.scss
+++ b/UI/Web/src/theme/themes/dark.scss
@@ -236,6 +236,18 @@
--tagbadge-filled-border-color: rgba(239, 239, 239, 0.125);
--tagbadge-filled-text-color: var(--body-text-color);
--tagbadge-filled-bg-color: var(--primary-color);
+ --tagbadge-pill-hover-bg-color: rgba(74, 198, 148, 0.15);
+
+ /* Label Card */
+ --label-card-bg: rgba(255, 255, 255, 0.04);
+ --label-card-border: rgba(239, 239, 239, 0.07);
+ --label-card-icon-color: var(--primary-color);
+ --label-card-label-color: rgba(255, 255, 255, 0.4);
+ --label-card-value-color: #efefef;
+ --label-card-value-muted-color: rgba(255, 255, 255, 0.25);
+
+ /* File path */
+ --file-path-color: rgba(255, 255, 255, 0.55);
/* Side Nav */
--side-nav-width: 14.375rem;
@@ -490,6 +502,7 @@
/** Misc **/
--offwhite-text-color: #8b95a5;
+ --white-text-color: white;
/** Activity Card **/
--activity-card-client-platform-badge-bg-color: #8b5cf6;