diff --git a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.html b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.html index 9b945de7d..42a89447f 100644 --- a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.html +++ b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.html @@ -1,196 +1,241 @@ -
- -
-

Internal Authentication:

-
- - - -
- - - Internal authentication is always enabled and cannot be disabled. However, OIDC can be used alongside internal authentication if OIDC is enabled. - -
-
+
+
+

+ + Authentication Settings +

+

+ Configure authentication methods for your Booklore instance. Internal authentication is always enabled, + and you can optionally enable OIDC for external authentication providers. +

- +
+
+
+

+ + Internal Authentication +

+

+ Internal authentication is always enabled and cannot be disabled. However, OIDC can be used alongside internal authentication if OIDC is enabled. +

+
-
-

OIDC Authentication (Experimental):

-
- - - - @if (!isOidcFormComplete()) { -
- (Fill all required fields to enable) -
- } -
- -

OIDC Provider Settings:

-
-
-
- - -
-
- - -
-
- - -
- - - Required scopes for OIDC login and token exchange. Must be supported and advertised in the provider’s discovery metadata. - +
+
+
+ + +
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
- - - These claims are used by Booklore to provision new OIDC users with their name, username, and email. Ensure they match the claims provided by your OIDC provider. - -
-
- -
- @if (oidcEnabled) { -
-

OIDC User Provisioning:

-
-
-

Automatic user provisioning:

-
- - - -
- - - Enabling auto-provisioning ensures that new users are automatically created with the selected default permissions. If turned off, users must be manually created in Booklore before they can log in. - -
-
- @if (autoUserProvisioningEnabled) { -
-

Default permissions for auto-provisioned users:

-
- - - -
- @for (perm of availablePermissions; track perm) { -
- - - -
- } -

Default libraries for auto-provisioned users:

-
- - -
-
- -
+
+
+

+ + OIDC Authentication (Experimental) +

+

+ Configure external authentication providers using OpenID Connect (OIDC) protocol for seamless single sign-on integration. +

+
+ +
+
+
+ + + + @if (!isOidcFormComplete()) { +
+ (Fill all required fields to enable)
}
+ +
+

OIDC Provider Settings

+
+
+
+ + +
+
+ + +
+
+ + +
+ + + Required scopes for OIDC login and token exchange. Must be supported and advertised in the provider's discovery metadata. + +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + These claims are used by Booklore to provision new OIDC users with their name, username, and email. Ensure they match the claims provided by your OIDC provider. + +
+
+
+ + +
+
+
+
+ + @if (oidcEnabled) { +
+

OIDC User Provisioning

+
+
+
+ + + +
+
+ + + Enabling auto-provisioning ensures that new users are automatically created with the selected default permissions. If turned off, users must be manually created in Booklore before they can log in. + +
+
+ + @if (autoUserProvisioningEnabled) { +
+
Default permissions for auto-provisioned users
+
+
+ + + +
+ @for (perm of availablePermissions; track perm) { +
+ + + +
+ } +
+ +
Default libraries for auto-provisioned users
+
+ + +
+ +
+ + +
+
+ } +
+
+ }
- } +
diff --git a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.scss b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.scss index 98ea08bb0..be010d5e1 100644 --- a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.scss +++ b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.scss @@ -1,8 +1,252 @@ -.enclosing-container { - border-color: var(--p-content-border-color); +.main-container { + width: 100%; + padding: 1rem; + height: calc(100dvh - 10.5rem); + overflow-y: auto; + border-width: 1px; + border-radius: 0.5rem; + + @media (min-width: 768px) { + height: calc(100dvh - 11.65rem); + } } -.custom-border { - border: 1px solid var(--border-color); - border-radius: var(--card-border); +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; + margin-bottom: 2rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 3rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + margin: 0 0 0.5rem 0; + + .pi { + color: var(--p-primary-color); + } +} + +.section-description { + display: flex; + align-items: flex-start; + gap: 0.5rem; + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; + + .pi { + color: var(--p-primary-color); + margin-top: 0.125rem; + flex-shrink: 0; + } +} + +.settings-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + background: var(--p-content-background); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.auth-option { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.auth-control { + display: flex; + align-items: center; + gap: 1rem; +} + +.auth-label { + font-weight: 500; + color: var(--p-text-color); + min-width: 6rem; +} + +.auth-info { + display: flex; + align-items: flex-start; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--p-text-muted-color); + padding-left: 1rem; + + .pi { + margin-top: 0.125rem; + flex-shrink: 0; + } +} + +.auth-status { + font-size: 0.875rem; + color: var(--p-text-muted-color); +} + +.provider-section, +.provisioning-section { + margin-top: 1rem; +} + +.subsection-title { + font-size: 1rem; + font-weight: 600; + color: var(--p-text-color); + margin-bottom: 1rem; +} + +.provider-form, +.provisioning-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + + @media (min-width: 768px) { + grid-template-columns: repeat(2, 1fr); + } +} + +.form-field { + display: flex; + flex-direction: column; + gap: 0.5rem; + + &.form-field-full { + @media (min-width: 768px) { + grid-column: 1 / -1; + } + } + + label { + font-weight: 600; + color: var(--p-text-color); + font-size: 0.875rem; + } +} + +.claims-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + + @media (min-width: 768px) { + grid-template-columns: repeat(3, 1fr); + } +} + +.field-info { + display: flex; + align-items: flex-start; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--p-text-muted-color); + margin-top: 0.5rem; + + .pi { + margin-top: 0.125rem; + flex-shrink: 0; + } +} + +.form-actions { + display: flex; + justify-content: flex-start; + margin-top: 1rem; +} + +.permissions-section { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 1rem; +} + +.permissions-title { + font-size: 0.875rem; + font-weight: 600; + color: var(--p-text-color); + margin-bottom: 0.5rem; +} + +.permissions-list { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding-left: 1rem; +} + +.permission-item { + display: flex; + align-items: center; + gap: 0.5rem; + + label { + font-size: 0.875rem; + color: var(--p-text-color); + cursor: pointer; + } +} + +.libraries-section { + padding-left: 1rem; + margin-bottom: 1rem; } diff --git a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.ts b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.ts index d95f664d7..195ae5091 100644 --- a/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.ts +++ b/booklore-ui/src/app/core/security/oauth2-management/authentication-settings.component.ts @@ -10,7 +10,6 @@ import {AppSettingsService} from '../../service/app-settings.service'; import {Observable} from 'rxjs'; import {AppSettingKey, AppSettings, OidcProviderDetails} from '../../model/app-settings.model'; import {filter, take} from 'rxjs/operators'; -import {Divider} from 'primeng/divider'; import {MultiSelect} from 'primeng/multiselect'; import {Library} from '../../../book/model/library.model'; import {LibraryService} from '../../../book/service/library.service'; @@ -24,7 +23,6 @@ import {LibraryService} from '../../../book/service/library.service'; InputText, Checkbox, ToggleSwitch, - Divider, Button, MultiSelect, ReactiveFormsModule diff --git a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html index 2d1461507..42ea2d21c 100644 --- a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html +++ b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html @@ -95,7 +95,7 @@
- +
diff --git a/booklore-ui/src/app/settings/device-settings-component/device-settings-component.html b/booklore-ui/src/app/settings/device-settings-component/device-settings-component.html index 6e1b020f4..f1dd2b616 100644 --- a/booklore-ui/src/app/settings/device-settings-component/device-settings-component.html +++ b/booklore-ui/src/app/settings/device-settings-component/device-settings-component.html @@ -1,10 +1,9 @@
-
+
-
+
-
diff --git a/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.html b/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.html index 3d813e46a..f4d844c14 100644 --- a/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.html +++ b/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.html @@ -1,117 +1,186 @@ -

- Kobo Integration Settings: - - -

+
+
+

+ + Kobo Integration Settings + + +

+

+ Configure Kobo device integration to sync your library books and reading progress with your Kobo e-reader. +

+
-@if (hasKoboTokenPermission) { -
-
-
- - - -
- - @if (koboSyncSettings.syncEnabled) { -
- -
- - - - - -
-
- - - } -
-
- - @if (isAdmin) { -
-

Kobo Settings (Admin Only):

-
-
-
- - - -
-

- Pros: KEPUB format enables enhanced Kobo features like better typography, reading stats, and faster page turns. -
- Cons: Conversion takes extra processing time and may occasionally fail for complex layouts. - Books over the size limit below will skip conversion automatically. + @if (hasKoboTokenPermission) { +

+
+
+

+ + Sync Configuration +

+

+ Enable and configure synchronization between your Booklore library and Kobo device.

-
-
- +
+
+
+
+
+ + + +
+

+ Turn on synchronization to automatically sync your library books to your Kobo device. +

+
+
+ + @if (koboSyncSettings.syncEnabled) { +
+
+ +

+ Your unique sync token for authenticating with your Kobo device. Keep this secure. +

+
+
+
+ + + + + +
+
+
+ +
+
+ +

+ Regenerate your sync token if needed. This will invalidate the current token. +

+
+
+ + +
+
+ } +
+
+
+ + @if (isAdmin) { +
+
+

+ + Administrator Settings +

+

+ Configure advanced Kobo sync settings that affect all users on this server. +

- - -

- Large files (100MB+) can cause conversion timeouts and server strain. Lower limits ensure faster syncing but may skip conversion of larger books. + +

+
+
+
+ + + +
+

+ Pros: KEPUB format enables enhanced Kobo features like better typography, reading stats, and faster page turns. +
+ Cons: Conversion takes extra processing time and may occasionally fail for complex layouts. + Books over the size limit below will skip conversion automatically. +

+
+
+ +
+
+
+ +
+ + +
+
+

+ Large files (100MB+) can cause conversion timeouts and server strain. Lower limits ensure faster syncing but may skip conversion of larger books. +
+ Recommended: 50-100MB for most libraries. Very large books will sync as regular EPUB regardless of this setting. +

+
+
+
+
+ } +
+ } @else { +
+
+ +
+

Access Restricted

+

+ Access to Kobo sync is restricted.
- Recommended: 50-100MB for most libraries. Very large books will sync as regular EPUB regardless of this setting. + Please contact your administrator to request permission.

} -} @else { -
- - - Access to Kobo sync is restricted. -
- Please contact your administrator to request permission. -
-
-} +
diff --git a/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.scss b/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.scss index e69de29bb..003839c79 100644 --- a/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.scss +++ b/booklore-ui/src/app/settings/device-settings-component/kobo-sync-settings-component/kobo-sync-settings-component.scss @@ -0,0 +1,221 @@ +.main-container { + width: 100%; + padding: 1rem; + overflow-y: auto; +} + +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; + margin-bottom: 2rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } + + .external-link-icon { + color: #0ea5e9 !important; // Sky-600 equivalent + font-size: 1rem !important; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + margin: 0 0 0.5rem 0; + + .pi { + color: var(--p-primary-color); + } +} + +.section-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; +} + +.settings-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + background: var(--p-content-background); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.setting-item { + display: flex; + align-items: flex-start; + gap: 2rem; + padding: 0.25rem 0; + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + @media (max-width: 768px) { + flex-direction: column; + gap: 1rem; + } +} + +.setting-info { + flex: 1; + min-width: 0; + + .setting-label { + display: block; + font-weight: 600; + color: var(--p-text-color); + font-size: 1rem; + margin-bottom: 0.5rem; + } + + .setting-label-row { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 0.5rem; + flex-wrap: wrap; + + .setting-label { + margin-bottom: 0; + flex-shrink: 0; + } + + .slider-container { + flex: 1; + min-width: 200px; + max-width: 300px; + + @media (max-width: 768px) { + min-width: 180px; + max-width: 250px; + } + } + } + + .setting-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; + } +} + +.setting-control { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: flex-end; + + @media (max-width: 768px) { + align-items: flex-start; + width: 100%; + } +} + +.token-input-group { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + max-width: 500px; + + @media (max-width: 768px) { + max-width: 100%; + } + + .token-input { + flex: 1; + min-width: 200px; + } +} + +.slider-container { + width: 250px; + + @media (max-width: 768px) { + width: 200px; + } +} + +.access-denied-card { + display: flex; + align-items: center; + gap: 1rem; + padding: 1.5rem; + margin: 1rem 0; + border-radius: 8px; + background: rgba(220, 38, 38, 0.1); + border: 1px solid rgba(220, 38, 38, 0.3); + color: var(--p-red-400); + max-width: 500px; + + .pi { + font-size: 1.25rem; + flex-shrink: 0; + } + + .access-denied-content { + h3 { + margin: 0 0 0.5rem 0; + font-size: 1rem; + font-weight: 600; + color: var(--p-red-300); + } + + p { + margin: 0; + font-size: 0.875rem; + line-height: 1.5; + } + } +} diff --git a/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.html b/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.html index 26b68a873..51b6dedfd 100644 --- a/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.html +++ b/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.html @@ -1,128 +1,192 @@ -

- KOReader Sync Settings: - - -

- -@if (hasPermission) { -
- -
- - - -
- -
- -
- -
- - - -
-
- - -
- -
- - - -
- - Username is required. - -
- - -
- -
- - - - - -
- - Password must be at least 6 characters. - -
- - - -
-
-} @else { -
- - - Access to KOReader sync is restricted. -
- Please contact your administrator to request permission. -
+
+
+

+ + KOReader Sync Settings + + +

+

+ Configure KOReader integration to sync your reading progress and annotations between your device and Booklore library. +

-} + + @if (hasPermission) { +
+
+
+

+ + Sync Configuration +

+

+ Enable and configure synchronization between your Booklore library and KOReader device. +

+
+ +
+
+
+
+
+ + + +
+

+ Turn on synchronization to automatically sync your reading progress and annotations. +

+
+
+ +
+
+
+ +
+ + + +
+
+

+ The API endpoint path for your KOReader sync service. +

+
+
+ +
+
+
+ +
+ + + +
+
+

+ Your username for authenticating with the KOReader sync service. +

+ + Username is required. + +
+
+ +
+
+
+ +
+ + + + + +
+
+

+ Your password for authenticating with the KOReader sync service. +

+ + Password must be at least 6 characters. + +
+
+ +
+
+
+ + + +
+

+ Save your credentials or edit existing settings. +

+
+
+
+
+
+
+ } @else { +
+
+ +
+

Access Restricted

+

+ Access to KOReader sync is restricted. +
+ Please contact your administrator to request permission. +

+
+
+
+ } +
diff --git a/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.scss b/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.scss index e69de29bb..cca5c4891 100644 --- a/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.scss +++ b/booklore-ui/src/app/settings/device-settings-component/koreader-settings-component/koreader-settings-component.scss @@ -0,0 +1,247 @@ +.main-container { + width: 100%; + padding: 1rem; + overflow-y: auto; +} + +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; + margin-bottom: 2rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } + + .external-link-icon { + color: #0ea5e9 !important; + font-size: 1rem !important; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 2rem; + + @media (max-width: 768px) { + padding-left: 0; + } +} + +.preferences-section { + + @media (max-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; + margin-left: 1rem; + margin-right: 1rem; +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + margin: 0 0 0.5rem 0; + + .pi { + color: var(--p-primary-color); + } +} + +.section-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; +} + +.settings-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + background: var(--p-content-background); + padding: 1rem 2rem 2rem 2rem; + margin-left: 1rem; + margin-right: 1rem; + display: flex; + flex-direction: column; + gap: 2rem; + + @media (max-width: 768px) { + padding: 1rem; + margin-left: 0; + margin-right: 0; + } +} + +.setting-item { + display: flex; + align-items: flex-start; + gap: 2rem; + padding: 0.75rem 0; + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + @media (max-width: 768px) { + flex-direction: column; + gap: 1rem; + padding: 0.75rem 0; + } +} + +.setting-info { + flex: 1; + min-width: 0; + + .setting-label { + display: block; + font-weight: 600; + color: var(--p-text-color); + font-size: 1rem; + margin-bottom: 0.5rem; + } + + .setting-label-row { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 0.5rem; + flex-wrap: wrap; + + .setting-label { + margin-bottom: 0; + flex-shrink: 0; + min-width: 150px; + } + + .token-input-group { + flex: 1; + min-width: 300px; + max-width: 500px; + + @media (max-width: 768px) { + min-width: 250px; + max-width: 100%; + } + } + } + + .setting-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; + } +} + +.setting-control { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: flex-end; + min-width: 350px; + + @media (max-width: 768px) { + align-items: flex-start; + width: 100%; + min-width: unset; + } +} + +.token-input-group { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + + .token-input { + flex: 1; + min-width: 200px; + + @media (max-width: 768px) { + min-width: 150px; + } + } + + .token-input-password { + flex: 1; + min-width: 200px; + + @media (max-width: 768px) { + min-width: 150px; + } + } +} + +.error-message { + color: var(--p-red-500); + font-size: 0.75rem; + margin-top: 0.25rem; + display: block; +} + +.access-denied-card { + display: flex; + align-items: center; + gap: 1rem; + padding: 1.5rem; + margin: 1rem 0; + border-radius: 8px; + background: rgba(220, 38, 38, 0.1); + border: 1px solid rgba(220, 38, 38, 0.3); + color: var(--p-red-400); + max-width: 500px; + + .pi { + font-size: 1.25rem; + flex-shrink: 0; + } + + .access-denied-content { + h3 { + margin: 0 0 0.5rem 0; + font-size: 1rem; + font-weight: 600; + color: var(--p-red-300); + } + + p { + margin: 0; + font-size: 0.875rem; + line-height: 1.5; + } + } +} diff --git a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.html b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.html index 99394b5c8..195a0b56a 100644 --- a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.html +++ b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.html @@ -1,127 +1,240 @@ -
-

- Email Providers - -

- +
+
+

+ + Email Providers +

+

+ Configure email-sending services like Gmail, Outlook, or custom SMTP servers for sending books via email. The default email provider will be used for 'Quick Book Send' located in the Book Card menu. +

+
+ +
+
+
+
+

+ + Current Providers +

+ + +
+
+ +
+ + + + +
+ + Default +
+ + +
+ + Name +
+ + +
+ + Host +
+ + +
+ + Port +
+ + +
+ + Username +
+ + +
+ + Password +
+ + +
+ + From Address +
+ + Auth + StartTLS + +
+ + Edit +
+ + +
+ + Delete +
+ + +
+ + + + + + + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + {{ provider.name }} + } + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + {{ provider.host }} + } + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + {{ provider.port }} + } + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + {{ provider.username }} + } + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + Hidden + } + + + + @if (provider.isEditing) { + + } + @if (!provider.isEditing) { + {{ provider.fromAddress }} + } + + + + + + + + + + + + + + @if (!provider.isEditing) { + + + } + @if (provider.isEditing) { +
+ + + + +
+ } + + + + + + + +
+ + + + +
+ +

No email providers found

+

Create your first email provider to start sending books

+
+ + +
+
+
+
+
- - - - - Default - Name - Host - Port - Username - Password - From Address - Auth - StartTLS - Edit - Delete - - - - - - - - - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - {{ provider.name }} - } - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - {{ provider.host }} - } - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - {{ provider.port }} - } - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - {{ provider.username }} - } - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - Hidden - } - - - - @if (provider.isEditing) { - - } - @if (!provider.isEditing) { - {{ provider.fromAddress }} - } - - - - - - - - - - - - - - @if (!provider.isEditing) { - - } - @if (provider.isEditing) { - - } - @if (provider.isEditing) { - - } - - - - - - - - diff --git a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.scss b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.scss index e69de29bb..98306e3d1 100644 --- a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.scss +++ b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.scss @@ -0,0 +1,167 @@ +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0.5rem 1rem; + } +} + +.section-header { + margin-bottom: 1rem; + + .section-title-group { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + + @media (max-width: 767px) { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + + p-button { + align-self: flex-end; + } + } + } +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + + .pi { + color: var(--p-primary-color); + } +} + +.table-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + overflow: hidden; + background: var(--p-content-background); +} + +.p-datatable { + .p-datatable-table { + border-collapse: separate; + border-spacing: 0; + } + + .p-datatable-thead > tr > th { + background: var(--p-surface-100); + border-bottom: 2px solid var(--p-content-border-color); + padding: 1rem; + font-weight: 600; + color: var(--p-text-color); + white-space: nowrap; + } + + .p-datatable-tbody > tr { + transition: background-color 0.2s; + + &:hover { + background: var(--p-surface-50); + } + + &:last-child { + border-bottom: none; + } + } + + .p-datatable-tbody > tr > td { + padding: 1rem; + border: none; + } +} + +.p-datatable th .header-content { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.5rem; +} + +.permission-header { + text-align: center; + font-size: 0.875rem; + padding: 0.75rem 0.5rem !important; + min-width: 80px; + width: 80px; +} + +.actions-header { + text-align: center; + min-width: 80px; +} + +.actions-cell { + text-align: center; +} + +.password-hidden { + color: var(--p-text-muted-color); + font-style: italic; +} + +.empty-message { + text-align: center; + padding: 2rem 1rem; + color: var(--p-text-muted-color); + + .pi { + font-size: 2rem; + margin-bottom: 1rem; + color: var(--p-surface-400); + } + + .empty-title { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 0.5rem; + } + + .empty-subtitle { + font-size: 0.875rem; + } +} diff --git a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.ts b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.ts index d39bd16ab..8dd96a671 100644 --- a/booklore-ui/src/app/settings/email/email-provider/email-provider.component.ts +++ b/booklore-ui/src/app/settings/email/email-provider/email-provider.component.ts @@ -114,7 +114,7 @@ export class EmailProviderComponent implements OnInit { openCreateProviderDialog() { this.ref = this.dialogService.open(CreateEmailProviderDialogComponent, { - header: 'Create New Email Provider', + header: 'Create Email Provider', modal: true, closable: true, style: { position: 'absolute', top: '15%' }, diff --git a/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.html b/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.html index 37723cb9d..d8a145ec0 100644 --- a/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.html +++ b/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.html @@ -1,69 +1,162 @@ -
-

- Recipient Emails - -

- +
+
+

+ + Recipient Emails +

+

+ Manage the list of recipients who will receive books via email. The 'Default' recipient will be used for 'Quick Book Send,' located in the Book Card menu. +

+
+ +
+
+
+
+

+ + Current Recipients +

+ + +
+
+ +
+ + + + +
+ + Default +
+ + +
+ + Email Address +
+ + +
+ + Name +
+ + +
+ + Edit +
+ + +
+ + Delete +
+ + +
+ + + + + + + + + + @if (recipient.isEditing) { + + } + @if (!recipient.isEditing) { + {{ recipient.email }} + } + + + + @if (recipient.isEditing) { + + } + @if (!recipient.isEditing) { + {{ recipient.name }} + } + + + + @if (!recipient.isEditing) { + + + } + @if (recipient.isEditing) { +
+ + + + +
+ } + + + + + + + +
+ + + + +
+ +

No recipients found

+

Add your first email recipient to start sending books

+
+ + +
+
+
+
+
- - - - - Default - Email Address - Name - Edit - Delete - - - - - - - - - - - - @if (recipient.isEditing) { - - } - @if (!recipient.isEditing) { - {{ recipient.email }} - } - - - - @if (recipient.isEditing) { - - } - @if (!recipient.isEditing) { - {{ recipient.name }} - } - - - - @if (!recipient.isEditing) { - - } - @if (recipient.isEditing) { - - } - @if (recipient.isEditing) { - - } - - - - - - - - diff --git a/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.scss b/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.scss index e69de29bb..b07197096 100644 --- a/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.scss +++ b/booklore-ui/src/app/settings/email/email-recipient/email-recipient.component.scss @@ -0,0 +1,144 @@ +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; + + .section-title-group { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; + } +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + + .pi { + color: var(--p-primary-color); + } +} + +.table-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + overflow: hidden; + background: var(--p-content-background); +} + +.p-datatable { + .p-datatable-table { + border-collapse: separate; + border-spacing: 0; + } + + .p-datatable-thead > tr > th { + background: var(--p-surface-100); + border-bottom: 2px solid var(--p-content-border-color); + padding: 1rem; + font-weight: 600; + color: var(--p-text-color); + white-space: nowrap; + } + + .p-datatable-tbody > tr { + transition: background-color 0.2s; + + &:hover { + background: var(--p-surface-50); + } + + &:last-child { + border-bottom: none; + } + } + + .p-datatable-tbody > tr > td { + padding: 1rem; + border: none; + } +} + +.p-datatable th .header-content { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.5rem; +} + +.actions-header { + text-align: center; + min-width: 80px; +} + +.actions-cell { + text-align: center; +} + +.empty-message { + text-align: center; + padding: 2rem 1rem; + color: var(--p-text-muted-color); + + .pi { + font-size: 2rem; + margin-bottom: 1rem; + color: var(--p-surface-400); + } + + .empty-title { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 0.5rem; + } + + .empty-subtitle { + font-size: 0.875rem; + } +} diff --git a/booklore-ui/src/app/settings/email/email.component.html b/booklore-ui/src/app/settings/email/email.component.html index f09d20202..be17bb619 100644 --- a/booklore-ui/src/app/settings/email/email.component.html +++ b/booklore-ui/src/app/settings/email/email.component.html @@ -1,8 +1,9 @@
-
+
-
+ +
diff --git a/booklore-ui/src/app/settings/email/email.component.ts b/booklore-ui/src/app/settings/email/email.component.ts index 9b3516aab..bdac89437 100644 --- a/booklore-ui/src/app/settings/email/email.component.ts +++ b/booklore-ui/src/app/settings/email/email.component.ts @@ -3,6 +3,7 @@ import {FormsModule} from '@angular/forms'; import {TableModule} from 'primeng/table'; import {EmailProviderComponent} from './email-provider/email-provider.component'; import {EmailRecipientComponent} from './email-recipient/email-recipient.component'; +import {Divider} from 'primeng/divider'; @Component({ selector: 'app-email', @@ -10,7 +11,8 @@ import {EmailRecipientComponent} from './email-recipient/email-recipient.compone FormsModule, TableModule, EmailProviderComponent, - EmailRecipientComponent + EmailRecipientComponent, + Divider ], templateUrl: './email.component.html', styleUrls: ['./email.component.scss'], diff --git a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.html b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.html index 2c9836b58..56eecf671 100644 --- a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.html +++ b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.html @@ -1,258 +1,263 @@ -
-
-

+

+
+

+ File Naming Patterns - - +

+

+ Define custom naming patterns for uploaded files and for moving files within your library. Use metadata placeholders to automate organization.

-
+
-
-

Default File Naming Pattern:

-

+

+
+
+

+ + Default File Naming Pattern +

+

Define the default naming pattern for files. This pattern applies to all libraries unless overridden.

-
- - -
- @if (defaultErrorMessage) { -
{{ defaultErrorMessage }}
- } - -
-

Preview:

-

{{ generateDefaultPreview() }}

-
-
-

Overrides for Libraries:

-

+

+
+
+ + Default Pattern +
+
+ + + +
+ @if (defaultErrorMessage) { +
{{ defaultErrorMessage }}
+ } +
+ +
+
+ Preview: + {{ generateDefaultPreview() }} +
+
+
+
+ +
+
+

+ + Library-Specific Overrides +

+

Define custom naming patterns for specific libraries. Leave empty to use the default pattern.

- - @for (library of libraries; track library.id) { -
-
- - {{ library.name }} - - - -
-
-

Preview:

-

{{ generateLibraryPreview(library) }}

-
-
- } - -
- -
-
- +
+
+ @for (library of libraries; track library.id) { +
+
+ + {{ library.name }} +
+
+ + + +
+
+ Preview: + {{ generateLibraryPreview(library) }} +
+
+ } +
-

Available Placeholders:

+
+ + +
+
+
-

+

+
+

+ + Available Placeholders +

+

Use placeholders to dynamically insert book metadata into file names and folder paths. These will be replaced with actual values when uploading or moving files. - You can also wrap parts of the pattern in {{ '{{<...>}' }} to make them optional, if any placeholder inside is missing, that section will be omitted entirely.

+
-
-
-
    -
  • {{ '{title}' }} – Book title
  • -
  • {{ '{authors}' }} – Author(s)
  • -
  • {{ '{year}' }} – Full year (e.g. 2025)
  • -
  • {{ '{series}' }} – Series name
  • -
  • {{ '{seriesIndex}' }} – Series index (e.g. 01)
  • -
  • {{ '{language}' }} – Language code (e.g. en)
  • -
  • {{ '{publisher}' }} – Publisher name
  • -
  • {{ '{isbn}' }} – ISBN number
  • -
  • {{ '{currentFilename}' }} Original file name (with extension)
  • -
+
+
+
+
+

Available Placeholders

+
    +
  • {{ '{title}' }} – Book title
  • +
  • {{ '{authors}' }} – Author(s)
  • +
  • {{ '{year}' }} – Full year (e.g. 2025)
  • +
  • {{ '{series}' }} – Series name
  • +
  • {{ '{seriesIndex}' }} – Series index (e.g. 01)
  • +
  • {{ '{language}' }} – Language code (e.g. en)
  • +
  • {{ '{publisher}' }} – Publisher name
  • +
  • {{ '{isbn}' }} – ISBN number
  • +
  • {{ '{currentFilename}' }} Original file name (with extension)
  • +
+
- - -
-

Optional blocks

-

+

+

Optional blocks

+

Surround parts of your pattern with angle brackets - {{ '{<...>}' }} - to make them optional.
If any placeholder inside the block has no value, the whole block is excluded. + {{ '{<...>}' }} +
+ to make them optional. If any placeholder inside the block has no value, the whole block is excluded.

-

Example:

-

{{ '{<{seriesIndex} - >{title}' }}

-

01 - Dune (if {{ '{seriesIndex}' }} exists)

-

Dune (if {{ '{seriesIndex}' }} is missing)

+
+

Example:

+

{{ '{<{seriesIndex} - >{title}' }}

+
    +
  • 01 - Dune (if {{ '{seriesIndex}' }} exists)
  • +
  • Dune (if {{ '{seriesIndex}' }} is missing)
  • +
+
+
+
- +
+
+

+ + Example Patterns & Output +

+

+ See how different patterns work with sample book metadata and learn best practices for organizing your library. +

+
-
-

Example Patterns & Output:

- -
-

Examples with Full Metadata

-

- title: Harry Potter and the Sorcerer's Stone - authors: J.K. Rowling - series: Harry Potter - seriesIndex: 01 - year: 1997 - currentFilename: harry1_original.epub -

- -
-

Basic pattern: {{ '{authors} - {title}' }}

-

Output: J.K. Rowling - Harry Potter and the Sorcerer's Stone.epub

+
+
+
+

Examples with Full Metadata

+ -
-

Pattern with punctuation: {{ '{title}: {series}' }}

-

Output: Harry Potter and the Sorcerer's Stone: Harry Potter.epub

-
+
+
+

Basic pattern: {{ '{authors} - {title}' }}

+

Output: J.K. Rowling - Harry Potter and the Sorcerer's Stone.epub

+
-
-

Series in folder path: {{ '{authors}/{series}/{seriesIndex} - {title}' }}

-

Output: J.K. Rowling/Harry Potter/01 - Harry Potter and the Sorcerer's Stone.epub

-
+
+

Pattern with punctuation: {{ '{title}: {series}' }}

+

Output: Harry Potter and the Sorcerer's Stone: Harry Potter.epub

+
-
-

Folder only: {{ '{title}/' }}

-

Output: /Harry Potter and the Sorcerer's Stone/harry1_original.epub

-
+
+

Series in folder path: {{ '{authors}/{series}/{seriesIndex} - {title}' }}

+

Output: J.K. Rowling/Harry Potter/01 - Harry Potter and the Sorcerer's Stone.epub

+
-
-

Absolute path: {{ '/{authors}/{title}' }}

-

Output: /J.K. Rowling/Harry Potter and the Sorcerer's Stone.epub

-
+
+

Folder only: {{ '{title}/' }}

+

Output: /Harry Potter and the Sorcerer's Stone/harry1_original.epub

+
-
-

Reuse original filename in path: {{ '{authors}/{series}/{currentFilename}' }} -

-

Output: J.K. Rowling/Harry Potter/harry1_original.epub

-
+
+

Absolute path: {{ '/{authors}/{title}' }}

+

Output: /J.K. Rowling/Harry Potter and the Sorcerer's Stone.epub

+
-
-

Preserve original filename only: {{ '{currentFilename}' }}

-

Output: harry1_original.epub

-
- -
-

Empty pattern (defaults to current filename): {{ '' }}

-

Output: harry1_original.epub

+
+

Reuse original filename in path: {{ '{authors}/{series}/{currentFilename}' }}

+

Output: J.K. Rowling/Harry Potter/harry1_original.epub

+
-
-

Examples with Missing Optional Fields

-

- title: Project Hail Mary - authors: Andy Weir - year: 2021 - series: (not provided) - seriesIndex: (not provided) - currentFilename: project_hail_mary_final.epub -

+ -
-

Pattern with optional blocks: {{ '{authors}/<{series}/><{seriesIndex}. >{title}< ({year})>' }}

-

Output: Andy Weir/Project Hail Mary (2021).epub

+
+

Examples with Missing Optional Fields

+ -
-

Fallback with seriesIndex and dash: {{ '<{seriesIndex}. >{title}< - {authors}>' }}

-

Output: Project Hail Mary - Andy Weir.epub

-
+
+
+

Pattern with optional blocks: {{ '{authors}/<{series}/><{seriesIndex}. >{title}< ({year})>' }}

+

Output: Andy Weir/Project Hail Mary (2021).epub

+
-
-

Brackets & punctuation fallback: {{ '<[{series}] >{title} - {authors}' }}

-

Output: Project Hail Mary - Andy Weir.epub

-
+
+

Fallback with seriesIndex and dash: {{ '<{seriesIndex}. >{title}< - {authors}>' }}

+

Output: Project Hail Mary - Andy Weir.epub

+
-
-

Series + punctuation fallback: {{ '<{series}: >{title} by {authors}' }}

-

Output: Project Hail Mary by Andy Weir.epub

-
+
+

Brackets & punctuation fallback: {{ '<[{series}] >{title} - {authors}' }}

+

Output: Project Hail Mary - Andy Weir.epub

+
-
-

Only folders, no filename: {{ '{authors}/' }}

-

Output: Andy Weir/project_hail_mary_final.epub

-
+
+

Series + punctuation fallback: {{ '<{series}: >{title} by {authors}' }}

+

Output: Project Hail Mary by Andy Weir.epub

+
-
-

Deep nested folders: {{ '{authors}/books/<{series}/>{title}/' }}

-

Output: Andy Weir/books/Project Hail Mary/project_hail_mary_final.epub

-
- -
-

With static folder prefix: {{ 'Books/<{series}/>{authors} - {title}' }}

-

Output: Books/Andy Weir - Project Hail Mary.epub

-
- -
-

Use original filename in path: {{ '{authors}/books/<{series}/>{currentFilename}' }}

-

Output: Andy Weir/books/project_hail_mary_final.epub

-
- -
-

Prefix current filename manually: {{ '{authors}/final__{currentFilename}' }}

-

Output: Andy Weir/final__project_hail_mary_final.epub

-
- -
-

Filename preserved, renamed folder: {{ '{title}/source/{currentFilename}' }}

-

Output: Project Hail Mary/source/project_hail_mary_final.epub

-
- -
-

Use current filename with year suffix: {{ '{authors}/{year}__{currentFilename}' }}

-

Output: Andy Weir/2021__project_hail_mary_final.epub

-
- -
-

Fallback with optional + extras folder: {{ '<{series}/>{authors}/extras/{currentFilename}' }}

-

Output: Andy Weir/extras/project_hail_mary_final.epub

-
- -
-

Archive structure using original name: {{ 'archive/<{series}/>{year}/{currentFilename}' }}

-

Output: archive/2021/project_hail_mary_final.epub

-
- -
-

Structured folder + renamed file: {{ '{authors}/<{series}/>{title} ({year}) by {authors}' }}

-

Output: Andy Weir/Project Hail Mary (2021) by Andy Weir.epub

+
+

Use original filename with year suffix: {{ '{authors}/{year}__{currentFilename}' }}

+

Output: Andy Weir/2021__project_hail_mary_final.epub

+
diff --git a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.scss b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.scss index 98ea08bb0..3c11e045c 100644 --- a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.scss +++ b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.scss @@ -1,8 +1,408 @@ -.enclosing-container { - border-color: var(--p-content-border-color); +.main-container { + width: 100%; + padding: 1rem; + height: calc(100dvh - 10.5rem); + overflow-y: auto; + border-width: 1px; + border-radius: 0.5rem; + + @media (min-width: 768px) { + height: calc(100dvh - 11.65rem); + } } -.custom-border { - border: 1px solid var(--border-color); - border-radius: var(--card-border); +.enclosing-container { + border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; + margin-bottom: 2rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 3rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + margin: 0 0 0.5rem 0; + + .pi { + color: var(--p-primary-color); + } +} + +.section-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; +} + +.settings-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + background: var(--p-content-background); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; + + @media (max-width: 767px) { + padding: 1rem; + gap: 1rem; + } +} + +.form-field { + display: flex; + flex-direction: column; + gap: 0.5rem; + + label { + font-weight: 600; + color: var(--p-text-color); + font-size: 0.875rem; + } +} + +.pattern-input-group { + display: flex; + gap: 1rem; + align-items: flex-start; + + @media (max-width: 768px) { + flex-direction: column; + gap: 0.75rem; + } +} + +.error-message { + color: var(--p-red-500); + font-size: 0.875rem; + margin-top: 0.25rem; +} + +.preview-section { + display: flex; + gap: 0.5rem; + align-items: center; + + @media (max-width: 767px) { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } +} + +.preview-label { + color: var(--p-text-muted-color); + font-size: 0.875rem; +} + +.preview-value { + color: var(--p-green-500); + font-size: 0.8rem; + font-family: monospace; +} + +.library-patterns { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.library-pattern-item { + display: flex; + flex-direction: column; + gap: 0.75rem; + border: 1px solid var(--p-surface-800); + border-left: 3px solid var(--p-primary-color); + border-radius: 6px; + background: var(--p-surface-900); + padding: 1rem; + + @media (max-width: 767px) { + padding: 0.75rem; + } +} + +.library-header { + display: flex; + align-items: center; + gap: 0.5rem; + + .pi { + color: var(--p-primary-color); + } +} + +.library-name { + font-weight: 500; + color: var(--p-text-color); + min-width: 100px; +} + +.library-input-group { + display: flex; + gap: 1rem; + align-items: flex-start; + + @media (max-width: 768px) { + flex-direction: column; + gap: 0.75rem; + } +} + +.form-actions { + display: flex; + justify-content: flex-end; + + @media (max-width: 767px) { + justify-content: stretch; + + p-button { + width: 100%; + } + } +} + +.subsection-title { + font-size: 1rem; + font-weight: 600; + color: var(--p-text-color); + margin-bottom: 1rem; +} + +.placeholders-info { + display: flex; + flex-direction: column; + gap: 1.5rem; + + @media (max-width: 767px) { + gap: 1rem; + } +} + +.placeholders-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + + code { + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.75rem; + } +} + +.placeholders-grid { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + + @media (min-width: 768px) { + grid-template-columns: 1fr auto 1fr; + } + + @media (max-width: 767px) { + gap: 1.5rem; + } +} + +.placeholders-list { + ul { + list-style: disc; + padding-left: 1.5rem; + margin: 0; + + @media (max-width: 767px) { + padding-left: 1rem; + } + + li { + color: var(--p-text-color); + font-size: 0.875rem; + margin-bottom: 0.5rem; + } + } +} + +.placeholder-code { + color: var(--p-orange-400); + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.75rem; + font-family: monospace; +} + +.optional-blocks-info { + p { + color: var(--p-text-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; + } +} + +.example-section { + margin-top: 1rem; +} + +.example-label { + font-weight: 600; + color: var(--p-text-color); + margin-bottom: 0.5rem; +} + +.example-list { + list-style: disc; + padding-left: 1.5rem; + + @media (max-width: 767px) { + padding-left: 0; + list-style: none; + } + + li { + color: var(--p-text-color); + font-size: 0.875rem; + margin-bottom: 0.25rem; + } +} + +.examples-section { + display: flex; + flex-direction: column; + gap: 2rem; + + @media (max-width: 767px) { + gap: 1.5rem; + } +} + +.example-category { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.metadata-sample { + display: flex; + flex-direction: column; + gap: 0.25rem; + background: var(--p-surface-800); + padding: 1rem; + border-radius: 6px; + margin-bottom: 1rem; + + @media (max-width: 767px) { + padding: 0.75rem; + } + + span { + font-size: 0.875rem; + color: var(--p-text-muted-color); + + code { + color: var(--p-text-color); + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.75rem; + } + } +} + +.example-list { + display: flex; + flex-direction: column; + gap: 1rem; + + @media (max-width: 767px) { + gap: 0.75rem; + } +} + +.example-item { + background: var(--p-surface-800); + padding: 1rem; + border-radius: 6px; + border-left: 3px solid var(--p-primary-color); + + @media (max-width: 767px) { + padding: 0.75rem; + } +} + +.example-pattern { + font-size: 0.875rem; + margin-bottom: 0.5rem; + + strong { + color: var(--p-text-color); + } + + code { + color: var(--p-text-muted-color); + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.75rem; + } +} + +.example-output { + font-size: 0.875rem; + margin: 0; + + strong { + color: var(--p-text-color); + } + + code { + color: var(--p-text-muted-color); + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.75rem; + } } diff --git a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.ts b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.ts index 2b7121dbd..133c04096 100644 --- a/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.ts +++ b/booklore-ui/src/app/settings/file-naming-pattern/file-naming-pattern.component.ts @@ -6,16 +6,16 @@ import {AppSettingsService} from '../../core/service/app-settings.service'; import {forkJoin, Observable, of} from 'rxjs'; import {AppSettingKey, AppSettings} from '../../core/model/app-settings.model'; import {catchError, filter, take} from 'rxjs/operators'; -import {Divider} from 'primeng/divider'; -import {Tooltip} from 'primeng/tooltip'; import {Library} from '../../book/model/library.model'; import {LibraryService} from '../../book/service/library.service'; +import {InputText} from 'primeng/inputtext'; +import {Divider} from 'primeng/divider'; @Component({ selector: 'app-file-naming-pattern', templateUrl: './file-naming-pattern.component.html', standalone: true, - imports: [FormsModule, Button, Divider, Tooltip], + imports: [FormsModule, Button, InputText, Divider], styleUrls: ['./file-naming-pattern.component.scss'], }) export class FileNamingPatternComponent implements OnInit { diff --git a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.html b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.html index c6c1bf7b4..01faa5a18 100644 --- a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.html +++ b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.html @@ -1,117 +1,148 @@ -
- -
-
-

Book Cover Image

- - -
- -
-

Regenerate Covers? - - -

- -
-
- -
- -
- -
-

Auto Book Search - - +

+
+

+ + Global Preferences +

+

+ Configure global settings for your Booklore instance, including cover image handling, search preferences, and file upload limits.

-
- -
- -

Similar Book Recommendation - - -

-
- -
-
- -
- -
-

Max File Upload Size: - - -

-
-
- - MB +
+
+
+

+ + Book Cover Image +

- -
- - - Changes will take effect after restarting the server - + +
+
+
+
+ + + +
+

+ + Regenerates cover images for all EPUB and PDF books (excluding locked ones) from the embedded covers in the file. +

+
+
-

CBX Cache Size: - - -

-
-
- - MB + +
+
+

+ + Search & Recommendations +

+
+ +
+
+
+
+ + + +
+

+ + Automatically attempts metadata matching when the book information panel is opened. +

+
+
+ +
+
+
+ + + +
+

+ + Enables or disables similar book recommendations based on your library. +

+
+
+
+
+ +
+
+

+ + File Management +

+
+ +
+
+
+
+ +
+
+ + MB +
+ +
+
+

+ + Defines the maximum allowed size (in MB) for each uploaded file. Applies to EPUB, PDF, CBZ, CBR, and CB7 formats. +

+
+ + Changes will take effect after restarting the server +
+
+
+ +
+
+
+ +
+
+ + MB +
+ +
+
+

+ + Limits the total size (in MB) of the CBX image cache. If exceeded, older cache entries are removed automatically. +

+
+
-
- -
- -
-
diff --git a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.scss b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.scss index b886b1f68..191e9ce6e 100644 --- a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.scss +++ b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.scss @@ -1,3 +1,217 @@ +.main-container { + width: 100%; + padding: 1rem; + height: calc(100dvh - 10.5rem); + overflow-y: auto; + border-width: 1px; + border-radius: 0.5rem; + + @media (min-width: 768px) { + height: calc(100dvh - 11.65rem); + } +} + .enclosing-container { border-color: var(--p-content-border-color); + background: var(--p-content-background); +} + +.settings-header { + margin-top: 1rem; + margin-bottom: 2rem; +} + +.settings-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--p-text-color); + margin: 0 0 0.75rem 0; + + .pi { + color: var(--p-primary-color); + font-size: 1.25rem; + } +} + +.settings-description { + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin-bottom: 1rem; +} + +.settings-content { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.preferences-section { + @media (min-width: 768px) { + padding: 0 1rem; + } +} + +.section-header { + margin-bottom: 1rem; +} + +.section-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.125rem; + font-weight: 600; + color: var(--p-text-color); + margin: 0; + + .pi { + color: var(--p-primary-color); + } +} + +.settings-card { + border: 1px solid var(--p-content-border-color); + border-radius: 8px; + background: var(--p-content-background); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.setting-item { + display: flex; + align-items: flex-start; + gap: 2rem; + + &:last-child { + border-bottom: none; + padding-bottom: 0; + } + + @media (max-width: 768px) { + flex-direction: column; + gap: 1rem; + } +} + +.setting-info { + flex: 1; + min-width: 0; + + .setting-label { + display: block; + font-weight: 600; + color: var(--p-text-color); + font-size: 1rem; + margin-bottom: 0.5rem; + } + + .setting-label-row { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 0.5rem; + flex-wrap: wrap; + + .setting-label { + margin-bottom: 0; + flex-shrink: 0; + min-width: 180px; + } + + .input-group { + display: flex; + align-items: center; + gap: 0.75rem; + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + } + } + + .setting-description { + display: flex; + align-items: flex-start; + gap: 0.5rem; + color: var(--p-text-muted-color); + font-size: 0.875rem; + line-height: 1.5; + margin: 0; + + .pi { + color: var(--p-primary-color); + margin-top: 0.125rem; + flex-shrink: 0; + } + } +} + +.setting-control { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: flex-end; + + @media (max-width: 768px) { + align-items: flex-start; + width: 100%; + } +} + +.input-group { + display: flex; + align-items: center; + gap: 0.75rem; + + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } +} + +.input-with-unit { + position: relative; + display: flex; + align-items: center; + + input { + padding-right: 2.5rem; + width: 120px; + } + + .unit { + position: absolute; + right: 0.75rem; + color: var(--p-text-muted-color); + font-size: 0.875rem; + pointer-events: none; + } +} + +.warning-message { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; + color: var(--p-text-muted-color); + margin-top: 0.5rem; + + .pi { + color: var(--p-orange-500); + font-size: 0.875rem; + } + + @media (max-width: 768px) { + margin-top: 0.5rem; + } } diff --git a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.ts b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.ts index 7ad1eefd8..0c2975f21 100644 --- a/booklore-ui/src/app/settings/global-preferences/global-preferences.component.ts +++ b/booklore-ui/src/app/settings/global-preferences/global-preferences.component.ts @@ -1,10 +1,7 @@ import {Component, inject, OnInit} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {Observable} from 'rxjs'; - -import {Divider} from 'primeng/divider'; import {Button} from 'primeng/button'; -import {Tooltip} from 'primeng/tooltip'; import {ToggleSwitch} from 'primeng/toggleswitch'; import {MessageService} from 'primeng/api'; @@ -18,9 +15,7 @@ import {InputText} from 'primeng/inputtext'; selector: 'app-global-preferences', standalone: true, imports: [ - Divider, Button, - Tooltip, ToggleSwitch, FormsModule, InputText diff --git a/booklore-ui/src/app/settings/global-preferences/metadata-match-weights-component/metadata-match-weights-component.html b/booklore-ui/src/app/settings/global-preferences/metadata-match-weights-component/metadata-match-weights-component.html index 106f497c3..78cc97072 100644 --- a/booklore-ui/src/app/settings/global-preferences/metadata-match-weights-component/metadata-match-weights-component.html +++ b/booklore-ui/src/app/settings/global-preferences/metadata-match-weights-component/metadata-match-weights-component.html @@ -53,6 +53,7 @@
- Enabled Metadata Providers: - - -

+