From feac864e73b84b1e61f5b18d113033e372642fac Mon Sep 17 00:00:00 2001 From: Kevin Rauwolf Date: Tue, 1 Apr 2025 01:13:45 -0700 Subject: [PATCH 01/19] Rename CACHE_DIR env var to CACHE_DIRECTORY (#3343) --- cps/constants.py | 7 +++++-- cps/fs.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cps/constants.py b/cps/constants.py index 48b5a1060..42ed829f8 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -37,9 +37,12 @@ STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static') TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates') TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations') -# Cache dir - use CACHE_DIR environment variable, otherwise use the default directory: cps/cache +# Cache dir - use CACHE_DIRECTORY environment variable, otherwise CACHE_DIR (for backwards compatibility), +# otherwise use the default directory: cps/cache DEFAULT_CACHE_DIR = os.path.join(BASE_DIR, 'cps', 'cache') -CACHE_DIR = os.environ.get('CACHE_DIR', DEFAULT_CACHE_DIR) +CACHE_DIRECTORY = os.environ.get( + 'CACHE_DIRECTORY', os.environ.get('CACHE_DIR', DEFAULT_CACHE_DIR) +) if HOME_CONFIG: home_dir = os.path.join(os.path.expanduser("~"), ".calibre-web") diff --git a/cps/fs.py b/cps/fs.py index 996499c3e..1bee86a34 100644 --- a/cps/fs.py +++ b/cps/fs.py @@ -17,7 +17,7 @@ # along with this program. If not, see . from . import logger -from .constants import CACHE_DIR +from .constants import CACHE_DIRECTORY from os import makedirs, remove from os.path import isdir, isfile, join from shutil import rmtree @@ -25,7 +25,7 @@ from shutil import rmtree class FileSystem: _instance = None - _cache_dir = CACHE_DIR + _cache_dir = CACHE_DIRECTORY def __new__(cls): if cls._instance is None: From d32ad1d5ca974403dc619e33a87d067ebdaf909c Mon Sep 17 00:00:00 2001 From: David Champion Date: Thu, 3 Apr 2025 13:14:15 -0700 Subject: [PATCH 02/19] Show shelf sizes in layout --- cps/templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/layout.html b/cps/templates/layout.html index d433a5aaa..25ee6fa4c 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -154,7 +154,7 @@ {% if current_user.is_authenticated or g.allow_anonymous %} {% for shelf in g.shelves_access %} -
  • {{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}
  • +
  • {{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %} ({{shelf.books.all()|length}})
  • {% endfor %} {% if not current_user.is_anonymous %} From 1ce1aee35e40b5b48cd993eaf10acb0f34c09c3b Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Sun, 26 Oct 2025 18:23:07 +0100 Subject: [PATCH 03/19] fix(js reader): adjust height to dynamic viewport height, add amber theme --- .gitignore | 2 + cps/static/css/epub_themes.css | 23 +- cps/static/css/main.css | 243 +++++++++++------ cps/templates/read.html | 462 ++++++++++++++++++++------------- 4 files changed, 462 insertions(+), 268 deletions(-) diff --git a/.gitignore b/.gitignore index ff08f874b..f3e0ae8b7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ gdrive_credentials client_secrets.json gmail.json /.key +calibre-volume +calibre-web-env diff --git a/cps/static/css/epub_themes.css b/cps/static/css/epub_themes.css index b9cbcedbe..0a90a07fe 100644 --- a/cps/static/css/epub_themes.css +++ b/cps/static/css/epub_themes.css @@ -1,19 +1,24 @@ .lightTheme { - background: #fff; - color: #000; + background: #fff; + color: #000; } .darkTheme { - background: #202124; - color: #fff + background: #202124; + color: #fff; } .sepiaTheme { - background: #ece1ca; - color: #000; + background: #ece1ca; + color: #000; +} + +.amberTheme { + background: #e8bf5e; + color: #000; } .blackTheme { - background: #000; - color: #fff; -} \ No newline at end of file + background: #000; + color: #fff; +} diff --git a/cps/static/css/main.css b/cps/static/css/main.css index 571f546c9..fcb00eed6 100644 --- a/cps/static/css/main.css +++ b/cps/static/css/main.css @@ -1,11 +1,10 @@ @font-face { - font-family: 'fontello'; - src: url('fonts/fontello.eot?60518104'); - src: - url('fonts/fontello.eot?60518104#iefix') format('embedded-opentype'), - url('fonts/fontello.woff?60518104') format('woff'), - url('fonts/fontello.ttf?60518104') format('truetype'), - url('fonts/fontello.svg?60518104#fontello') format('svg'); + font-family: "fontello"; + src: url("fonts/fontello.eot?60518104"); + src: url("fonts/fontello.eot?60518104#iefix") format("embedded-opentype"), + url("fonts/fontello.woff?60518104") format("woff"), + url("fonts/fontello.ttf?60518104") format("truetype"), + url("fonts/fontello.svg?60518104#fontello") format("svg"); font-weight: normal; font-style: normal; } @@ -16,7 +15,7 @@ body { } .myselect { - overflow: visible !important; + overflow: visible !important; } #main { @@ -48,7 +47,7 @@ body { -webkit-transition: opacity 0.5s; -moz-transition: opacity 0.5s; transition: opacity 0.5s; - z-index: 10; + z-index: 100; } #titlebar:hover { @@ -93,14 +92,18 @@ body { box-shadow: inset 0 0 6px rgba(155, 155, 155, 0.8); } -#book-title { font-weight: 600; } -#title-seperator { display: none; } +#book-title { + font-weight: 600; +} +#title-seperator { + display: none; +} #viewer { width: 80%; height: 80%; margin: 0 auto; - z-index: 2; + z-index: 0; position: relative; overflow: hidden; } @@ -136,6 +139,7 @@ body { user-select: none; padding-top: 160px; padding-bottom: 160px; + z-index: 10; } .arrow:hover { @@ -200,10 +204,16 @@ body { max-width: 80%; } -#title-controls { float: right; } +#title-controls { + float: right; +} -#panels a::before { visibility: visible; } -#panels a:hover { color: #aaa; } +#panels a::before { + visibility: visible; +} +#panels a:hover { + color: #aaa; +} .list_item.currentChapter > a, .list_item a:hover { @@ -237,8 +247,12 @@ body { margin-top: -1px; } -input::-webkit-input-placeholder { color: #454545; } -input:-moz-placeholder { color: #454545; } +input::-webkit-input-placeholder { + color: #454545; +} +input:-moz-placeholder { + color: #454545; +} #divider { position: absolute; @@ -378,14 +392,14 @@ input:-moz-placeholder { color: #454545; } } .toc_toggle::before { - content: '▸'; + content: "▸"; color: #fff; margin-right: -4px; } .currentChapter > .toc_toggle::before, .openChapter > .toc_toggle::before { - content: '▾'; + content: "▾"; } .view { @@ -499,13 +513,25 @@ input:-moz-placeholder { color: #454545; } list-style-type: none; } -#settingsPanel .xsmall { font-size: x-small; } -#settingsPanel .small { font-size: small; } -#settingsPanel .medium { font-size: medium; } -#settingsPanel .large { font-size: large; } -#settingsPanel .xlarge { font-size: x-large; } +#settingsPanel .xsmall { + font-size: x-small; +} +#settingsPanel .small { + font-size: small; +} +#settingsPanel .medium { + font-size: medium; +} +#settingsPanel .large { + font-size: large; +} +#settingsPanel .xlarge { + font-size: x-large; +} -.highlight { background-color: yellow; } +.highlight { + background-color: yellow; +} .modal { position: fixed; @@ -581,6 +607,7 @@ input:-moz-placeholder { color: #454545; } color: white; } +.md-content .themes button.lightTheme, .md-content .themes button.whiteTheme { background-color: white; color: black; @@ -591,6 +618,11 @@ input:-moz-placeholder { color: #454545; } color: black; } +.md-content .themes button.amberTheme { + background-color: #e8bf5e; + color: black; +} + .md-content .themes button.blackTheme { background-color: black; color: white; @@ -638,31 +670,47 @@ input:-moz-placeholder { color: #454545; } @media only screen and (max-width: 900px) { #viewer { - width: 60%; - margin-left: 20%; + width: 100%; + height: calc(100dvh - 99px); + margin-left: 0; + } + + #viewer iframe { + width: 100%; + height: 100%; } #prev { left: 20px; + z-index: 10; } #next { right: 20px; + z-index: 10; } } @media only screen and (max-width: 550px) { #viewer { - width: 80%; - margin-left: 10%; + width: 100%; + height: calc(100dvh - 99px); + margin-left: 0; + } + + #viewer iframe { + width: 100%; + height: 100%; } #prev { left: 0; + z-index: 10; } #next { right: 0; + z-index: 10; } .arrow { @@ -712,11 +760,7 @@ input:-moz-placeholder { color: #454545; } } } -@media only screen - and (min-device-width: 768px) - and (max-device-width: 1024px) - and (orientation: landscape) - /* and (-webkit-min-device-pixel-ratio: 2)*/ { +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) /* and (-webkit-min-device-pixel-ratio: 2) */ { #viewer { width: 80%; margin-left: 10%; @@ -739,47 +783,52 @@ input:-moz-placeholder { color: #454545; } /* For iPhone 6\6s portrait layouts only */ @media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: portrait) { #viewer { - width: 300px; - height: 480px; + width: 100%; + height: calc(100dvh - 99px); } #viewer iframe { - width: 300px; - height: 480px; + width: 100%; + height: 100%; } } /* For iPhone 6\6s landscape layouts only */ @media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: landscape) { #viewer { - width: 450px; - height: 300px; + width: 100%; + height: calc(100dvh - 99px); } #viewer iframe { - width: 450px; - height: 300px; + width: 100%; + height: 100%; } } /* For iPhone portrait layouts only */ @media only screen and (max-device-width: 374px) and (orientation: portrait) { #viewer { - width: 256px; - height: 432px; + width: 100%; + height: calc(100dvh - 99px); } #viewer iframe { - width: 256px; - height: 432px; + width: 100%; + height: 100%; } } /* For iPhone landscape layouts only */ @media only screen and (max-device-width: 374px) and (orientation: landscape) { + #viewer { + width: 100%; + height: calc(100dvh - 99px); + } + #viewer iframe { - width: 256px; - height: 124px; + width: 100%; + height: 100%; } } @@ -803,32 +852,76 @@ input:-moz-placeholder { color: #454545; } font-size: 112%; } -.icon-search::before { content: '\e807'; } /* '' */ -.icon-resize-full-1::before { content: '\e804'; } /* '' */ -.icon-cancel-circled2::before { content: '\e80f'; } /* '' */ -.icon-link::before { content: '\e80d'; } /* '' */ -.icon-bookmark::before { content: '\e805'; } /* '' */ -.icon-bookmark-empty::before { content: '\e806'; } /* '' */ -.icon-download-cloud::before { content: '\e811'; } /* '' */ -.icon-edit::before { content: '\e814'; } /* '' */ -.icon-menu::before { content: '\e802'; } /* '' */ -.icon-cog::before { content: '\e813'; } /* '' */ -.icon-resize-full::before { content: '\e812'; } /* '' */ -.icon-cancel-circled::before { content: '\e80e'; } /* '' */ -.icon-up-dir::before { content: '\e80c'; } /* '' */ -.icon-right-dir::before { content: '\e80b'; } /* '' */ -.icon-angle-right::before { content: '\e809'; } /* '' */ -.icon-angle-down::before { content: '\e80a'; } /* '' */ -.icon-right::before { content: '\e815'; } /* '' */ -.icon-list-1::before { content: '\e803'; } /* '' */ -.icon-list-numbered::before { content: '\e801'; } /* '' */ -.icon-columns::before { content: '\e810'; } /* '' */ -.icon-list::before { content: '\e800'; } /* '' */ -.icon-resize-small::before { content: '\e808'; } /* '' */ +.icon-search::before { + content: "\e807"; +} /* '' */ +.icon-resize-full-1::before { + content: "\e804"; +} /* '' */ +.icon-cancel-circled2::before { + content: "\e80f"; +} /* '' */ +.icon-link::before { + content: "\e80d"; +} /* '' */ +.icon-bookmark::before { + content: "\e805"; +} /* '' */ +.icon-bookmark-empty::before { + content: "\e806"; +} /* '' */ +.icon-download-cloud::before { + content: "\e811"; +} /* '' */ +.icon-edit::before { + content: "\e814"; +} /* '' */ +.icon-menu::before { + content: "\e802"; +} /* '' */ +.icon-cog::before { + content: "\e813"; +} /* '' */ +.icon-resize-full::before { + content: "\e812"; +} /* '' */ +.icon-cancel-circled::before { + content: "\e80e"; +} /* '' */ +.icon-up-dir::before { + content: "\e80c"; +} /* '' */ +.icon-right-dir::before { + content: "\e80b"; +} /* '' */ +.icon-angle-right::before { + content: "\e809"; +} /* '' */ +.icon-angle-down::before { + content: "\e80a"; +} /* '' */ +.icon-right::before { + content: "\e815"; +} /* '' */ +.icon-list-1::before { + content: "\e803"; +} /* '' */ +.icon-list-numbered::before { + content: "\e801"; +} /* '' */ +.icon-columns::before { + content: "\e810"; +} /* '' */ +.icon-list::before { + content: "\e800"; +} /* '' */ +.icon-resize-small::before { + content: "\e808"; +} /* '' */ -#progress{ - right: 4rem; - bottom: 4rem; - width: fit-content; - position: absolute; +#progress { + right: 4rem; + bottom: 2px; + width: fit-content; + position: absolute; } diff --git a/cps/templates/read.html b/cps/templates/read.html index 9c6e4bcac..7166d86a6 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -1,46 +1,72 @@ - - - - + + + {{_('epub Reader')}} | {{title}} - - - + + + {% if g.google_site_verification|length > 0 %} - + {% endif %} - - + + - - - - - + + + + + - +
    - -
    -
    - Menu -
    -
    - -   –   - -
    - -
    - -
    - -
    - -
    0%
    -
    -
    -
    -
    - - + +
    + + 100% +
    @@ -273,13 +297,40 @@ } // font size settings logic - let fontSizeFader = document.getElementById("fontSizeFader"); + let currentFontSize = 100; // default 100% + const minFontSize = 50; + const maxFontSize = 300; + const stepSize = 5; - // Save font size when changed - fontSizeFader.addEventListener("change", function () { - const fontSize = this.value; - localStorage.setItem("calibre.reader.fontSize", fontSize); - reader.rendition.themes.fontSize(`${fontSize}%`); + const fontSizeDisplay = document.getElementById("fontSizeDisplay"); + const fontSizeDecrease = document.getElementById("fontSizeDecrease"); + const fontSizeIncrease = document.getElementById("fontSizeIncrease"); + + function updateFontSize(newSize) { + if (newSize < minFontSize) newSize = minFontSize; + if (newSize > maxFontSize) newSize = maxFontSize; + + currentFontSize = newSize; + fontSizeDisplay.textContent = newSize + "%"; + localStorage.setItem("calibre.reader.fontSize", newSize); + if (reader && reader.rendition) { + reader.rendition.themes.fontSize(`${newSize}%`); + } + } + + // Restore saved font size on load + const savedFontSize = localStorage.getItem("calibre.reader.fontSize"); + if (savedFontSize) { + currentFontSize = parseInt(savedFontSize); + fontSizeDisplay.textContent = savedFontSize + "%"; + } + + fontSizeDecrease.addEventListener("click", function () { + updateFontSize(currentFontSize - stepSize); + }); + + fontSizeIncrease.addEventListener("click", function () { + updateFontSize(currentFontSize + stepSize); }); let defaultFont; From 902368efa7b6c765fc4e0aeadc69bd23eeb37bb3 Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Sun, 26 Oct 2025 18:47:56 +0100 Subject: [PATCH 06/19] feat(epub.js): add saving for settings of the font family to localstorage --- cps/static/js/reading/epub.js | 7 ++++++- cps/templates/read.html | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 21663a6ab..0a907ed2a 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -119,11 +119,16 @@ var reader; const theme = localStorage.getItem("calibre.reader.theme") ?? "lightTheme"; selectTheme(theme); - // Restore saved font size after reader is ready + // Restore saved font and font size after reader is ready reader.book.ready.then(() => { const savedFontSize = localStorage.getItem("calibre.reader.fontSize"); if (savedFontSize) { reader.rendition.themes.fontSize(`${savedFontSize}%`); } + + const savedFont = localStorage.getItem("calibre.reader.font"); + if (savedFont && window.selectFont) { + window.selectFont(savedFont); + } }); })(); diff --git a/cps/templates/read.html b/cps/templates/read.html index f31b97141..6262f403b 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -335,7 +335,7 @@ let defaultFont; - function selectFont(id) { + window.selectFont = function (id) { if (!defaultFont) { defaultFont = reader.rendition.getContents()[0]?.css("font-family"); } @@ -346,12 +346,15 @@ } document.getElementById(id).querySelector("span").textContent = "✓"; + // Save font selection to localStorage + localStorage.setItem("calibre.reader.font", id); + if (id == "default") { reader.rendition.themes.font(defaultFont); return; } reader.rendition.themes.font(id); - } + }; function spread(id) { spans = document.getElementById("layout").querySelectorAll("span"); From d8fbf967b364532bae5c3c4f5f2c68f782bbc04e Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Sun, 26 Oct 2025 21:28:58 +0100 Subject: [PATCH 07/19] add console process interruption by ctrl C --- cps.py | 20 ++++++++++++++++++-- cps/server.py | 12 ++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cps.py b/cps.py index 039c2a5e1..97863504f 100755 --- a/cps.py +++ b/cps.py @@ -42,9 +42,25 @@ def hide_console_windows(): if __name__ == '__main__': - if os.name == "nt": + # Only hide the console window on Windows when there is no interactive + # console attached. This preserves the ability to use Ctrl+C from a + # terminal (stdin is a tty) to stop the server. + if os.name == "nt" and not sys.stdin.isatty(): hide_console_windows() - main() + + try: + main() + except KeyboardInterrupt: + # Allow stopping the server with Ctrl+C from an interactive console. + try: + # Try to perform a graceful stop if web_server is available. + # Import here to avoid import-time side-effects when not needed. + from cps import web_server + web_server.stop() + except Exception: + pass + print('\nCalibre-Web: received interrupt, shutting down') + sys.exit(0) diff --git a/cps/server.py b/cps/server.py index dca407a7e..bf53a3d46 100644 --- a/cps/server.py +++ b/cps/server.py @@ -216,6 +216,12 @@ class WebServer(object): try: sock, output = self._make_gevent_listener() log.info('Starting Gevent server on %s', output) + # Also print to stdout so interactive terminals show a clear success message + try: + print(f"Calibre-Web: server started on {output}") + except Exception: + print(f"Calibre-Web: error {output}") + pass self.wsgiserver = WSGIServer(sock, self.app, log=self.access_logger, handler_class=MyWSGIHandler, error_log=log, spawn=Pool(), **ssl_args) @@ -266,6 +272,12 @@ class WebServer(object): output = _readable_listen_address(self.listen_address, self.listen_port) http_server.listen(self.listen_port, self.listen_address) log.info('Starting Tornado server on %s', output) + # Also print to stdout so interactive terminals show a clear success message + try: + print(f"Calibre-Web: server started on {output}") + except Exception: + print(f"Calibre-Web: error {output}") + pass self.wsgiserver = IOLoop.current() self.wsgiserver.start() From 560e8e132d0db9cc1d622ccba2c313c36e39bcc4 Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Wed, 29 Oct 2025 20:37:15 +0100 Subject: [PATCH 08/19] add swipes by gestures in settings as an option, hammer.js for mobile --- cps/static/css/reader.css | 75 +- cps/static/js/libs/hammer.min.js | 1255 ++++++++++++++++++++++++++++++ cps/static/js/reading/epub.js | 129 ++- cps/templates/read.html | 30 + 4 files changed, 1430 insertions(+), 59 deletions(-) create mode 100644 cps/static/js/libs/hammer.min.js diff --git a/cps/static/css/reader.css b/cps/static/css/reader.css index 7229a6e3b..ac943460f 100644 --- a/cps/static/css/reader.css +++ b/cps/static/css/reader.css @@ -1,47 +1,52 @@ .fontSizeWrapper { - position: relative; + position: relative; } .slider { - position: absolute; - top: 50%; - transform: translate(0,-50%); - width: 90%; - height: 60px; - background: transparent; - border-radius: 20px; - display: flex; - align-items: center; - box-shadow: 0px 15px 40px #7E6D5766; + position: absolute; + top: 50%; + transform: translate(0, -50%); + width: 90%; + height: 60px; + background: transparent; + border-radius: 20px; + display: flex; + align-items: center; + box-shadow: 0px 15px 40px #7e6d5766; } .slider label { - font-size: 20px; - font-weight: 400; - font-family: Open Sans; - padding-right: 10px; - color: white; + font-size: 20px; + font-weight: 400; + font-family: Open Sans; + padding-right: 10px; + color: white; } .slider input[type="range"] { - width: 80%; - height: 5px; - background: black; - border: none; - outline: none; + width: 80%; + height: 5px; + background: black; + border: none; + outline: none; } .item { - font-size: 20px; - font-weight: 400; - font-family: Open Sans; - padding-right: 10px; - color: white; + font-size: 20px; + font-weight: 400; + font-family: Open Sans; + padding-right: 10px; + color: white; } -.item~button { - display: inline-block; - border: none; - text-align: center; - text-decoration: none; - margin-top: 5%; - margin-right: 1%; - font-size: 16px; -} \ No newline at end of file +.item ~ button { + display: inline-block; + border: none; + text-align: center; + text-decoration: none; + margin-top: 5%; + margin-right: 1%; + font-size: 16px; +} + +#main, +#viewer { + touch-action: pan-y; +} diff --git a/cps/static/js/libs/hammer.min.js b/cps/static/js/libs/hammer.min.js new file mode 100644 index 000000000..4e9b56f93 --- /dev/null +++ b/cps/static/js/libs/hammer.min.js @@ -0,0 +1,1255 @@ +/*! Hammer.JS - v2.0.8 - 2016-04-23 + * http://hammerjs.github.io/ + * + * Copyright (c) 2016 Jorik Tangelder; + * Licensed under the MIT license */ +!(function (a, b, c, d) { + "use strict"; + function e(a, b, c) { + return setTimeout(j(a, c), b); + } + function f(a, b, c) { + return Array.isArray(a) ? (g(a, c[b], c), !0) : !1; + } + function g(a, b, c) { + var e; + if (a) + if (a.forEach) a.forEach(b, c); + else if (a.length !== d) + for (e = 0; e < a.length; ) b.call(c, a[e], e, a), e++; + else for (e in a) a.hasOwnProperty(e) && b.call(c, a[e], e, a); + } + function h(b, c, d) { + var e = "DEPRECATED METHOD: " + c + "\n" + d + " AT \n"; + return function () { + var c = new Error("get-stack-trace"), + d = + c && c.stack + ? c.stack + .replace(/^[^\(]+?[\n$]/gm, "") + .replace(/^\s+at\s+/gm, "") + .replace( + /^Object.\s*\(/gm, + "{anonymous}()@" + ) + : "Unknown Stack Trace", + f = a.console && (a.console.warn || a.console.log); + return f && f.call(a.console, e, d), b.apply(this, arguments); + }; + } + function i(a, b, c) { + var d, + e = b.prototype; + (d = a.prototype = Object.create(e)), + (d.constructor = a), + (d._super = e), + c && la(d, c); + } + function j(a, b) { + return function () { + return a.apply(b, arguments); + }; + } + function k(a, b) { + return typeof a == oa ? a.apply(b ? b[0] || d : d, b) : a; + } + function l(a, b) { + return a === d ? b : a; + } + function m(a, b, c) { + g(q(b), function (b) { + a.addEventListener(b, c, !1); + }); + } + function n(a, b, c) { + g(q(b), function (b) { + a.removeEventListener(b, c, !1); + }); + } + function o(a, b) { + for (; a; ) { + if (a == b) return !0; + a = a.parentNode; + } + return !1; + } + function p(a, b) { + return a.indexOf(b) > -1; + } + function q(a) { + return a.trim().split(/\s+/g); + } + function r(a, b, c) { + if (a.indexOf && !c) return a.indexOf(b); + for (var d = 0; d < a.length; ) { + if ((c && a[d][c] == b) || (!c && a[d] === b)) return d; + d++; + } + return -1; + } + function s(a) { + return Array.prototype.slice.call(a, 0); + } + function t(a, b, c) { + for (var d = [], e = [], f = 0; f < a.length; ) { + var g = b ? a[f][b] : a[f]; + r(e, g) < 0 && d.push(a[f]), (e[f] = g), f++; + } + return ( + c && + (d = b + ? d.sort(function (a, c) { + return a[b] > c[b]; + }) + : d.sort()), + d + ); + } + function u(a, b) { + for ( + var c, e, f = b[0].toUpperCase() + b.slice(1), g = 0; + g < ma.length; + + ) { + if (((c = ma[g]), (e = c ? c + f : b), e in a)) return e; + g++; + } + return d; + } + function v() { + return ua++; + } + function w(b) { + var c = b.ownerDocument || b; + return c.defaultView || c.parentWindow || a; + } + function x(a, b) { + var c = this; + (this.manager = a), + (this.callback = b), + (this.element = a.element), + (this.target = a.options.inputTarget), + (this.domHandler = function (b) { + k(a.options.enable, [a]) && c.handler(b); + }), + this.init(); + } + function y(a) { + var b, + c = a.options.inputClass; + return new (b = c ? c : xa ? M : ya ? P : wa ? R : L)(a, z); + } + function z(a, b, c) { + var d = c.pointers.length, + e = c.changedPointers.length, + f = b & Ea && d - e === 0, + g = b & (Ga | Ha) && d - e === 0; + (c.isFirst = !!f), + (c.isFinal = !!g), + f && (a.session = {}), + (c.eventType = b), + A(a, c), + a.emit("hammer.input", c), + a.recognize(c), + (a.session.prevInput = c); + } + function A(a, b) { + var c = a.session, + d = b.pointers, + e = d.length; + c.firstInput || (c.firstInput = D(b)), + e > 1 && !c.firstMultiple + ? (c.firstMultiple = D(b)) + : 1 === e && (c.firstMultiple = !1); + var f = c.firstInput, + g = c.firstMultiple, + h = g ? g.center : f.center, + i = (b.center = E(d)); + (b.timeStamp = ra()), + (b.deltaTime = b.timeStamp - f.timeStamp), + (b.angle = I(h, i)), + (b.distance = H(h, i)), + B(c, b), + (b.offsetDirection = G(b.deltaX, b.deltaY)); + var j = F(b.deltaTime, b.deltaX, b.deltaY); + (b.overallVelocityX = j.x), + (b.overallVelocityY = j.y), + (b.overallVelocity = qa(j.x) > qa(j.y) ? j.x : j.y), + (b.scale = g ? K(g.pointers, d) : 1), + (b.rotation = g ? J(g.pointers, d) : 0), + (b.maxPointers = c.prevInput + ? b.pointers.length > c.prevInput.maxPointers + ? b.pointers.length + : c.prevInput.maxPointers + : b.pointers.length), + C(c, b); + var k = a.element; + o(b.srcEvent.target, k) && (k = b.srcEvent.target), (b.target = k); + } + function B(a, b) { + var c = b.center, + d = a.offsetDelta || {}, + e = a.prevDelta || {}, + f = a.prevInput || {}; + (b.eventType !== Ea && f.eventType !== Ga) || + ((e = a.prevDelta = { x: f.deltaX || 0, y: f.deltaY || 0 }), + (d = a.offsetDelta = { x: c.x, y: c.y })), + (b.deltaX = e.x + (c.x - d.x)), + (b.deltaY = e.y + (c.y - d.y)); + } + function C(a, b) { + var c, + e, + f, + g, + h = a.lastInterval || b, + i = b.timeStamp - h.timeStamp; + if (b.eventType != Ha && (i > Da || h.velocity === d)) { + var j = b.deltaX - h.deltaX, + k = b.deltaY - h.deltaY, + l = F(i, j, k); + (e = l.x), + (f = l.y), + (c = qa(l.x) > qa(l.y) ? l.x : l.y), + (g = G(j, k)), + (a.lastInterval = b); + } else + (c = h.velocity), + (e = h.velocityX), + (f = h.velocityY), + (g = h.direction); + (b.velocity = c), + (b.velocityX = e), + (b.velocityY = f), + (b.direction = g); + } + function D(a) { + for (var b = [], c = 0; c < a.pointers.length; ) + (b[c] = { + clientX: pa(a.pointers[c].clientX), + clientY: pa(a.pointers[c].clientY), + }), + c++; + return { + timeStamp: ra(), + pointers: b, + center: E(b), + deltaX: a.deltaX, + deltaY: a.deltaY, + }; + } + function E(a) { + var b = a.length; + if (1 === b) return { x: pa(a[0].clientX), y: pa(a[0].clientY) }; + for (var c = 0, d = 0, e = 0; b > e; ) + (c += a[e].clientX), (d += a[e].clientY), e++; + return { x: pa(c / b), y: pa(d / b) }; + } + function F(a, b, c) { + return { x: b / a || 0, y: c / a || 0 }; + } + function G(a, b) { + return a === b + ? Ia + : qa(a) >= qa(b) + ? 0 > a + ? Ja + : Ka + : 0 > b + ? La + : Ma; + } + function H(a, b, c) { + c || (c = Qa); + var d = b[c[0]] - a[c[0]], + e = b[c[1]] - a[c[1]]; + return Math.sqrt(d * d + e * e); + } + function I(a, b, c) { + c || (c = Qa); + var d = b[c[0]] - a[c[0]], + e = b[c[1]] - a[c[1]]; + return (180 * Math.atan2(e, d)) / Math.PI; + } + function J(a, b) { + return I(b[1], b[0], Ra) + I(a[1], a[0], Ra); + } + function K(a, b) { + return H(b[0], b[1], Ra) / H(a[0], a[1], Ra); + } + function L() { + (this.evEl = Ta), + (this.evWin = Ua), + (this.pressed = !1), + x.apply(this, arguments); + } + function M() { + (this.evEl = Xa), + (this.evWin = Ya), + x.apply(this, arguments), + (this.store = this.manager.session.pointerEvents = []); + } + function N() { + (this.evTarget = $a), + (this.evWin = _a), + (this.started = !1), + x.apply(this, arguments); + } + function O(a, b) { + var c = s(a.touches), + d = s(a.changedTouches); + return b & (Ga | Ha) && (c = t(c.concat(d), "identifier", !0)), [c, d]; + } + function P() { + (this.evTarget = bb), (this.targetIds = {}), x.apply(this, arguments); + } + function Q(a, b) { + var c = s(a.touches), + d = this.targetIds; + if (b & (Ea | Fa) && 1 === c.length) + return (d[c[0].identifier] = !0), [c, c]; + var e, + f, + g = s(a.changedTouches), + h = [], + i = this.target; + if ( + ((f = c.filter(function (a) { + return o(a.target, i); + })), + b === Ea) + ) + for (e = 0; e < f.length; ) (d[f[e].identifier] = !0), e++; + for (e = 0; e < g.length; ) + d[g[e].identifier] && h.push(g[e]), + b & (Ga | Ha) && delete d[g[e].identifier], + e++; + return h.length ? [t(f.concat(h), "identifier", !0), h] : void 0; + } + function R() { + x.apply(this, arguments); + var a = j(this.handler, this); + (this.touch = new P(this.manager, a)), + (this.mouse = new L(this.manager, a)), + (this.primaryTouch = null), + (this.lastTouches = []); + } + function S(a, b) { + a & Ea + ? ((this.primaryTouch = b.changedPointers[0].identifier), + T.call(this, b)) + : a & (Ga | Ha) && T.call(this, b); + } + function T(a) { + var b = a.changedPointers[0]; + if (b.identifier === this.primaryTouch) { + var c = { x: b.clientX, y: b.clientY }; + this.lastTouches.push(c); + var d = this.lastTouches, + e = function () { + var a = d.indexOf(c); + a > -1 && d.splice(a, 1); + }; + setTimeout(e, cb); + } + } + function U(a) { + for ( + var b = a.srcEvent.clientX, c = a.srcEvent.clientY, d = 0; + d < this.lastTouches.length; + d++ + ) { + var e = this.lastTouches[d], + f = Math.abs(b - e.x), + g = Math.abs(c - e.y); + if (db >= f && db >= g) return !0; + } + return !1; + } + function V(a, b) { + (this.manager = a), this.set(b); + } + function W(a) { + if (p(a, jb)) return jb; + var b = p(a, kb), + c = p(a, lb); + return b && c ? jb : b || c ? (b ? kb : lb) : p(a, ib) ? ib : hb; + } + function X() { + if (!fb) return !1; + var b = {}, + c = a.CSS && a.CSS.supports; + return ( + [ + "auto", + "manipulation", + "pan-y", + "pan-x", + "pan-x pan-y", + "none", + ].forEach(function (d) { + b[d] = c ? a.CSS.supports("touch-action", d) : !0; + }), + b + ); + } + function Y(a) { + (this.options = la({}, this.defaults, a || {})), + (this.id = v()), + (this.manager = null), + (this.options.enable = l(this.options.enable, !0)), + (this.state = nb), + (this.simultaneous = {}), + (this.requireFail = []); + } + function Z(a) { + return a & sb + ? "cancel" + : a & qb + ? "end" + : a & pb + ? "move" + : a & ob + ? "start" + : ""; + } + function $(a) { + return a == Ma + ? "down" + : a == La + ? "up" + : a == Ja + ? "left" + : a == Ka + ? "right" + : ""; + } + function _(a, b) { + var c = b.manager; + return c ? c.get(a) : a; + } + function aa() { + Y.apply(this, arguments); + } + function ba() { + aa.apply(this, arguments), (this.pX = null), (this.pY = null); + } + function ca() { + aa.apply(this, arguments); + } + function da() { + Y.apply(this, arguments), (this._timer = null), (this._input = null); + } + function ea() { + aa.apply(this, arguments); + } + function fa() { + aa.apply(this, arguments); + } + function ga() { + Y.apply(this, arguments), + (this.pTime = !1), + (this.pCenter = !1), + (this._timer = null), + (this._input = null), + (this.count = 0); + } + function ha(a, b) { + return ( + (b = b || {}), + (b.recognizers = l(b.recognizers, ha.defaults.preset)), + new ia(a, b) + ); + } + function ia(a, b) { + (this.options = la({}, ha.defaults, b || {})), + (this.options.inputTarget = this.options.inputTarget || a), + (this.handlers = {}), + (this.session = {}), + (this.recognizers = []), + (this.oldCssProps = {}), + (this.element = a), + (this.input = y(this)), + (this.touchAction = new V(this, this.options.touchAction)), + ja(this, !0), + g( + this.options.recognizers, + function (a) { + var b = this.add(new a[0](a[1])); + a[2] && b.recognizeWith(a[2]), + a[3] && b.requireFailure(a[3]); + }, + this + ); + } + function ja(a, b) { + var c = a.element; + if (c.style) { + var d; + g(a.options.cssProps, function (e, f) { + (d = u(c.style, f)), + b + ? ((a.oldCssProps[d] = c.style[d]), (c.style[d] = e)) + : (c.style[d] = a.oldCssProps[d] || ""); + }), + b || (a.oldCssProps = {}); + } + } + function ka(a, c) { + var d = b.createEvent("Event"); + d.initEvent(a, !0, !0), (d.gesture = c), c.target.dispatchEvent(d); + } + var la, + ma = ["", "webkit", "Moz", "MS", "ms", "o"], + na = b.createElement("div"), + oa = "function", + pa = Math.round, + qa = Math.abs, + ra = Date.now; + la = + "function" != typeof Object.assign + ? function (a) { + if (a === d || null === a) + throw new TypeError( + "Cannot convert undefined or null to object" + ); + for (var b = Object(a), c = 1; c < arguments.length; c++) { + var e = arguments[c]; + if (e !== d && null !== e) + for (var f in e) e.hasOwnProperty(f) && (b[f] = e[f]); + } + return b; + } + : Object.assign; + var sa = h( + function (a, b, c) { + for (var e = Object.keys(b), f = 0; f < e.length; ) + (!c || (c && a[e[f]] === d)) && (a[e[f]] = b[e[f]]), f++; + return a; + }, + "extend", + "Use `assign`." + ), + ta = h( + function (a, b) { + return sa(a, b, !0); + }, + "merge", + "Use `assign`." + ), + ua = 1, + va = /mobile|tablet|ip(ad|hone|od)|android/i, + wa = "ontouchstart" in a, + xa = u(a, "PointerEvent") !== d, + ya = wa && va.test(navigator.userAgent), + za = "touch", + Aa = "pen", + Ba = "mouse", + Ca = "kinect", + Da = 25, + Ea = 1, + Fa = 2, + Ga = 4, + Ha = 8, + Ia = 1, + Ja = 2, + Ka = 4, + La = 8, + Ma = 16, + Na = Ja | Ka, + Oa = La | Ma, + Pa = Na | Oa, + Qa = ["x", "y"], + Ra = ["clientX", "clientY"]; + x.prototype = { + handler: function () {}, + init: function () { + this.evEl && m(this.element, this.evEl, this.domHandler), + this.evTarget && m(this.target, this.evTarget, this.domHandler), + this.evWin && m(w(this.element), this.evWin, this.domHandler); + }, + destroy: function () { + this.evEl && n(this.element, this.evEl, this.domHandler), + this.evTarget && n(this.target, this.evTarget, this.domHandler), + this.evWin && n(w(this.element), this.evWin, this.domHandler); + }, + }; + var Sa = { mousedown: Ea, mousemove: Fa, mouseup: Ga }, + Ta = "mousedown", + Ua = "mousemove mouseup"; + i(L, x, { + handler: function (a) { + var b = Sa[a.type]; + b & Ea && 0 === a.button && (this.pressed = !0), + b & Fa && 1 !== a.which && (b = Ga), + this.pressed && + (b & Ga && (this.pressed = !1), + this.callback(this.manager, b, { + pointers: [a], + changedPointers: [a], + pointerType: Ba, + srcEvent: a, + })); + }, + }); + var Va = { + pointerdown: Ea, + pointermove: Fa, + pointerup: Ga, + pointercancel: Ha, + pointerout: Ha, + }, + Wa = { 2: za, 3: Aa, 4: Ba, 5: Ca }, + Xa = "pointerdown", + Ya = "pointermove pointerup pointercancel"; + a.MSPointerEvent && + !a.PointerEvent && + ((Xa = "MSPointerDown"), + (Ya = "MSPointerMove MSPointerUp MSPointerCancel")), + i(M, x, { + handler: function (a) { + var b = this.store, + c = !1, + d = a.type.toLowerCase().replace("ms", ""), + e = Va[d], + f = Wa[a.pointerType] || a.pointerType, + g = f == za, + h = r(b, a.pointerId, "pointerId"); + e & Ea && (0 === a.button || g) + ? 0 > h && (b.push(a), (h = b.length - 1)) + : e & (Ga | Ha) && (c = !0), + 0 > h || + ((b[h] = a), + this.callback(this.manager, e, { + pointers: b, + changedPointers: [a], + pointerType: f, + srcEvent: a, + }), + c && b.splice(h, 1)); + }, + }); + var Za = { touchstart: Ea, touchmove: Fa, touchend: Ga, touchcancel: Ha }, + $a = "touchstart", + _a = "touchstart touchmove touchend touchcancel"; + i(N, x, { + handler: function (a) { + var b = Za[a.type]; + if ((b === Ea && (this.started = !0), this.started)) { + var c = O.call(this, a, b); + b & (Ga | Ha) && + c[0].length - c[1].length === 0 && + (this.started = !1), + this.callback(this.manager, b, { + pointers: c[0], + changedPointers: c[1], + pointerType: za, + srcEvent: a, + }); + } + }, + }); + var ab = { touchstart: Ea, touchmove: Fa, touchend: Ga, touchcancel: Ha }, + bb = "touchstart touchmove touchend touchcancel"; + i(P, x, { + handler: function (a) { + var b = ab[a.type], + c = Q.call(this, a, b); + c && + this.callback(this.manager, b, { + pointers: c[0], + changedPointers: c[1], + pointerType: za, + srcEvent: a, + }); + }, + }); + var cb = 2500, + db = 25; + i(R, x, { + handler: function (a, b, c) { + var d = c.pointerType == za, + e = c.pointerType == Ba; + if ( + !( + e && + c.sourceCapabilities && + c.sourceCapabilities.firesTouchEvents + ) + ) { + if (d) S.call(this, b, c); + else if (e && U.call(this, c)) return; + this.callback(a, b, c); + } + }, + destroy: function () { + this.touch.destroy(), this.mouse.destroy(); + }, + }); + var eb = u(na.style, "touchAction"), + fb = eb !== d, + gb = "compute", + hb = "auto", + ib = "manipulation", + jb = "none", + kb = "pan-x", + lb = "pan-y", + mb = X(); + V.prototype = { + set: function (a) { + a == gb && (a = this.compute()), + fb && + this.manager.element.style && + mb[a] && + (this.manager.element.style[eb] = a), + (this.actions = a.toLowerCase().trim()); + }, + update: function () { + this.set(this.manager.options.touchAction); + }, + compute: function () { + var a = []; + return ( + g(this.manager.recognizers, function (b) { + k(b.options.enable, [b]) && + (a = a.concat(b.getTouchAction())); + }), + W(a.join(" ")) + ); + }, + preventDefaults: function (a) { + var b = a.srcEvent, + c = a.offsetDirection; + if (this.manager.session.prevented) return void b.preventDefault(); + var d = this.actions, + e = p(d, jb) && !mb[jb], + f = p(d, lb) && !mb[lb], + g = p(d, kb) && !mb[kb]; + if (e) { + var h = 1 === a.pointers.length, + i = a.distance < 2, + j = a.deltaTime < 250; + if (h && i && j) return; + } + return g && f + ? void 0 + : e || (f && c & Na) || (g && c & Oa) + ? this.preventSrc(b) + : void 0; + }, + preventSrc: function (a) { + (this.manager.session.prevented = !0), a.preventDefault(); + }, + }; + var nb = 1, + ob = 2, + pb = 4, + qb = 8, + rb = qb, + sb = 16, + tb = 32; + (Y.prototype = { + defaults: {}, + set: function (a) { + return ( + la(this.options, a), + this.manager && this.manager.touchAction.update(), + this + ); + }, + recognizeWith: function (a) { + if (f(a, "recognizeWith", this)) return this; + var b = this.simultaneous; + return ( + (a = _(a, this)), + b[a.id] || ((b[a.id] = a), a.recognizeWith(this)), + this + ); + }, + dropRecognizeWith: function (a) { + return f(a, "dropRecognizeWith", this) + ? this + : ((a = _(a, this)), delete this.simultaneous[a.id], this); + }, + requireFailure: function (a) { + if (f(a, "requireFailure", this)) return this; + var b = this.requireFail; + return ( + (a = _(a, this)), + -1 === r(b, a) && (b.push(a), a.requireFailure(this)), + this + ); + }, + dropRequireFailure: function (a) { + if (f(a, "dropRequireFailure", this)) return this; + a = _(a, this); + var b = r(this.requireFail, a); + return b > -1 && this.requireFail.splice(b, 1), this; + }, + hasRequireFailures: function () { + return this.requireFail.length > 0; + }, + canRecognizeWith: function (a) { + return !!this.simultaneous[a.id]; + }, + emit: function (a) { + function b(b) { + c.manager.emit(b, a); + } + var c = this, + d = this.state; + qb > d && b(c.options.event + Z(d)), + b(c.options.event), + a.additionalEvent && b(a.additionalEvent), + d >= qb && b(c.options.event + Z(d)); + }, + tryEmit: function (a) { + return this.canEmit() ? this.emit(a) : void (this.state = tb); + }, + canEmit: function () { + for (var a = 0; a < this.requireFail.length; ) { + if (!(this.requireFail[a].state & (tb | nb))) return !1; + a++; + } + return !0; + }, + recognize: function (a) { + var b = la({}, a); + return k(this.options.enable, [this, b]) + ? (this.state & (rb | sb | tb) && (this.state = nb), + (this.state = this.process(b)), + void (this.state & (ob | pb | qb | sb) && this.tryEmit(b))) + : (this.reset(), void (this.state = tb)); + }, + process: function (a) {}, + getTouchAction: function () {}, + reset: function () {}, + }), + i(aa, Y, { + defaults: { pointers: 1 }, + attrTest: function (a) { + var b = this.options.pointers; + return 0 === b || a.pointers.length === b; + }, + process: function (a) { + var b = this.state, + c = a.eventType, + d = b & (ob | pb), + e = this.attrTest(a); + return d && (c & Ha || !e) + ? b | sb + : d || e + ? c & Ga + ? b | qb + : b & ob + ? b | pb + : ob + : tb; + }, + }), + i(ba, aa, { + defaults: { + event: "pan", + threshold: 10, + pointers: 1, + direction: Pa, + }, + getTouchAction: function () { + var a = this.options.direction, + b = []; + return a & Na && b.push(lb), a & Oa && b.push(kb), b; + }, + directionTest: function (a) { + var b = this.options, + c = !0, + d = a.distance, + e = a.direction, + f = a.deltaX, + g = a.deltaY; + return ( + e & b.direction || + (b.direction & Na + ? ((e = 0 === f ? Ia : 0 > f ? Ja : Ka), + (c = f != this.pX), + (d = Math.abs(a.deltaX))) + : ((e = 0 === g ? Ia : 0 > g ? La : Ma), + (c = g != this.pY), + (d = Math.abs(a.deltaY)))), + (a.direction = e), + c && d > b.threshold && e & b.direction + ); + }, + attrTest: function (a) { + return ( + aa.prototype.attrTest.call(this, a) && + (this.state & ob || + (!(this.state & ob) && this.directionTest(a))) + ); + }, + emit: function (a) { + (this.pX = a.deltaX), (this.pY = a.deltaY); + var b = $(a.direction); + b && (a.additionalEvent = this.options.event + b), + this._super.emit.call(this, a); + }, + }), + i(ca, aa, { + defaults: { event: "pinch", threshold: 0, pointers: 2 }, + getTouchAction: function () { + return [jb]; + }, + attrTest: function (a) { + return ( + this._super.attrTest.call(this, a) && + (Math.abs(a.scale - 1) > this.options.threshold || + this.state & ob) + ); + }, + emit: function (a) { + if (1 !== a.scale) { + var b = a.scale < 1 ? "in" : "out"; + a.additionalEvent = this.options.event + b; + } + this._super.emit.call(this, a); + }, + }), + i(da, Y, { + defaults: { event: "press", pointers: 1, time: 251, threshold: 9 }, + getTouchAction: function () { + return [hb]; + }, + process: function (a) { + var b = this.options, + c = a.pointers.length === b.pointers, + d = a.distance < b.threshold, + f = a.deltaTime > b.time; + if ( + ((this._input = a), + !d || !c || (a.eventType & (Ga | Ha) && !f)) + ) + this.reset(); + else if (a.eventType & Ea) + this.reset(), + (this._timer = e( + function () { + (this.state = rb), this.tryEmit(); + }, + b.time, + this + )); + else if (a.eventType & Ga) return rb; + return tb; + }, + reset: function () { + clearTimeout(this._timer); + }, + emit: function (a) { + this.state === rb && + (a && a.eventType & Ga + ? this.manager.emit(this.options.event + "up", a) + : ((this._input.timeStamp = ra()), + this.manager.emit(this.options.event, this._input))); + }, + }), + i(ea, aa, { + defaults: { event: "rotate", threshold: 0, pointers: 2 }, + getTouchAction: function () { + return [jb]; + }, + attrTest: function (a) { + return ( + this._super.attrTest.call(this, a) && + (Math.abs(a.rotation) > this.options.threshold || + this.state & ob) + ); + }, + }), + i(fa, aa, { + defaults: { + event: "swipe", + threshold: 10, + velocity: 0.3, + direction: Na | Oa, + pointers: 1, + }, + getTouchAction: function () { + return ba.prototype.getTouchAction.call(this); + }, + attrTest: function (a) { + var b, + c = this.options.direction; + return ( + c & (Na | Oa) + ? (b = a.overallVelocity) + : c & Na + ? (b = a.overallVelocityX) + : c & Oa && (b = a.overallVelocityY), + this._super.attrTest.call(this, a) && + c & a.offsetDirection && + a.distance > this.options.threshold && + a.maxPointers == this.options.pointers && + qa(b) > this.options.velocity && + a.eventType & Ga + ); + }, + emit: function (a) { + var b = $(a.offsetDirection); + b && this.manager.emit(this.options.event + b, a), + this.manager.emit(this.options.event, a); + }, + }), + i(ga, Y, { + defaults: { + event: "tap", + pointers: 1, + taps: 1, + interval: 300, + time: 250, + threshold: 9, + posThreshold: 10, + }, + getTouchAction: function () { + return [ib]; + }, + process: function (a) { + var b = this.options, + c = a.pointers.length === b.pointers, + d = a.distance < b.threshold, + f = a.deltaTime < b.time; + if ((this.reset(), a.eventType & Ea && 0 === this.count)) + return this.failTimeout(); + if (d && f && c) { + if (a.eventType != Ga) return this.failTimeout(); + var g = this.pTime + ? a.timeStamp - this.pTime < b.interval + : !0, + h = + !this.pCenter || + H(this.pCenter, a.center) < b.posThreshold; + (this.pTime = a.timeStamp), + (this.pCenter = a.center), + h && g ? (this.count += 1) : (this.count = 1), + (this._input = a); + var i = this.count % b.taps; + if (0 === i) + return this.hasRequireFailures() + ? ((this._timer = e( + function () { + (this.state = rb), this.tryEmit(); + }, + b.interval, + this + )), + ob) + : rb; + } + return tb; + }, + failTimeout: function () { + return ( + (this._timer = e( + function () { + this.state = tb; + }, + this.options.interval, + this + )), + tb + ); + }, + reset: function () { + clearTimeout(this._timer); + }, + emit: function () { + this.state == rb && + ((this._input.tapCount = this.count), + this.manager.emit(this.options.event, this._input)); + }, + }), + (ha.VERSION = "2.0.8"), + (ha.defaults = { + domEvents: !1, + touchAction: gb, + enable: !0, + inputTarget: null, + inputClass: null, + preset: [ + [ea, { enable: !1 }], + [ca, { enable: !1 }, ["rotate"]], + [fa, { direction: Na }], + [ba, { direction: Na }, ["swipe"]], + [ga], + [ga, { event: "doubletap", taps: 2 }, ["tap"]], + [da], + ], + cssProps: { + userSelect: "none", + touchSelect: "none", + touchCallout: "none", + contentZooming: "none", + userDrag: "none", + tapHighlightColor: "rgba(0,0,0,0)", + }, + }); + var ub = 1, + vb = 2; + (ia.prototype = { + set: function (a) { + return ( + la(this.options, a), + a.touchAction && this.touchAction.update(), + a.inputTarget && + (this.input.destroy(), + (this.input.target = a.inputTarget), + this.input.init()), + this + ); + }, + stop: function (a) { + this.session.stopped = a ? vb : ub; + }, + recognize: function (a) { + var b = this.session; + if (!b.stopped) { + this.touchAction.preventDefaults(a); + var c, + d = this.recognizers, + e = b.curRecognizer; + (!e || (e && e.state & rb)) && (e = b.curRecognizer = null); + for (var f = 0; f < d.length; ) + (c = d[f]), + b.stopped === vb || + (e && c != e && !c.canRecognizeWith(e)) + ? c.reset() + : c.recognize(a), + !e && + c.state & (ob | pb | qb) && + (e = b.curRecognizer = c), + f++; + } + }, + get: function (a) { + if (a instanceof Y) return a; + for (var b = this.recognizers, c = 0; c < b.length; c++) + if (b[c].options.event == a) return b[c]; + return null; + }, + add: function (a) { + if (f(a, "add", this)) return this; + var b = this.get(a.options.event); + return ( + b && this.remove(b), + this.recognizers.push(a), + (a.manager = this), + this.touchAction.update(), + a + ); + }, + remove: function (a) { + if (f(a, "remove", this)) return this; + if ((a = this.get(a))) { + var b = this.recognizers, + c = r(b, a); + -1 !== c && (b.splice(c, 1), this.touchAction.update()); + } + return this; + }, + on: function (a, b) { + if (a !== d && b !== d) { + var c = this.handlers; + return ( + g(q(a), function (a) { + (c[a] = c[a] || []), c[a].push(b); + }), + this + ); + } + }, + off: function (a, b) { + if (a !== d) { + var c = this.handlers; + return ( + g(q(a), function (a) { + b ? c[a] && c[a].splice(r(c[a], b), 1) : delete c[a]; + }), + this + ); + } + }, + emit: function (a, b) { + this.options.domEvents && ka(a, b); + var c = this.handlers[a] && this.handlers[a].slice(); + if (c && c.length) { + (b.type = a), + (b.preventDefault = function () { + b.srcEvent.preventDefault(); + }); + for (var d = 0; d < c.length; ) c[d](b), d++; + } + }, + destroy: function () { + this.element && ja(this, !1), + (this.handlers = {}), + (this.session = {}), + this.input.destroy(), + (this.element = null); + }, + }), + la(ha, { + INPUT_START: Ea, + INPUT_MOVE: Fa, + INPUT_END: Ga, + INPUT_CANCEL: Ha, + STATE_POSSIBLE: nb, + STATE_BEGAN: ob, + STATE_CHANGED: pb, + STATE_ENDED: qb, + STATE_RECOGNIZED: rb, + STATE_CANCELLED: sb, + STATE_FAILED: tb, + DIRECTION_NONE: Ia, + DIRECTION_LEFT: Ja, + DIRECTION_RIGHT: Ka, + DIRECTION_UP: La, + DIRECTION_DOWN: Ma, + DIRECTION_HORIZONTAL: Na, + DIRECTION_VERTICAL: Oa, + DIRECTION_ALL: Pa, + Manager: ia, + Input: x, + TouchAction: V, + TouchInput: P, + MouseInput: L, + PointerEventInput: M, + TouchMouseInput: R, + SingleTouchInput: N, + Recognizer: Y, + AttrRecognizer: aa, + Tap: ga, + Pan: ba, + Swipe: fa, + Pinch: ca, + Rotate: ea, + Press: da, + on: m, + off: n, + each: g, + merge: ta, + extend: sa, + assign: la, + inherit: i, + bindFn: j, + prefixed: u, + }); + var wb = + "undefined" != typeof a ? a : "undefined" != typeof self ? self : {}; + (wb.Hammer = ha), + "function" == typeof define && define.amd + ? define(function () { + return ha; + }) + : "undefined" != typeof module && module.exports + ? (module.exports = ha) + : (a[c] = ha); +})(window, document, "Hammer"); +//# sourceMappingURL=hammer.min.js.map diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 0a907ed2a..46c9c1da9 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -24,33 +24,114 @@ var reader; $("#bookmark, #show-Bookmarks").remove(); } - // Enable swipe support - // I have no idea why swiperRight/swiperLeft from plugins is not working, events just don't get fired - var touchStart = 0; - var touchEnd = 0; + // Navigation mode: 'sides' or 'gestures' + var hammerManagers = []; - reader.rendition.on("touchstart", function (event) { - touchStart = event.changedTouches[0].screenX; - }); - reader.rendition.on("touchend", function (event) { - touchEnd = event.changedTouches[0].screenX; - if (touchStart < touchEnd) { - if (reader.book.package.metadata.direction === "rtl") { - reader.rendition.next(); - } else { - reader.rendition.prev(); - } - // Swiped Right + function hasActiveSelection(win) { + try { + var sel = win.getSelection && win.getSelection(); + return sel && sel.type === "Range" && sel.toString().length > 0; + } catch (e) { + return false; } - if (touchStart > touchEnd) { - if (reader.book.package.metadata.direction === "rtl") { - reader.rendition.prev(); - } else { - reader.rendition.next(); - } - // Swiped Left + } + + function bindHammer(target, isIframeDoc) { + if (typeof Hammer === "undefined") { + return; } - }); + var mc = new Hammer(target); + mc.get("swipe").set({ + direction: Hammer.DIRECTION_HORIZONTAL, + threshold: 25, + velocity: 0.3, + }); + mc.on("swipeleft swiperight", function (ev) { + if (!window.cwGesturesEnabled) return; + if (ev.pointers && ev.pointers.length > 1) return; // ignore multi-touch + var win = isIframeDoc ? target.defaultView : window; + if (hasActiveSelection(win)) return; // do not navigate when selecting text + // Mapping per requirement: L->R = PREV, R->L = NEXT (independent of RTL) + if (ev.type === "swipeleft") reader.rendition.next(); + else reader.rendition.prev(); + }); + hammerManagers.push(mc); + } + + function destroyHammers() { + while (hammerManagers.length) { + var mc = hammerManagers.pop(); + try { + mc.destroy(); + } catch (e) {} + } + } + + function enableSideClicks() { + var prevBtn = document.getElementById("prev"); + var nextBtn = document.getElementById("next"); + if (prevBtn) { + prevBtn.style.display = ""; + prevBtn.onclick = function () { + reader.rendition.prev(); + }; + } + if (nextBtn) { + nextBtn.style.display = ""; + nextBtn.onclick = function () { + reader.rendition.next(); + }; + } + } + + function disableSideClicks() { + var prevBtn = document.getElementById("prev"); + var nextBtn = document.getElementById("next"); + if (prevBtn) { + prevBtn.onclick = null; + prevBtn.style.display = "none"; + } + if (nextBtn) { + nextBtn.onclick = null; + nextBtn.style.display = "none"; + } + } + + function enableGestures() { + // Bind to outer container + if (reader && reader.rendition && reader.rendition.container) { + bindHammer(reader.rendition.container, false); + } + // Bind to inner iframes when rendered + reader.rendition.on("rendered", function (section, contents) { + var docEl = contents.document; + if (docEl && docEl.documentElement && docEl.documentElement.style) { + docEl.documentElement.style.touchAction = "pan-y"; + } + bindHammer(docEl, true); + }); + } + + window.applyNavigationMode = function (mode) { + if (!window.cwInitialized) { + destroyHammers(); + enableGestures(); + window.cwInitialized = true; + } + + if (mode === "sides") { + enableSideClicks(); + window.cwGesturesEnabled = false; + } else { + disableSideClicks(); + window.cwGesturesEnabled = true; + } + }; + + // Apply saved or default mode on load + var savedMode = + localStorage.getItem("calibre.reader.navMode") || "gestures"; + window.applyNavigationMode(savedMode); // Update progress percentage let progressDiv = document.getElementById("progress"); diff --git a/cps/templates/read.html b/cps/templates/read.html index 6262f403b..690d60a56 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -157,6 +157,19 @@ />{{_('Reflow text when sidebars are open.')}}

    +
    + From 6d6d5ac097e146d83d29ba5348dcf8122cb05b78 Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Wed, 29 Oct 2025 22:06:09 +0100 Subject: [PATCH 09/19] add books button, add page count on bottom and in settings, add swipe for closing sidebar, fix settings modal, fix few styles --- cps/static/css/main.css | 4 +-- cps/static/css/reader.css | 24 +++++++++++++++++- cps/static/js/reading/epub.js | 48 +++++++++++++++++++++++++++++++++-- cps/templates/read.html | 44 +++++++++++++++++++++++++++++--- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/cps/static/css/main.css b/cps/static/css/main.css index fcb00eed6..feb105244 100644 --- a/cps/static/css/main.css +++ b/cps/static/css/main.css @@ -539,7 +539,7 @@ input:-moz-placeholder { left: 50%; transform: translate(-50%, -50%); width: 100vw; - height: auto; + height: 90dvh; max-width: 630px; z-index: 2000; visibility: hidden; @@ -580,7 +580,7 @@ input:-moz-placeholder { } .md-content > div { - padding: 15px 40px 30px; + padding: 10px 20px; margin: 0; font-weight: 300; font-size: 14px; diff --git a/cps/static/css/reader.css b/cps/static/css/reader.css index ac943460f..6294353d6 100644 --- a/cps/static/css/reader.css +++ b/cps/static/css/reader.css @@ -47,6 +47,28 @@ } #main, -#viewer { +#viewer, +#sidebar, +#tocView { touch-action: pan-y; } + +.read-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 12px; +} +.read-footer #pages-count { + font-size: 12px; + + position: absolute; + left: 12px; + bottom: 2px; +} + +#back-button { + width: 50px !important; + right: 50px; + position: absolute; +} diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 46c9c1da9..9f906674e 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -102,6 +102,24 @@ var reader; if (reader && reader.rendition && reader.rendition.container) { bindHammer(reader.rendition.container, false); } + // Bind swipe on sidebar to close/open main + try { + var sidebarEl = document.getElementById("tocView"); + if (sidebarEl && typeof Hammer !== "undefined") { + var sidebarMc = new Hammer(sidebarEl); + sidebarMc.get("swipe").set({ + direction: Hammer.DIRECTION_HORIZONTAL, + threshold: 25, + velocity: 0.3, + }); + sidebarMc.on("swipeleft swiperight", function (ev) { + if ($("#sidebar").hasClass("open")) { + $("#slider").click(); + } + }); + hammerManagers.push(sidebarMc); + } + } catch (e) {} // Bind to inner iframes when rendered reader.rendition.on("rendered", function (section, contents) { var docEl = contents.document; @@ -129,12 +147,23 @@ var reader; }; // Apply saved or default mode on load - var savedMode = - localStorage.getItem("calibre.reader.navMode") || "gestures"; + var savedMode = localStorage.getItem("calibre.reader.navMode") || "sizes"; window.applyNavigationMode(savedMode); // Update progress percentage let progressDiv = document.getElementById("progress"); + // Pages counter (virtual pages via EPUB locations) + let pagesDiv = document.getElementById("pages-count"); + // Honor saved visibility preference for pages counter + (function () { + try { + var pref = localStorage.getItem("calibre.reader.showPages"); + var show = pref === null ? true : pref === "true"; + if (pagesDiv) + pagesDiv.style.visibility = show ? "visible" : "hidden"; + } catch (e) {} + })(); + reader.book.ready.then(() => { let locations_key = reader.book.key() + "-locations"; let stored_locations = localStorage.getItem(locations_key); @@ -159,6 +188,21 @@ var reader; reader.rendition.on("relocated", (location) => { let percentage = Math.round(location.end.percentage * 100); progressDiv.textContent = percentage + "%"; + + // Pages based on generated EPUB locations (CFI positions) + const cfi = location.start.cfi; + const current = + reader.book.locations.locationFromCfi(cfi) || 0; // 1-based index typically + const total = reader.book.locations.length() || 0; + const remaining = Math.max(total - current, 0); + + if (total > 0) { + pagesDiv.textContent = current + "/" + total; + pagesDiv.style.visibility = "visible"; + } else { + pagesDiv.textContent = ""; + pagesDiv.style.visibility = "hidden"; + } }); reader.rendition.reportLocation(); progressDiv.style.visibility = "visible"; diff --git a/cps/templates/read.html b/cps/templates/read.html index 690d60a56..5d21feb12 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -57,6 +57,13 @@ data-view="Bookmarks" >Bookmarks + + Books +
    @@ -95,7 +102,10 @@
    -
    0%
    +
    @@ -167,9 +177,18 @@ id="navGestures" onclick="selectNavMode('gestures')" > - {{_('Gestures')}} + {{_('Gestures (for mobile)')}}
    +
    +

    + {{_('Show pages count')}} +

    +
    From 458d0d1e3db9aa03231046277e5f9749e7fbf7e3 Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Wed, 29 Oct 2025 22:10:08 +0100 Subject: [PATCH 10/19] feat(read.html, epub): add custom theme selection with color picker, used pickr.js --- cps/static/css/main.css | 4 +- cps/static/css/reader.css | 4 +- cps/static/js/libs/pickr/pickr.min.css | 2 + cps/static/js/libs/pickr/pickr.min.js | 3 + cps/templates/read.html | 258 ++++++++++++++++++++++++- 5 files changed, 257 insertions(+), 14 deletions(-) create mode 100644 cps/static/js/libs/pickr/pickr.min.css create mode 100644 cps/static/js/libs/pickr/pickr.min.js diff --git a/cps/static/css/main.css b/cps/static/css/main.css index feb105244..f590eda3a 100644 --- a/cps/static/css/main.css +++ b/cps/static/css/main.css @@ -538,7 +538,7 @@ input:-moz-placeholder { top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 100vw; + width: 97vw; height: 90dvh; max-width: 630px; z-index: 2000; @@ -565,7 +565,7 @@ input:-moz-placeholder { } .md-show ~ .overlay { - opacity: 1; + /* opacity: 1; */ visibility: visible; } diff --git a/cps/static/css/reader.css b/cps/static/css/reader.css index 6294353d6..4db589e9d 100644 --- a/cps/static/css/reader.css +++ b/cps/static/css/reader.css @@ -29,8 +29,8 @@ } .item { - font-size: 20px; - font-weight: 400; + font-size: 14px; + font-weight: 600; font-family: Open Sans; padding-right: 10px; color: white; diff --git a/cps/static/js/libs/pickr/pickr.min.css b/cps/static/js/libs/pickr/pickr.min.css new file mode 100644 index 000000000..e4ccfd28f --- /dev/null +++ b/cps/static/js/libs/pickr/pickr.min.css @@ -0,0 +1,2 @@ +/*! Pickr 1.9.1 MIT | https://github.com/Simonwep/pickr */ +.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;border-radius:.15em;background:url("data:image/svg+xml;utf8, ") no-repeat center;background-size:0;transition:all .3s}.pickr .pcr-button::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pickr .pcr-button::before{z-index:initial}.pickr .pcr-button::after{position:absolute;content:"";top:0;left:0;height:100%;width:100%;transition:background .3s;background:var(--pcr-color);border-radius:.15em}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear::before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-button.disabled{cursor:not-allowed}.pickr *,.pcr-app *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr input:focus,.pickr input.pcr-active,.pickr button:focus,.pickr button.pcr-active,.pcr-app input:focus,.pcr-app input.pcr-active,.pcr-app button:focus,.pcr-app button.pcr-active{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px var(--pcr-color)}.pickr .pcr-palette,.pickr .pcr-slider,.pcr-app .pcr-palette,.pcr-app .pcr-slider{transition:box-shadow .3s}.pickr .pcr-palette:focus,.pickr .pcr-slider:focus,.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(0,0,0,.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity .3s,visibility 0s .3s;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Helvetica Neue",Arial,sans-serif;box-shadow:0 .15em 1.5em 0 rgba(0,0,0,.1),0 0 1em 0 rgba(0,0,0,.03);left:0;top:0}.pcr-app.visible{transition:opacity .3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports(display: grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit, 1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all .15s;overflow:hidden;background:rgba(0,0,0,0);z-index:1}.pcr-app .pcr-swatches>button::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button::after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:var(--pcr-color);border:1px solid rgba(0,0,0,.05);border-radius:.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -0.2em 0 -0.2em}.pcr-app .pcr-interaction>*{margin:0 .2em}.pcr-app .pcr-interaction input{letter-spacing:.07em;font-size:.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all .15s;padding:.45em .5em;margin-top:.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(0.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(66,133,244,.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all .2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff;width:auto}.pcr-app .pcr-interaction .pcr-save,.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{color:#fff}.pcr-app .pcr-interaction .pcr-save:hover,.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover{filter:brightness(0.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-cancel{background:#f44250}.pcr-app .pcr-interaction .pcr-clear:focus,.pcr-app .pcr-interaction .pcr-cancel:focus{box-shadow:0 0 0 1px rgba(255,255,255,.85),0 0 0 3px rgba(244,66,80,.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-palette,.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity{position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-palette:active,.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme=classic]{width:28.5em;max-width:95vw;padding:.8em}.pcr-app[data-theme=classic] .pcr-selection{display:flex;justify-content:space-between;flex-grow:1}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview{position:relative;z-index:1;width:2em;display:flex;flex-direction:column;justify-content:space-between;margin-right:.75em}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview .pcr-last-color{cursor:pointer;border-radius:.15em .15em 0 0;z-index:2}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview .pcr-current-color{border-radius:0 0 .15em .15em}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview .pcr-last-color,.pcr-app[data-theme=classic] .pcr-selection .pcr-color-preview .pcr-current-color{background:var(--pcr-color);width:100%;height:50%}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-palette{width:100%;height:8em;z-index:1}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-palette .pcr-palette{flex-grow:1;border-radius:.15em}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-palette .pcr-palette::before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url("data:image/svg+xml;utf8, ");background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme=classic] .pcr-selection .pcr-color-opacity{margin-left:.75em}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme=classic] .pcr-selection .pcr-color-opacity .pcr-picker{left:50%;transform:translateX(-50%)}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme=classic] .pcr-selection .pcr-color-opacity .pcr-slider{width:8px;flex-grow:1;border-radius:50em}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(to bottom, hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(0, 100%, 50%))}.pcr-app[data-theme=classic] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(to bottom, transparent, black),url("data:image/svg+xml;utf8, ");background-size:100%,50%} diff --git a/cps/static/js/libs/pickr/pickr.min.js b/cps/static/js/libs/pickr/pickr.min.js new file mode 100644 index 000000000..5fa877d29 --- /dev/null +++ b/cps/static/js/libs/pickr/pickr.min.js @@ -0,0 +1,3 @@ +/*! Pickr 1.9.1 MIT | https://github.com/Simonwep/pickr */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Pickr=e():t.Pickr=e()}(self,(()=>(()=>{"use strict";var t={d:(e,o)=>{for(var n in o)t.o(o,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:o[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.d(e,{default:()=>E});var o={};function n(t,e,o,n,i={}){e instanceof HTMLCollection||e instanceof NodeList?e=Array.from(e):Array.isArray(e)||(e=[e]),Array.isArray(o)||(o=[o]);for(const s of e)for(const e of o)s[t](e,n,{capture:!1,...i});return Array.prototype.slice.call(arguments,1)}t.r(o),t.d(o,{adjustableInputNumbers:()=>p,createElementFromString:()=>r,createFromTemplate:()=>a,eventPath:()=>l,off:()=>s,on:()=>i,resolveElement:()=>c});const i=n.bind(null,"addEventListener"),s=n.bind(null,"removeEventListener");function r(t){const e=document.createElement("div");return e.innerHTML=t.trim(),e.firstElementChild}function a(t){const e=(t,e)=>{const o=t.getAttribute(e);return t.removeAttribute(e),o},o=(t,n={})=>{const i=e(t,":obj"),s=e(t,":ref"),r=i?n[i]={}:n;s&&(n[s]=t);for(const n of Array.from(t.children)){const t=e(n,":arr"),i=o(n,t?{}:r);t&&(r[t]||(r[t]=[])).push(Object.keys(i).length?i:n)}return n};return o(r(t))}function l(t){let e=t.path||t.composedPath&&t.composedPath();if(e)return e;let o=t.target.parentElement;for(e=[t.target,o];o=o.parentElement;)e.push(o);return e.push(document,window),e}function c(t){return t instanceof Element?t:"string"==typeof t?t.split(/>>/g).reduce(((t,e,o,n)=>(t=t.querySelector(e),ot)){function o(o){const n=[.001,.01,.1][Number(o.shiftKey||2*o.ctrlKey)]*(o.deltaY<0?1:-1);let i=0,s=t.selectionStart;t.value=t.value.replace(/[\d.]+/g,((t,o)=>o<=s&&o+t.length>=s?(s=o,e(Number(t),n,i)):(i++,t))),t.focus(),t.setSelectionRange(s,s),o.preventDefault(),t.dispatchEvent(new Event("input"))}i(t,"focus",(()=>i(window,"wheel",o,{passive:!1}))),i(t,"blur",(()=>s(window,"wheel",o)))}const{min:u,max:h,floor:d,round:m}=Math;function f(t,e,o){e/=100,o/=100;const n=d(t=t/360*6),i=t-n,s=o*(1-e),r=o*(1-i*e),a=o*(1-(1-i)*e),l=n%6;return[255*[o,r,s,s,a,o][l],255*[a,o,o,r,s,s][l],255*[s,s,a,o,o,r][l]]}function v(t,e,o){const n=(2-(e/=100))*(o/=100)/2;return 0!==n&&(e=1===n?0:n<.5?e*o/(2*n):e*o/(2-2*n)),[t,100*e,100*n]}function b(t,e,o){const n=u(t/=255,e/=255,o/=255),i=h(t,e,o),s=i-n;let r,a;if(0===s)r=a=0;else{a=s/i;const n=((i-t)/6+s/2)/s,l=((i-e)/6+s/2)/s,c=((i-o)/6+s/2)/s;t===i?r=c-l:e===i?r=1/3+n-c:o===i&&(r=2/3+l-n),r<0?r+=1:r>1&&(r-=1)}return[360*r,100*a,100*i]}function y(t,e,o,n){e/=100,o/=100;return[...b(255*(1-u(1,(t/=100)*(1-(n/=100))+n)),255*(1-u(1,e*(1-n)+n)),255*(1-u(1,o*(1-n)+n)))]}function g(t,e,o){e/=100;const n=2*(e*=(o/=100)<.5?o:1-o)/(o+e)*100,i=100*(o+e);return[t,isNaN(n)?0:n,i]}function _(t){return b(...t.match(/.{2}/g).map((t=>parseInt(t,16))))}function w(t){t=t.match(/^[a-zA-Z]+$/)?function(t){if("black"===t.toLowerCase())return"#000";const e=document.createElement("canvas").getContext("2d");return e.fillStyle=t,"#000"===e.fillStyle?null:e.fillStyle}(t):t;const e={cmyk:/^cmyk\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)/i,rgba:/^rgba?\D+([\d.]+)(%?)\D+([\d.]+)(%?)\D+([\d.]+)(%?)\D*?(([\d.]+)(%?)|$)/i,hsla:/^hsla?\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D*?(([\d.]+)(%?)|$)/i,hsva:/^hsva?\D+([\d.]+)\D+([\d.]+)\D+([\d.]+)\D*?(([\d.]+)(%?)|$)/i,hexa:/^#?(([\dA-Fa-f]{3,4})|([\dA-Fa-f]{6})|([\dA-Fa-f]{8}))$/i},o=t=>t.map((t=>/^(|\d+)\.\d+|\d+$/.test(t)?Number(t):void 0));let n;t:for(const i in e)if(n=e[i].exec(t))switch(i){case"cmyk":{const[,t,e,s,r]=o(n);if(t>100||e>100||s>100||r>100)break t;return{values:y(t,e,s,r),type:i}}case"rgba":{let[,t,,e,,s,,,r]=o(n);if(t="%"===n[2]?t/100*255:t,e="%"===n[4]?e/100*255:e,s="%"===n[6]?s/100*255:s,r="%"===n[9]?r/100:r,t>255||e>255||s>255||r<0||r>1)break t;return{values:[...b(t,e,s),r],a:r,type:i}}case"hexa":{let[,t]=n;4!==t.length&&3!==t.length||(t=t.split("").map((t=>t+t)).join(""));const e=t.substring(0,6);let o=t.substring(6);return o=o?parseInt(o,16)/255:void 0,{values:[..._(e),o],a:o,type:i}}case"hsla":{let[,t,e,s,,r]=o(n);if(r="%"===n[6]?r/100:r,t>360||e>100||s>100||r<0||r>1)break t;return{values:[...g(t,e,s),r],a:r,type:i}}case"hsva":{let[,t,e,s,,r]=o(n);if(r="%"===n[6]?r/100:r,t>360||e>100||s>100||r<0||r>1)break t;return{values:[t,e,s,r],a:r,type:i}}}return{values:null,type:null}}function A(t=0,e=0,o=0,n=1){const i=(t,e)=>(o=-1)=>e(~o?t.map((t=>Number(t.toFixed(o)))):t),s={h:t,s:e,v:o,a:n,toHSVA(){const t=[s.h,s.s,s.v,s.a];return t.toString=i(t,(t=>`hsva(${t[0]}, ${t[1]}%, ${t[2]}%, ${s.a})`)),t},toHSLA(){const t=[...v(s.h,s.s,s.v),s.a];return t.toString=i(t,(t=>`hsla(${t[0]}, ${t[1]}%, ${t[2]}%, ${s.a})`)),t},toRGBA(){const t=[...f(s.h,s.s,s.v),s.a];return t.toString=i(t,(t=>`rgba(${t[0]}, ${t[1]}, ${t[2]}, ${s.a})`)),t},toCMYK(){const t=function(t,e,o){const n=f(t,e,o),i=n[0]/255,s=n[1]/255,r=n[2]/255,a=u(1-i,1-s,1-r);return[100*(1===a?0:(1-i-a)/(1-a)),100*(1===a?0:(1-s-a)/(1-a)),100*(1===a?0:(1-r-a)/(1-a)),100*a]}(s.h,s.s,s.v);return t.toString=i(t,(t=>`cmyk(${t[0]}%, ${t[1]}%, ${t[2]}%, ${t[3]}%)`)),t},toHEXA(){const t=function(t,e,o){return f(t,e,o).map((t=>m(t).toString(16).padStart(2,"0")))}(s.h,s.s,s.v),e=s.a>=1?"":Number((255*s.a).toFixed(0)).toString(16).toUpperCase().padStart(2,"0");return e&&t.push(e),t.toString=()=>`#${t.join("").toUpperCase()}`,t},clone:()=>A(s.h,s.s,s.v,s.a)};return s}const $=t=>Math.max(Math.min(t,1),0);function C(t){const e={options:Object.assign({lock:null,onchange:()=>0,onstop:()=>0},t),_keyboard(t){const{options:o}=e,{type:n,key:i}=t;if(document.activeElement===o.wrapper){const{lock:o}=e.options,s="ArrowUp"===i,r="ArrowRight"===i,a="ArrowDown"===i,l="ArrowLeft"===i;if("keydown"===n&&(s||r||a||l)){let n=0,i=0;"v"===o?n=s||r?1:-1:"h"===o?n=s||r?-1:1:(i=s?-1:a?1:0,n=l?-1:r?1:0),e.update($(e.cache.x+.01*n),$(e.cache.y+.01*i)),t.preventDefault()}else i.startsWith("Arrow")&&(e.options.onstop(),t.preventDefault())}},_tapstart(t){i(document,["mouseup","touchend","touchcancel"],e._tapstop),i(document,["mousemove","touchmove"],e._tapmove),t.cancelable&&t.preventDefault(),e._tapmove(t)},_tapmove(t){const{options:o,cache:n}=e,{lock:i,element:s,wrapper:r}=o,a=r.getBoundingClientRect();let l=0,c=0;if(t){const e=t&&t.touches&&t.touches[0];l=t?(e||t).clientX:0,c=t?(e||t).clientY:0,la.left+a.width&&(l=a.left+a.width),ca.top+a.height&&(c=a.top+a.height),l-=a.left,c-=a.top}else n&&(l=n.x*a.width,c=n.y*a.height);"h"!==i&&(s.style.left=`calc(${l/a.width*100}% - ${s.offsetWidth/2}px)`),"v"!==i&&(s.style.top=`calc(${c/a.height*100}% - ${s.offsetHeight/2}px)`),e.cache={x:l/a.width,y:c/a.height};const p=$(l/a.width),u=$(c/a.height);switch(i){case"v":return o.onchange(p);case"h":return o.onchange(u);default:return o.onchange(p,u)}},_tapstop(){e.options.onstop(),s(document,["mouseup","touchend","touchcancel"],e._tapstop),s(document,["mousemove","touchmove"],e._tapmove)},trigger(){e._tapmove()},update(t=0,o=0){const{left:n,top:i,width:s,height:r}=e.options.wrapper.getBoundingClientRect();"h"===e.options.lock&&(o=t),e._tapmove({clientX:n+s*t,clientY:i+r*o})},destroy(){const{options:t,_tapstart:o,_keyboard:n}=e;s(document,["keydown","keyup"],n),s([t.wrapper,t.element],"mousedown",o),s([t.wrapper,t.element],"touchstart",o,{passive:!1})}},{options:o,_tapstart:n,_keyboard:r}=e;return i([o.wrapper,o.element],"mousedown",n),i([o.wrapper,o.element],"touchstart",n,{passive:!1}),i(document,["keydown","keyup"],r),e}function k(t={}){t=Object.assign({onchange:()=>0,className:"",elements:[]},t);const e=i(t.elements,"click",(e=>{t.elements.forEach((o=>o.classList[e.target===o?"add":"remove"](t.className))),t.onchange(e),e.stopPropagation()}));return{destroy:()=>s(...e)}}const S={variantFlipOrder:{start:"sme",middle:"mse",end:"ems"},positionFlipOrder:{top:"tbrl",right:"rltb",bottom:"btrl",left:"lrbt"},position:"bottom",margin:8,padding:0},O=(t,e,o)=>{const n="object"!=typeof t||t instanceof HTMLElement?{reference:t,popper:e,...o}:t;return{update(t=n){const{reference:e,popper:o}=Object.assign(n,t);if(!o||!e)throw new Error("Popper- or reference-element missing.");return((t,e,o)=>{const{container:n,arrow:i,margin:s,padding:r,position:a,variantFlipOrder:l,positionFlipOrder:c}={container:document.documentElement.getBoundingClientRect(),...S,...o},{left:p,top:u}=e.style;e.style.left="0",e.style.top="0";const h=t.getBoundingClientRect(),d=e.getBoundingClientRect(),m={t:h.top-d.height-s,b:h.bottom+s,r:h.right+s,l:h.left-d.width-s},f={vs:h.left,vm:h.left+h.width/2-d.width/2,ve:h.left+h.width-d.width,hs:h.top,hm:h.bottom-h.height/2-d.height/2,he:h.bottom-d.height},[v,b="middle"]=a.split("-"),y=c[v],g=l[b],{top:_,left:w,bottom:A,right:$}=n;for(const t of y){const o="t"===t||"b"===t;let n=m[t];const[s,a]=o?["top","left"]:["left","top"],[l,c]=o?[d.height,d.width]:[d.width,d.height],[p,u]=o?[A,$]:[$,A],[v,b]=o?[_,w]:[w,_];if(!(np))for(const p of g){let m=f[(o?"v":"h")+p];if(!(mu)){if(m-=d[a],n-=d[s],e.style[a]=`${m}px`,e.style[s]=`${n}px`,i){const e=o?h.width/2:h.height/2,r=c/2,u=e>r,d=m+{s:u?r:e,m:r,e:u?r:c-e}[p],f=n+{t:l,b:0,r:0,l}[t];i.style[a]=`${d}px`,i.style[s]=`${f}px`}return t+p}}}return e.style.left=p,e.style.top=u,null})(e,o,n)}}};class E{static utils=o;static version="1.9.1";static I18N_DEFAULTS={"ui:dialog":"color picker dialog","btn:toggle":"toggle color picker dialog","btn:swatch":"color swatch","btn:last-color":"use previous color","btn:save":"Save","btn:cancel":"Cancel","btn:clear":"Clear","aria:btn:save":"save and close","aria:btn:cancel":"cancel and close","aria:btn:clear":"clear and close","aria:input":"color input field","aria:palette":"color selection area","aria:hue":"hue selection slider","aria:opacity":"selection slider"};static DEFAULT_OPTIONS={appClass:null,theme:"classic",useAsButton:!1,padding:8,disabled:!1,comparison:!0,closeOnScroll:!1,outputPrecision:0,lockOpacity:!1,autoReposition:!0,container:"body",components:{interaction:{}},i18n:{},swatches:null,inline:!1,sliders:null,default:"#42445a",defaultRepresentation:null,position:"bottom-middle",adjustableNumbers:!0,showAlways:!1,closeWithKey:"Escape"};_initializingActive=!0;_recalc=!0;_nanopop=null;_root=null;_color=A();_lastColor=A();_swatchColors=[];_setupAnimationFrame=null;_eventListener={init:[],save:[],hide:[],show:[],clear:[],change:[],changestop:[],cancel:[],swatchselect:[]};constructor(t){this.options=t=Object.assign({...E.DEFAULT_OPTIONS},t);const{swatches:e,components:o,theme:n,sliders:i,lockOpacity:s,padding:r}=t;["nano","monolith"].includes(n)&&!i&&(t.sliders="h"),o.interaction||(o.interaction={});const{preview:a,opacity:l,hue:c,palette:p}=o;o.opacity=!s&&l,o.palette=p||a||l||c,this._preBuild(),this._buildComponents(),this._bindEvents(),this._finalBuild(),e&&e.length&&e.forEach((t=>this.addSwatch(t)));const{button:u,app:h}=this._root;this._nanopop=O(u,h,{margin:r}),u.setAttribute("role","button"),u.setAttribute("aria-label",this._t("btn:toggle"));const d=this;this._setupAnimationFrame=requestAnimationFrame((function e(){if(!h.offsetWidth)return requestAnimationFrame(e);d.setColor(t.default),d._rePositioningPicker(),t.defaultRepresentation&&(d._representation=t.defaultRepresentation,d.setColorRepresentation(d._representation)),t.showAlways&&d.show(),d._initializingActive=!1,d._emit("init")}))}static create=t=>new E(t);_preBuild(){const{options:t}=this;for(const e of["el","container"])t[e]=c(t[e]);this._root=(t=>{const{components:e,useAsButton:o,inline:n,appClass:i,theme:s,lockOpacity:r}=t.options,l=t=>t?"":'style="display:none" hidden',c=e=>t._t(e),p=a(`\n
    \n\n ${o?"":''}\n\n
    \n
    \n
    \n \n
    \n
    \n\n
    \n
    \n
    \n
    \n\n
    \n
    \n
    \n
    \n\n
    \n
    \n
    \n
    \n
    \n\n
    \n\n
    \n \n\n \n \n \n \n \n\n \n \n \n
    \n
    \n
    \n `),u=p.interaction;return u.options.find((t=>!t.hidden&&!t.classList.add("active"))),u.type=()=>u.options.find((t=>t.classList.contains("active"))),p})(this),t.useAsButton&&(this._root.button=t.el),t.container.appendChild(this._root.root)}_finalBuild(){const t=this.options,e=this._root;if(t.container.removeChild(e.root),t.inline){const o=t.el.parentElement;t.el.nextSibling?o.insertBefore(e.app,t.el.nextSibling):o.appendChild(e.app)}else t.container.appendChild(e.app);t.useAsButton?t.inline&&t.el.remove():t.el.parentNode.replaceChild(e.root,t.el),t.disabled&&this.disable(),t.comparison||(e.button.style.transition="none",t.useAsButton||(e.preview.lastColor.style.transition="none")),this.hide()}_buildComponents(){const t=this,e=this.options.components,o=(t.options.sliders||"v").repeat(2),[n,i]=o.match(/^[vh]+$/g)?o:[],s=()=>this._color||(this._color=this._lastColor.clone()),r={palette:C({element:t._root.palette.picker,wrapper:t._root.palette.palette,onstop:()=>t._emit("changestop","slider",t),onchange(o,n){if(!e.palette)return;const i=s(),{_root:r,options:a}=t,{lastColor:l,currentColor:c}=r.preview;t._recalc&&(i.s=100*o,i.v=100-100*n,i.v<0&&(i.v=0),t._updateOutput("slider"));const p=i.toRGBA().toString(0);this.element.style.background=p,this.wrapper.style.background=`\n linear-gradient(to top, rgba(0, 0, 0, ${i.a}), transparent),\n linear-gradient(to left, hsla(${i.h}, 100%, 50%, ${i.a}), rgba(255, 255, 255, ${i.a}))\n `,a.comparison?a.useAsButton||t._lastColor||l.style.setProperty("--pcr-color",p):(r.button.style.setProperty("--pcr-color",p),r.button.classList.remove("clear"));const u=i.toHEXA().toString();for(const{el:e,color:o}of t._swatchColors)e.classList[u===o.toHEXA().toString()?"add":"remove"]("pcr-active");c.style.setProperty("--pcr-color",p)}}),hue:C({lock:"v"===i?"h":"v",element:t._root.hue.picker,wrapper:t._root.hue.slider,onstop:()=>t._emit("changestop","slider",t),onchange(o){if(!e.hue||!e.palette)return;const n=s();t._recalc&&(n.h=360*o),this.element.style.backgroundColor=`hsl(${n.h}, 100%, 50%)`,r.palette.trigger()}}),opacity:C({lock:"v"===n?"h":"v",element:t._root.opacity.picker,wrapper:t._root.opacity.slider,onstop:()=>t._emit("changestop","slider",t),onchange(o){if(!e.opacity||!e.palette)return;const n=s();t._recalc&&(n.a=Math.round(100*o)/100),this.element.style.background=`rgba(0, 0, 0, ${n.a})`,r.palette.trigger()}}),selectable:k({elements:t._root.interaction.options,className:"active",onchange(e){t._representation=e.target.getAttribute("data-type").toUpperCase(),t._recalc&&t._updateOutput("swatch")}})};this._components=r}_bindEvents(){const{_root:t,options:e}=this,o=[i(t.interaction.clear,"click",(()=>this._clearColor())),i([t.interaction.cancel,t.preview.lastColor],"click",(()=>{this.setHSVA(...(this._lastColor||this._color).toHSVA(),!0),this._emit("cancel")})),i(t.interaction.save,"click",(()=>{!this.applyColor()&&!e.showAlways&&this.hide()})),i(t.interaction.result,["keyup","input"],(t=>{this.setColor(t.target.value,!0)&&!this._initializingActive&&(this._emit("change",this._color,"input",this),this._emit("changestop","input",this)),t.stopImmediatePropagation()})),i(t.interaction.result,["focus","blur"],(t=>{this._recalc="blur"===t.type,this._recalc&&this._updateOutput(null)})),i([t.palette.palette,t.palette.picker,t.hue.slider,t.hue.picker,t.opacity.slider,t.opacity.picker],["mousedown","touchstart"],(()=>this._recalc=!0),{passive:!0})];if(!e.showAlways){const n=e.closeWithKey;o.push(i(t.button,"click",(()=>this.isOpen()?this.hide():this.show())),i(document,"keyup",(t=>this.isOpen()&&(t.key===n||t.code===n)&&this.hide())),i(document,["touchstart","mousedown"],(e=>{this.isOpen()&&!l(e).some((e=>e===t.app||e===t.button))&&this.hide()}),{capture:!0}))}if(e.adjustableNumbers){const e={rgba:[255,255,255,1],hsva:[360,100,100,1],hsla:[360,100,100,1],cmyk:[100,100,100,100]};p(t.interaction.result,((t,o,n)=>{const i=e[this.getColorRepresentation().toLowerCase()];if(i){const e=i[n],s=t+(e>=100?1e3*o:o);return s<=0?0:Number((s{n.isOpen()&&(e.closeOnScroll&&n.hide(),null===t?(t=setTimeout((()=>t=null),100),requestAnimationFrame((function e(){n._rePositioningPicker(),null!==t&&requestAnimationFrame(e)}))):(clearTimeout(t),t=setTimeout((()=>t=null),100)))}),{capture:!0}))}this._eventBindings=o}_rePositioningPicker(){const{options:t}=this;if(!t.inline){if(!this._nanopop.update({container:document.body.getBoundingClientRect(),position:t.position})){const t=this._root.app,e=t.getBoundingClientRect();t.style.top=(window.innerHeight-e.height)/2+"px",t.style.left=(window.innerWidth-e.width)/2+"px"}}}_updateOutput(t){const{_root:e,_color:o,options:n}=this;if(e.interaction.type()){const t=`to${e.interaction.type().getAttribute("data-type")}`;e.interaction.result.value="function"==typeof o[t]?o[t]().toString(n.outputPrecision):""}!this._initializingActive&&this._recalc&&this._emit("change",o,t,this)}_clearColor(t=!1){const{_root:e,options:o}=this;o.useAsButton||e.button.style.setProperty("--pcr-color","rgba(0, 0, 0, 0.15)"),e.button.classList.add("clear"),o.showAlways||this.hide(),this._lastColor=null,this._initializingActive||t||(this._emit("save",null),this._emit("clear"))}_parseLocalColor(t){const{values:e,type:o,a:n}=w(t),{lockOpacity:i}=this.options,s=void 0!==n&&1!==n;return e&&3===e.length&&(e[3]=void 0),{values:!e||i&&s?null:e,type:o}}_t(t){return this.options.i18n[t]||E.I18N_DEFAULTS[t]}_emit(t,...e){this._eventListener[t].forEach((t=>t(...e,this)))}on(t,e){return this._eventListener[t].push(e),this}off(t,e){const o=this._eventListener[t]||[],n=o.indexOf(e);return~n&&o.splice(n,1),this}addSwatch(t){const{values:e}=this._parseLocalColor(t);if(e){const{_swatchColors:t,_root:o}=this,n=A(...e),s=r(` + +
    + Custom: + +
    + +

    @@ -177,7 +202,7 @@ id="navGestures" onclick="selectNavMode('gestures')" > - {{_('Gestures (for mobile)')}} + {{_('Gestures (mobile)')}}

    @@ -272,6 +297,14 @@ useBookmarks: "{{ current_user.is_authenticated | tojson }}", }; + // load custom theme color from localStorage (if any) + var _savedCustomThemeColor = null; + try { + _savedCustomThemeColor = localStorage.getItem( + "calibre.reader.customTheme" + ); + } catch (e) {} + window.themes = { darkTheme: { bgColor: "#202124", @@ -291,33 +324,67 @@ amberTheme: { bgColor: "#e8bf5e", css_path: "{{ url_for('static', filename='css/epub_themes.css') }}", - "title-color": "#4f4f4f", + "title-color": "#2f2f2f", }, blackTheme: { bgColor: "black", css_path: "{{ url_for('static', filename='css/epub_themes.css') }}", "title-color": "#fff", }, + customTheme: { + bgColor: _savedCustomThemeColor || "#ffffff", + css_path: "{{ url_for('static', filename='css/epub_themes.css') }}", + "title-color": "#4f4f4f", + }, }; function selectTheme(id) { - let tickSpans = document + // Collect all span tick markers inside themes and clear them safely + var tickSpans = document .getElementById("themes") .querySelectorAll("span"); - - // Remove tick mark from all theme buttons tickSpans.forEach(function (tickSpan) { - document.getElementById(tickSpan.id).textContent = ""; + try { + tickSpan.textContent = ""; + } catch (e) {} }); - // Add tick mark to the button corresponding to the currently selected theme - document.getElementById(id).querySelector("span").textContent = "✓"; + // If the theme button exists, set its inner span to a tick. Otherwise set any span with the matching id. + var el = document.getElementById(id); + if (el) { + var sp = el.querySelector("span"); + if (sp) sp.textContent = "✓"; + } else { + var spById = + document.getElementById(id + "Selected") || + document.getElementById("customSelected"); + if (spById) spById.textContent = "✓"; + } // Saving theme to local storage localStorage.setItem("calibre.reader.theme", id); - // Apply theme to epubjs iframe - reader.rendition.themes.select(id); + // If selecting custom theme, ensure epubjs theme is registered with chosen bg color + if (id === "customTheme") { + var customColor = window.themes.customTheme.bgColor || "#ffffff"; + try { + if (reader && reader.rendition && reader.rendition.themes) { + reader.rendition.themes.register("customTheme", { + body: { + background: customColor, + }, + }); + reader.rendition.themes.select("customTheme"); + } + } catch (e) { + console.error("Failed to register/select customTheme", e); + } + } else { + // Apply theme to epubjs iframe + try { + reader.rendition.themes.select(id); + } catch (e) {} + } // Apply theme to rest of the page. document.getElementById("main").style.backgroundColor = @@ -432,6 +499,177 @@ } })(); + + + + + + From bba7812a07ac2b756365e40706de62135eafd67b Mon Sep 17 00:00:00 2001 From: IgorKurkov Date: Wed, 29 Oct 2025 22:20:08 +0100 Subject: [PATCH 11/19] feat(epub.js): add autosaving of current book reading position in localStorage to open this book on the last read point --- cps/static/js/reading/epub.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 9f906674e..d24a5e7c2 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -166,6 +166,8 @@ var reader; reader.book.ready.then(() => { let locations_key = reader.book.key() + "-locations"; + // Key to persist last-read position for this book in localStorage + let position_key = "calibre.reader.position." + reader.book.key(); let stored_locations = localStorage.getItem(locations_key); let make_locations, save_locations; if (stored_locations) { @@ -185,6 +187,22 @@ var reader; } make_locations .then(() => { + // Try to restore last position (CFI) from localStorage if present + try { + var _savedPos = localStorage.getItem(position_key); + if (_savedPos) { + try { + var _posObj = JSON.parse(_savedPos); + if (_posObj && _posObj.cfi) { + // Display the saved CFI location + try { + reader.rendition.display(_posObj.cfi); + } catch (e) {} + } + } catch (e) {} + } + } catch (e) {} + reader.rendition.on("relocated", (location) => { let percentage = Math.round(location.end.percentage * 100); progressDiv.textContent = percentage + "%"; @@ -194,7 +212,6 @@ var reader; const current = reader.book.locations.locationFromCfi(cfi) || 0; // 1-based index typically const total = reader.book.locations.length() || 0; - const remaining = Math.max(total - current, 0); if (total > 0) { pagesDiv.textContent = current + "/" + total; @@ -203,6 +220,18 @@ var reader; pagesDiv.textContent = ""; pagesDiv.style.visibility = "hidden"; } + + // Persist last position (CFI + percentage) to localStorage so reader can restore on next open + try { + var posObj = { + cfi: location.start.cfi, + percentage: location.start.percentage, + }; + localStorage.setItem( + position_key, + JSON.stringify(posObj) + ); + } catch (e) {} }); reader.rendition.reportLocation(); progressDiv.style.visibility = "visible"; From 310443afc5811d41a084ecfde41854c9341f4e75 Mon Sep 17 00:00:00 2001 From: mapi68 <41143572+mapi68@users.noreply.github.com> Date: Sun, 7 Dec 2025 04:08:44 +0100 Subject: [PATCH 12/19] Update italian --- cps/translations/it/LC_MESSAGES/messages.po | 438 ++++++++++++++------ 1 file changed, 315 insertions(+), 123 deletions(-) diff --git a/cps/translations/it/LC_MESSAGES/messages.po b/cps/translations/it/LC_MESSAGES/messages.po index 4ad52f13f..a036c6eda 100644 --- a/cps/translations/it/LC_MESSAGES/messages.po +++ b/cps/translations/it/LC_MESSAGES/messages.po @@ -8,15 +8,16 @@ msgstr "" "Project-Id-Version: Calibre-Web\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2025-12-06 11:51+0100\n" -"PO-Revision-Date: 2025-09-11 01:30+0200\n" +"PO-Revision-Date: 2025-12-07 04:05+0100\n" "Last-Translator: Massimo Pissarello \n" -"Language: it\n" "Language-Team: Italian <>\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: it\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.17.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Generated-By: Babel 2.13.1\n" +"X-Generator: Lokalize 25.11.90\n" #: cps/about.py:85 msgid "Statistics" @@ -39,8 +40,11 @@ msgid "Unknown command" msgstr "Comando sconosciuto" #: cps/admin.py:175 -msgid "Success! Books queued for Metadata Backup, please check Tasks for result" -msgstr "Tutto OK! Libri in coda per il backup dei metadati, controlla le attività per il risultato" +msgid "" +"Success! Books queued for Metadata Backup, please check Tasks for result" +msgstr "" +"Tutto OK! Libri in coda per il backup dei metadati, controlla le attività " +"per il risultato" #: cps/admin.py:208 cps/editbooks.py:808 cps/editbooks.py:852 #: cps/editbooks.py:1520 cps/updater.py:615 cps/uploader.py:108 @@ -64,7 +68,8 @@ msgstr "Configurazione dell'interfaccia utente" #: cps/web.py:753 #, python-format msgid "Custom Column No.%(column)d does not exist in calibre database" -msgstr "La colonna personalizzata no.%(column)d non esiste nel database di Calibre" +msgstr "" +"La colonna personalizzata no.%(column)d non esiste nel database di Calibre" #: cps/admin.py:333 cps/templates/admin.html:51 msgid "Edit Users" @@ -103,7 +108,9 @@ msgstr "L'utente Guest (ospite) non può avere questo ruolo" #: cps/admin.py:500 cps/admin.py:2031 msgid "No admin user remaining, can't remove admin role" -msgstr "Non rimarrebbe nessun utente amministratore, non è possibile rimuovere il ruolo di amministratore" +msgstr "" +"Non rimarrebbe nessun utente amministratore, non è possibile rimuovere il " +"ruolo di amministratore" #: cps/admin.py:504 cps/admin.py:518 msgid "Value has to be true or false" @@ -123,7 +130,9 @@ msgstr "Visualizzazione non valida" #: cps/admin.py:523 msgid "Guest's Locale is determined automatically and can't be set" -msgstr "Le impostazioni locali dell'utente Guest (ospite) sono determinate automaticamente e non possono essere configurate" +msgstr "" +"Le impostazioni locali dell'utente Guest (ospite) sono determinate " +"automaticamente e non possono essere configurate" #: cps/admin.py:527 msgid "No Valid Locale Given" @@ -172,47 +181,77 @@ msgstr "Sei sicuro di voler eliminare questo scaffale?" #: cps/admin.py:625 msgid "Are you sure you want to change locales of selected user(s)?" -msgstr "Sei sicuro di voler cambiare le impostazioni internazionali degli utenti selezionati?" +msgstr "" +"Sei sicuro di voler cambiare le impostazioni internazionali degli utenti " +"selezionati?" #: cps/admin.py:627 -msgid "Are you sure you want to change visible book languages for selected user(s)?" -msgstr "Sei sicuro di voler cambiare le lingue visibili del libro per gli utenti selezionati?" +msgid "" +"Are you sure you want to change visible book languages for selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le lingue visibili del libro per gli utenti " +"selezionati?" #: cps/admin.py:629 -msgid "Are you sure you want to change the selected role for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare il ruolo selezionato per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected role for the selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare il ruolo selezionato per gli utenti selezionati?" #: cps/admin.py:631 -msgid "Are you sure you want to change the archive status for the selected book(s)?" -msgstr "Sei sicuro di voler cambiare lo stato dell'archivio per i libri selezionati?" +msgid "" +"Are you sure you want to change the archive status for the selected book(s)?" +msgstr "" +"Sei sicuro di voler cambiare lo stato dell'archivio per i libri selezionati?" #: cps/admin.py:633 -msgid "Are you sure you want to change the read status for the selected book(s)?" -msgstr "Sei sicuro di voler cambiare lo stato di lettura per i libri selezionati?" +msgid "" +"Are you sure you want to change the read status for the selected book(s)?" +msgstr "" +"Sei sicuro di voler cambiare lo stato di lettura per i libri selezionati?" #: cps/admin.py:635 -msgid "Are you sure you want to change the selected restrictions for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare le restrizioni selezionate per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected restrictions for the selected " +"user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le restrizioni selezionate per gli utenti " +"selezionati?" #: cps/admin.py:637 -msgid "Are you sure you want to change the selected visibility restrictions for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare le restrizioni di visibilità selezionate per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected visibility restrictions for the " +"selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le restrizioni di visibilità selezionate per " +"gli utenti selezionati?" #: cps/admin.py:640 -msgid "Are you sure you want to change shelf sync behavior for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare il comportamento di sincronizzazione dello scaffale per gli utenti selezionati?" +msgid "" +"Are you sure you want to change shelf sync behavior for the selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare il comportamento di sincronizzazione dello " +"scaffale per gli utenti selezionati?" #: cps/admin.py:642 msgid "Are you sure you want to change Calibre library location?" msgstr "Sei sicuro di voler cambiare la posizione della biblioteca di Calibre?" #: cps/admin.py:644 -msgid "Calibre-Web will search for updated Covers and update Cover Thumbnails, this may take a while?" -msgstr "Calibre-Web cercherà le copertine aggiornate e aggiornerà le miniature delle copertine, ma ci vorrà un po' di tempo." +msgid "" +"Calibre-Web will search for updated Covers and update Cover Thumbnails, this " +"may take a while?" +msgstr "" +"Calibre-Web cercherà le copertine aggiornate e aggiornerà le miniature delle " +"copertine, ma ci vorrà un po' di tempo." #: cps/admin.py:647 -msgid "Are you sure you want delete Calibre-Web's sync database to force a full sync with your Kobo Reader?" -msgstr "Sei sicuro di voler eliminare il database sincronizzato di Calibre-Web e forzare una sincronizzazione completa con il tuo lettore Kobo?" +msgid "" +"Are you sure you want delete Calibre-Web's sync database to force a full " +"sync with your Kobo Reader?" +msgstr "" +"Sei sicuro di voler eliminare il database sincronizzato di Calibre-Web e " +"forzare una sincronizzazione completa con il tuo lettore Kobo?" #: cps/admin.py:890 cps/admin.py:896 cps/admin.py:906 cps/admin.py:916 #: cps/templates/modal_dialogs.html:29 cps/templates/user_table.html:41 @@ -245,15 +284,21 @@ msgstr "client_secrets.json non è configurato per Web Application" #: cps/admin.py:1182 msgid "Logfile Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del file di log non è valida, per favore indica il percorso corretto" +msgstr "" +"La posizione del file di log non è valida, per favore indica il percorso " +"corretto" #: cps/admin.py:1188 msgid "Access Logfile Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del file del log di accesso non è valida, indica il percorso corretto" +msgstr "" +"La posizione del file del log di accesso non è valida, indica il percorso " +"corretto" #: cps/admin.py:1222 msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier" -msgstr "Inserisci un provider LDAP, una porta, un DN e un identificatore oggetto utente" +msgstr "" +"Inserisci un provider LDAP, una porta, un DN e un identificatore oggetto " +"utente" #: cps/admin.py:1228 msgid "Please Enter a LDAP Service Account and Password" @@ -266,7 +311,8 @@ msgstr "Inserisci un account di servizio LDAP" #: cps/admin.py:1236 #, python-format msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro oggetto gruppo LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro oggetto gruppo LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1238 msgid "LDAP Group Object Filter Has Unmatched Parenthesis" @@ -275,7 +321,8 @@ msgstr "Il filtro oggetto gruppo LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1242 #, python-format msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro oggetto utente LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro oggetto utente LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1244 msgid "LDAP User Object Filter Has Unmatched Parenthesis" @@ -284,15 +331,20 @@ msgstr "Il filtro oggetto utente LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1251 #, python-format msgid "LDAP Member User Filter needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro utente membro LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro utente membro LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1253 msgid "LDAP Member User Filter Has Unmatched Parenthesis" msgstr "Il filtro utente membro LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1260 -msgid "LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter Correct Path" -msgstr "Il certificato CA LDAP, il certificato o la posizione della chiave non sono validi. Inserisci il percorso corretto" +msgid "" +"LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter " +"Correct Path" +msgstr "" +"Il certificato CA LDAP, il certificato o la posizione della chiave non sono " +"validi. Inserisci il percorso corretto" #: cps/admin.py:1291 cps/templates/admin.html:53 msgid "Add New User" @@ -317,8 +369,11 @@ msgstr "Errore nel database: %(error)s." #: cps/admin.py:1349 #, python-format -msgid "Test e-mail queued for sending to %(email)s, please check Tasks for result" -msgstr "L'e-mail di prova è stato accodata correttamente per essere spedita a %(email)s, controlla il risultato in Attività" +msgid "" +"Test e-mail queued for sending to %(email)s, please check Tasks for result" +msgstr "" +"L'e-mail di prova è stato accodata correttamente per essere spedita a " +"%(email)s, controlla il risultato in Attività" #: cps/admin.py:1352 #, python-format @@ -426,7 +481,8 @@ msgstr "Errore generale" #: cps/admin.py:1556 msgid "Update file could not be saved in temp dir" -msgstr "Il file di aggiornamento non può essere salvato nella cartella temporanea" +msgstr "" +"Il file di aggiornamento non può essere salvato nella cartella temporanea" #: cps/admin.py:1557 msgid "Files could not be replaced during update" @@ -464,7 +520,8 @@ msgstr "Percorso dei libri non valido" #: cps/admin.py:1745 msgid "DB Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del DB non è valida, per favore indica il percorso corretto" +msgstr "" +"La posizione del DB non è valida, per favore indica il percorso corretto" #: cps/admin.py:1773 msgid "DB is not Writeable" @@ -522,7 +579,9 @@ msgstr "Impossibile eliminare l'utente Guest (ospite)" #: cps/admin.py:2016 msgid "No admin user remaining, can't delete user" -msgstr "Non rimarrebbe nessun utente amministratore, non è possibile eliminare l'utente" +msgstr "" +"Non rimarrebbe nessun utente amministratore, non è possibile eliminare " +"l'utente" #: cps/admin.py:2071 cps/web.py:1489 msgid "Email can't be empty and has to be a valid Email" @@ -587,8 +646,11 @@ msgstr "Il valore non è presente nella richiesta" #: cps/editbooks.py:319 cps/editbooks.py:326 cps/editbooks.py:627 #: cps/editbooks.py:1146 cps/web.py:535 cps/web.py:1581 cps/web.py:1627 #: cps/web.py:1677 -msgid "Oops! Selected book is unavailable. File does not exist or is not accessible" -msgstr "Il libro selezionato non è disponibile. Il file non esiste o non è accessibile" +msgid "" +"Oops! Selected book is unavailable. File does not exist or is not accessible" +msgstr "" +"Il libro selezionato non è disponibile. Il file non esiste o non è " +"accessibile" #: cps/editbooks.py:673 cps/editbooks.py:1503 msgid "User has no rights to upload cover" @@ -596,7 +658,9 @@ msgstr "L'utente non ha i permessi per caricare le copertine" #: cps/editbooks.py:694 cps/editbooks.py:938 msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier" -msgstr "Gli identificatori non fanno distinzione tra maiuscole e minuscole e sovrascrivono il vecchio identificatore" +msgstr "" +"Gli identificatori non fanno distinzione tra maiuscole e minuscole e " +"sovrascrivono il vecchio identificatore" #: cps/editbooks.py:709 cps/editbooks.py:912 cps/editbooks.py:1273 #, python-format @@ -613,8 +677,12 @@ msgid "Error editing book: {}" msgstr "Errore nella modifica del libro: {}" #: cps/editbooks.py:856 -msgid "Uploaded book probably exists in the library, consider to change before upload new: " -msgstr "Probabilmente il libro caricato esiste già nella biblioteca, cambialo prima di caricarlo di nuovo:" +msgid "" +"Uploaded book probably exists in the library, consider to change before " +"upload new: " +msgstr "" +"Probabilmente il libro caricato esiste già nella biblioteca, cambialo prima " +"di caricarlo di nuovo:" #: cps/editbooks.py:950 cps/editbooks.py:1420 msgid "File type isn't allowed to be uploaded to this server" @@ -623,7 +691,8 @@ msgstr "Non è consentito caricare questo tipo di file su questo server" #: cps/editbooks.py:956 cps/editbooks.py:1431 #, python-format msgid "File extension '%(ext)s' is not allowed to be uploaded to this server" -msgstr "Non è consentito caricare l'estensione del file '%(ext)s' su questo server" +msgstr "" +"Non è consentito caricare l'estensione del file '%(ext)s' su questo server" #: cps/editbooks.py:960 cps/editbooks.py:1436 msgid "File to be uploaded must have an extension" @@ -685,12 +754,20 @@ msgid "File format %(ext)s added to %(book)s" msgstr "Formato file %(ext)s aggiunto a %(book)s" #: cps/gdrive.py:58 -msgid "Google Drive setup not completed, try to deactivate and activate Google Drive again" -msgstr "Configurazione di Google Drive non completata, prova a disattivare e attivare nuovamente Google Drive" +msgid "" +"Google Drive setup not completed, try to deactivate and activate Google " +"Drive again" +msgstr "" +"Configurazione di Google Drive non completata, prova a disattivare e " +"attivare nuovamente Google Drive" #: cps/gdrive.py:96 -msgid "Callback domain is not verified, please follow steps to verify domain in google developer console" -msgstr "Il dominio di callback non è stato verificato, segui i passaggi per verificare il dominio nella console per sviluppatori di Google" +msgid "" +"Callback domain is not verified, please follow steps to verify domain in " +"google developer console" +msgstr "" +"Il dominio di callback non è stato verificato, segui i passaggi per " +"verificare il dominio nella console per sviluppatori di Google" #: cps/helper.py:87 #, python-format @@ -758,8 +835,11 @@ msgstr "Impossibile impostare lo stato di lettura: {}" #: cps/helper.py:373 #, python-format -msgid "Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" -msgstr "Eliminazione della cartella di libri per il libro %(id)s non riuscita, il percorso ha sottocartelle: %(path)s" +msgid "" +"Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" +msgstr "" +"Eliminazione della cartella di libri per il libro %(id)s non riuscita, il " +"percorso ha sottocartelle: %(path)s" #: cps/helper.py:379 #, python-format @@ -768,18 +848,29 @@ msgstr "Eliminazione del libro %(id)s non riuscita: %(message)s" #: cps/helper.py:390 #, python-format -msgid "Deleting book %(id)s from database only, book path in database not valid: %(path)s" -msgstr "Eliminazione del libro %(id)s solo dal database, percorso del libro nel database non valido: %(path)s" +msgid "" +"Deleting book %(id)s from database only, book path in database not valid: " +"%(path)s" +msgstr "" +"Eliminazione del libro %(id)s solo dal database, percorso del libro nel " +"database non valido: %(path)s" #: cps/helper.py:404 #, python-format -msgid "Moving book path of Book %(book_id)s to: '%(src)s' failed with error: %(error)s" -msgstr "Spostamento del percorso del libro %(book_id)s a: '%(src)s' non riuscito con errore: %(error)s" +msgid "" +"Moving book path of Book %(book_id)s to: '%(src)s' failed with error: " +"%(error)s" +msgstr "" +"Spostamento del percorso del libro %(book_id)s a: '%(src)s' non riuscito con " +"errore: %(error)s" #: cps/helper.py:443 #, python-format -msgid "Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "La modifica dell'autore da '%(src)s' a '%(dest)s' è terminata con l'errore: %(error)s" +msgid "" +"Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgstr "" +"La modifica dell'autore da '%(src)s' a '%(dest)s' è terminata con l'errore: " +"%(error)s" #: cps/helper.py:513 cps/helper.py:522 #, python-format @@ -789,7 +880,9 @@ msgstr "Il file %(file) non è stato trovato su Google Drive" #: cps/helper.py:564 #, python-format msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "La modifica del titolo da '%(src)s' a '%(dest)s' è terminata con l'errore: %(error)s" +msgstr "" +"La modifica del titolo da '%(src)s' a '%(dest)s' è terminata con l'errore: " +"%(error)s" #: cps/helper.py:583 #, python-format @@ -813,8 +906,11 @@ msgid "Password doesn't comply with password validation rules" msgstr "La password non è conforme alle regole di convalida della password" #: cps/helper.py:833 -msgid "Python module 'advocate' is not installed but is needed for cover uploads" -msgstr "Il modulo Python \"advocate\" non è installato ma è necessario per il caricamento delle copertine" +msgid "" +"Python module 'advocate' is not installed but is needed for cover uploads" +msgstr "" +"Il modulo Python \"advocate\" non è installato ma è necessario per il " +"caricamento delle copertine" #: cps/helper.py:843 msgid "Error Downloading Cover" @@ -825,8 +921,12 @@ msgid "Cover Format Error" msgstr "Errore nel formato della copertina" #: cps/helper.py:849 -msgid "You are not allowed to access localhost or the local network for cover uploads" -msgstr "Non ti è consentito accedere all'host locale o alla rete locale per caricare le copertine" +msgid "" +"You are not allowed to access localhost or the local network for cover " +"uploads" +msgstr "" +"Non ti è consentito accedere all'host locale o alla rete locale per caricare " +"le copertine" #: cps/helper.py:859 msgid "Failed to create path for cover" @@ -834,11 +934,14 @@ msgstr "Impossibile creare il percorso per la copertina" #: cps/helper.py:875 msgid "Cover-file is not a valid image file, or could not be stored" -msgstr "Il file della copertina non è in un formato di immagine valido o non può essere salvato" +msgstr "" +"Il file della copertina non è in un formato di immagine valido o non può " +"essere salvato" #: cps/helper.py:886 msgid "Only jpg/jpeg/png/webp/bmp files are supported as coverfile" -msgstr "Solo i file jpg/jpeg/png/webp/bmp sono supportati come file di copertina" +msgstr "" +"Solo i file jpg/jpeg/png/webp/bmp sono supportati come file di copertina" #: cps/helper.py:898 msgid "Invalid cover file content" @@ -891,8 +994,12 @@ msgid "Queue all books for metadata backup" msgstr "Metti in coda tutti i libri per il backup dei metadati" #: cps/kobo_auth.py:92 -msgid "Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device" -msgstr "Accedi a Calibre-Web da un host non locale per ottenere un api_endpoint valido per il dispositivo Kobo" +msgid "" +"Please access Calibre-Web from non localhost to get valid api_endpoint for " +"kobo device" +msgstr "" +"Accedi a Calibre-Web da un host non locale per ottenere un api_endpoint " +"valido per il dispositivo Kobo" #: cps/kobo_auth.py:118 msgid "Kobo Setup" @@ -915,7 +1022,8 @@ msgstr "Collegamento riuscito a %(oauth)s" #: cps/oauth_bb.py:156 msgid "Login failed, No User Linked With OAuth Account" -msgstr "Accesso non riuscito, non c'è nessun utente collegato all'account OAuth" +msgstr "" +"Accesso non riuscito, non c'è nessun utente collegato all'account OAuth" #: cps/oauth_bb.py:198 #, python-format @@ -1153,7 +1261,9 @@ msgstr "Stato di lettura = '%(status)s'" #: cps/search.py:351 msgid "Error on search for custom columns, please restart Calibre-Web" -msgstr "Errore nella ricerca delle colonne personalizzate. Per favore riavvia Calibre-Web" +msgstr "" +"Errore nella ricerca delle colonne personalizzate. Per favore riavvia " +"Calibre-Web" #: cps/search.py:370 cps/search.py:402 cps/templates/layout.html:57 msgid "Advanced Search" @@ -1165,7 +1275,8 @@ msgstr "Scaffale specificato non valido" #: cps/shelf.py:55 msgid "Sorry you are not allowed to add a book to that shelf" -msgstr "Spiacente, ma non sei autorizzato ad aggiungere libri a questo scaffale" +msgstr "" +"Spiacente, ma non sei autorizzato ad aggiungere libri a questo scaffale" #: cps/shelf.py:64 #, python-format @@ -1175,7 +1286,8 @@ msgstr "Il libro è gia presente nello scaffale: %(shelfname)s" #: cps/shelf.py:77 #, python-format msgid "%(book_id)s is a invalid Book Id. Could not be added to Shelf" -msgstr "%(book_id)s non è un valido ID libro. Impossibile aggiungerlo allo scaffale" +msgstr "" +"%(book_id)s non è un valido ID libro. Impossibile aggiungerlo allo scaffale" #: cps/shelf.py:97 #, python-format @@ -1270,7 +1382,9 @@ msgstr "Scaffale: '%(name)s'" #: cps/shelf.py:487 msgid "Error opening shelf. Shelf does not exist or is not accessible" -msgstr "Errore nell'apertura dello scaffale. Lo scaffale non esiste o non è accessibile" +msgstr "" +"Errore nell'apertura dello scaffale. Lo scaffale non esiste o non è " +"accessibile" #: cps/tasks_status.py:47 cps/templates/layout.html:90 #: cps/templates/tasks.html:7 @@ -1314,8 +1428,12 @@ msgid "No update available. You already have the latest version installed" msgstr "Nessun aggiornamento disponibile. Hai già l'ultima versione installata" #: cps/updater.py:459 -msgid "A new update is available. Click on the button below to update to the latest version." -msgstr "È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per aggiornare all'ultima versione" +msgid "" +"A new update is available. Click on the button below to update to the latest " +"version." +msgstr "" +"È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per " +"aggiornare all'ultima versione" #: cps/updater.py:476 msgid "Could not fetch update information" @@ -1323,12 +1441,18 @@ msgstr "Impossibile recuperare le informazioni sull'aggiornamento" #: cps/updater.py:487 msgid "Click on the button below to update to the latest stable version." -msgstr "Fai clic sul pulsante in basso per eseguire l'aggiornamento all'ultima versione stabile." +msgstr "" +"Fai clic sul pulsante in basso per eseguire l'aggiornamento all'ultima " +"versione stabile." #: cps/updater.py:495 cps/updater.py:509 cps/updater.py:520 #, python-format -msgid "A new update is available. Click on the button below to update to version: %(version)s" -msgstr "È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per aggiornare alla versione:%(version)s" +msgid "" +"A new update is available. Click on the button below to update to version: " +"%(version)s" +msgstr "" +"È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per " +"aggiornare alla versione:%(version)s" #: cps/updater.py:538 msgid "No release information available" @@ -1429,11 +1553,14 @@ msgstr "Registrati" #: cps/web.py:1295 cps/web.py:1398 msgid "Connection error to limiter backend, please contact your administrator" -msgstr "Il server e-mail non è configurato, per favore contatta l'amministratore" +msgstr "" +"Il server e-mail non è configurato, per favore contatta l'amministratore" #: cps/web.py:1300 cps/web.py:1347 -msgid "Oops! Email server is not configured, please contact your administrator." -msgstr "Il server e-mail non è configurato, per favore contatta l'amministratore" +msgid "" +"Oops! Email server is not configured, please contact your administrator." +msgstr "" +"Il server e-mail non è configurato, per favore contatta l'amministratore" #: cps/web.py:1333 msgid "Oops! Your Email is not allowed." @@ -1458,8 +1585,12 @@ msgstr "ora sei connesso come: '%(nickname)s'" #: cps/web.py:1420 #, python-format -msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" -msgstr "Accesso di riserva come: '%(nickname)s', il server LDAP non è raggiungibile o l'utente è sconosciuto" +msgid "" +"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not " +"known" +msgstr "" +"Accesso di riserva come: '%(nickname)s', il server LDAP non è raggiungibile " +"o l'utente è sconosciuto" #: cps/web.py:1425 #, python-format @@ -2059,8 +2190,11 @@ msgid "Add Identifier" msgstr "Aggiungi identificatore" #: cps/templates/book_edit.html:133 -msgid "Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)" -msgstr "Recupera la copertina dall'URL (JPEG: l'immagine verrà scaricata e archiviata nel database)" +msgid "" +"Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)" +msgstr "" +"Recupera la copertina dall'URL (JPEG: l'immagine verrà scaricata e " +"archiviata nel database)" #: cps/templates/book_edit.html:137 msgid "Upload Cover from Local Disk" @@ -2307,7 +2441,8 @@ msgstr "Modifica i metadati" #: cps/templates/book_table.html:275 msgid "Edit the fields you want changed. Blank fields will be ignored:" -msgstr "Modifica i campi che desideri modificare. I campi vuoti saranno ignorati:" +msgstr "" +"Modifica i campi che desideri modificare. I campi vuoti saranno ignorati:" #: cps/templates/config_db.html:12 msgid "Location of Calibre Database" @@ -2339,7 +2474,8 @@ msgstr "Revoca" #: cps/templates/config_db.html:80 msgid "New db location is invalid, please enter valid path" -msgstr "La nuova posizione del database non è valida, inserisci un percorso valido" +msgstr "" +"La nuova posizione del database non è valida, inserisci un percorso valido" #: cps/templates/config_edit.html:18 msgid "Server Configuration" @@ -2351,11 +2487,15 @@ msgstr "Porta del server" #: cps/templates/config_edit.html:28 msgid "SSL certfile location (leave it empty for non-SSL Servers)" -msgstr "Posizione del file del certificato SSL (lascia vuoto per una configurazione del server senza SSL)" +msgstr "" +"Posizione del file del certificato SSL (lascia vuoto per una configurazione " +"del server senza SSL)" #: cps/templates/config_edit.html:35 msgid "SSL Keyfile location (leave it empty for non-SSL Servers)" -msgstr "Posizione del file della chiave SSL (lascia vuoto per una configurazione del server senza SSL)" +msgstr "" +"Posizione del file della chiave SSL (lascia vuoto per una configurazione del " +"server senza SSL)" #: cps/templates/config_edit.html:43 msgid "Update Channel" @@ -2379,7 +2519,8 @@ msgstr "Configurazione del file di log" #: cps/templates/config_edit.html:77 msgid "Location and name of logfile (calibre-web.log for no entry)" -msgstr "Posizione e nome del file di log (se non specificato sarà calibre-web.log)" +msgstr "" +"Posizione e nome del file di log (se non specificato sarà calibre-web.log)" #: cps/templates/config_edit.html:82 msgid "Enable Access Log" @@ -2387,7 +2528,9 @@ msgstr "Abilita il log degli accessi" #: cps/templates/config_edit.html:85 msgid "Location and name of access logfile (access.log for no entry)" -msgstr "Posizione e nome del file di log degli accessi (se non specificato sarà access.log)" +msgstr "" +"Posizione e nome del file di log degli accessi (se non specificato sarà " +"access.log)" #: cps/templates/config_edit.html:96 msgid "Feature Configuration" @@ -2395,11 +2538,17 @@ msgstr "Configurazione funzionalità" #: cps/templates/config_edit.html:104 msgid "Convert non-English characters in title and author while saving to disk" -msgstr "Converti caratteri non inglesi nel titolo e nell'autore durante il salvataggio su disco" +msgstr "" +"Converti caratteri non inglesi nel titolo e nell'autore durante il " +"salvataggio su disco" #: cps/templates/config_edit.html:108 -msgid "Embed Metadata to Ebook File on Download/Conversion/e-mail (needs Calibre/Kepubify binaries)" -msgstr "Incorpora metadati nel file del libro al momento del download e della conversione per e-mail (sono necessari gli eseguibili Calibre/Kepubify)" +msgid "" +"Embed Metadata to Ebook File on Download/Conversion/e-mail (needs Calibre/" +"Kepubify binaries)" +msgstr "" +"Incorpora metadati nel file del libro al momento del download e della " +"conversione per e-mail (sono necessari gli eseguibili Calibre/Kepubify)" #: cps/templates/config_edit.html:112 msgid "Enable Uploads" @@ -2407,7 +2556,9 @@ msgstr "Abilita il caricamento" #: cps/templates/config_edit.html:112 msgid "(Please ensure that users also have upload permissions)" -msgstr "(assicurati che gli utenti dispongano anche delle autorizzazioni di caricamento)" +msgstr "" +"(assicurati che gli utenti dispongano anche delle autorizzazioni di " +"caricamento)" #: cps/templates/config_edit.html:116 msgid "Allowed Upload Fileformats" @@ -2451,7 +2602,7 @@ msgstr "Chiave API Goodreads" #: cps/templates/config_edit.html:167 msgid "Google Books API Key" -msgstr "" +msgstr "Chiave API di Google Libri" #: cps/templates/config_edit.html:172 msgid "Allow Reverse Proxy Authentication" @@ -2494,16 +2645,24 @@ msgid "SSL" msgstr "SSL" #: cps/templates/config_edit.html:213 -msgid "LDAP CACertificate Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso certificato CA LDAP (necessario solo per l'autenticazione del certificato client)" +msgid "" +"LDAP CACertificate Path (Only needed for Client Certificate Authentication)" +msgstr "" +"Percorso certificato CA LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:220 -msgid "LDAP Certificate Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso certificato LDAP (necessario solo per l'autenticazione del certificato client)" +msgid "" +"LDAP Certificate Path (Only needed for Client Certificate Authentication)" +msgstr "" +"Percorso certificato LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:227 msgid "LDAP Keyfile Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso del file di chiavi LDAP (necessario solo per l'autenticazione del certificato client)" +msgstr "" +"Percorso del file di chiavi LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:236 msgid "LDAP Authentication" @@ -2543,7 +2702,8 @@ msgstr "Il server LDAP è OpenLDAP?" #: cps/templates/config_edit.html:267 msgid "Following Settings are Needed For User Import" -msgstr "Per l'importazione degli utenti sono necessarie le seguenti impostazioni" +msgstr "" +"Per l'importazione degli utenti sono necessarie le seguenti impostazioni" #: cps/templates/config_edit.html:269 msgid "LDAP Group Object Filter" @@ -2626,7 +2786,9 @@ msgstr "Opzioni per il limitatore del Backend" #: cps/templates/config_edit.html:386 msgid "Check if file extensions matches file content on upload" -msgstr "Controlla se le estensioni dei file corrispondono al contenuto del file al momento del caricamento" +msgstr "" +"Controlla se le estensioni dei file corrispondono al contenuto del file al " +"momento del caricamento" #: cps/templates/config_edit.html:389 msgid "Session protection" @@ -2662,7 +2824,8 @@ msgstr "Obbliga caratteri maiuscoli" #: cps/templates/config_edit.html:418 msgid "Enforce characters (needed For Chinese/Japanese/Korean Characters)" -msgstr "Obbliga caratteri (necessario per caratteri cinesi, giapponesi e coreani)" +msgstr "" +"Obbliga caratteri (necessario per caratteri cinesi, giapponesi e coreani)" #: cps/templates/config_edit.html:422 msgid "Enforce special characters" @@ -2678,7 +2841,8 @@ msgstr "Numero di libri casuali da mostrare" #: cps/templates/config_view_edit.html:36 msgid "No. of Authors to Display Before Hiding (0=Disable Hiding)" -msgstr "Numero di autori da mostrare prima di nascondere (0=disabilita nascondere)" +msgstr "" +"Numero di autori da mostrare prima di nascondere (0=disabilita nascondere)" #: cps/templates/config_view_edit.html:40 cps/templates/readcbr.html:101 msgid "Theme" @@ -2802,8 +2966,12 @@ msgid "Add to archive" msgstr "Aggiungi all'archivio" #: cps/templates/detail.html:275 -msgid "Mark Book as archived or not, to hide it in Calibre-Web and delete it from Kobo Reader" -msgstr "Contrassegna il libro come archiviato o no per nasconderlo in Calibre-Web ed eliminarlo da Kobo Reader" +msgid "" +"Mark Book as archived or not, to hide it in Calibre-Web and delete it from " +"Kobo Reader" +msgstr "" +"Contrassegna il libro come archiviato o no per nasconderlo in Calibre-Web ed " +"eliminarlo da Kobo Reader" #: cps/templates/detail.html:301 cps/templates/listenmp3.html:190 #: cps/templates/search.html:16 @@ -2884,8 +3052,12 @@ msgid "Denied Domains (Blacklist)" msgstr "Domini non consentiti (lista nera)" #: cps/templates/generate_kobo_auth_url.html:6 -msgid "Open the .kobo/Kobo/Kobo eReader.conf file in a text editor and add (or edit):" -msgstr "Apri il file .kobo/Kobo/Kobo eReader.conf in un editor di testo e aggiungi (o modifica):" +msgid "" +"Open the .kobo/Kobo/Kobo eReader.conf file in a text editor and add (or " +"edit):" +msgstr "" +"Apri il file .kobo/Kobo/Kobo eReader.conf in un editor di testo e aggiungi " +"(o modifica):" #: cps/templates/generate_kobo_auth_url.html:11 msgid "Kobo Token:" @@ -2897,7 +3069,8 @@ msgstr "Elenco" #: cps/templates/http_error.html:34 msgid "Calibre-Web Instance is unconfigured, please contact your administrator" -msgstr "L'istanza Calibre-Web non è configurata, per favore contatta l'amministratore" +msgstr "" +"L'istanza Calibre-Web non è configurata, per favore contatta l'amministratore" #: cps/templates/http_error.html:44 msgid "Create Issue" @@ -3101,7 +3274,8 @@ msgstr "Seleziona le categorie consentite/negate per l'utente" #: cps/templates/modal_dialogs.html:9 msgid "Select Allowed/Denied Custom Column Values of User" -msgstr "Seleziona i valori personali consentiti/negati per le colonne dell'utente" +msgstr "" +"Seleziona i valori personali consentiti/negati per le colonne dell'utente" #: cps/templates/modal_dialogs.html:15 msgid "Enter Tag" @@ -3118,14 +3292,22 @@ msgstr "Questo formato di libro verrà definitivamente cancellato dal database" #: cps/templates/modal_dialogs.html:51 msgid "This book will be permanently erased from database and hard disk" msgstr "" +"Questo libro verrà cancellato definitivamente dal database e dal disco rigido" #: cps/templates/modal_dialogs.html:55 -msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." -msgstr "Nota importante su Kobo: i libri eliminati rimarranno su qualsiasi dispositivo Kobo associato." +msgid "" +"Important Kobo Note: deleted books will remain on any paired Kobo device." +msgstr "" +"Nota importante su Kobo: i libri eliminati rimarranno su qualsiasi " +"dispositivo Kobo associato." #: cps/templates/modal_dialogs.html:56 -msgid "Books must first be archived and the device synced before a book can safely be deleted." -msgstr "I libri devono essere prima archiviati e il dispositivo sincronizzato prima che un libro possa essere eliminato in sicurezza." +msgid "" +"Books must first be archived and the device synced before a book can safely " +"be deleted." +msgstr "" +"I libri devono essere prima archiviati e il dispositivo sincronizzato prima " +"che un libro possa essere eliminato in sicurezza." #: cps/templates/modal_dialogs.html:75 msgid "Choose File Location" @@ -3574,20 +3756,31 @@ msgid "Actions" msgstr "Azioni" #: cps/templates/tasks.html:41 -msgid "This task will be cancelled. Any progress made by this task will be saved." -msgstr "Questa attività sarà annullata. Tutti i progressi compiuti da questa attività saranno salvati." +msgid "" +"This task will be cancelled. Any progress made by this task will be saved." +msgstr "" +"Questa attività sarà annullata. Tutti i progressi compiuti da questa " +"attività saranno salvati." #: cps/templates/tasks.html:42 -msgid "If this is a scheduled task, it will be re-ran during the next scheduled time." -msgstr "Se si tratta di un'attività pianificata, verrà eseguita nuovamente all'orario pianificato successivo." +msgid "" +"If this is a scheduled task, it will be re-ran during the next scheduled " +"time." +msgstr "" +"Se si tratta di un'attività pianificata, verrà eseguita nuovamente " +"all'orario pianificato successivo." #: cps/templates/user_edit.html:20 msgid "Reset user Password" msgstr "Reimposta la password dell'utente" #: cps/templates/user_edit.html:28 -msgid "Send to eReader Email Address. Use comma to separate emails for multiple eReaders" -msgstr "Invia all'indirizzo e-mail dell'eReader. Usa la virgola per separare le email per più eReader" +msgid "" +"Send to eReader Email Address. Use comma to separate emails for multiple " +"eReaders" +msgstr "" +"Invia all'indirizzo e-mail dell'eReader. Usa la virgola per separare le " +"email per più eReader" #: cps/templates/user_edit.html:43 msgid "Language of Books" @@ -3716,4 +3909,3 @@ msgstr "Sincronizza gli scaffali selezionati con Kobo" #: cps/templates/user_table.html:156 msgid "Show Read/Unread Section" msgstr "Mostra sezione Libri letti e Libri da leggere" - From 3565e5f06827fd3bf7eaabb04dd8c9efd4feac50 Mon Sep 17 00:00:00 2001 From: szepix <44882451+szepix@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:53:57 +0100 Subject: [PATCH 13/19] Updated polish translations Did all missing translations --- cps/translations/pl/LC_MESSAGES/messages.po | 500 ++++++++++---------- 1 file changed, 250 insertions(+), 250 deletions(-) diff --git a/cps/translations/pl/LC_MESSAGES/messages.po b/cps/translations/pl/LC_MESSAGES/messages.po index 503a4a2d9..887e040d7 100644 --- a/cps/translations/pl/LC_MESSAGES/messages.po +++ b/cps/translations/pl/LC_MESSAGES/messages.po @@ -9,15 +9,16 @@ msgstr "" "Project-Id-Version: Calibre Web - polski (POT: 2021-06-12 08:52)\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2025-12-07 14:32+0100\n" -"PO-Revision-Date: 2021-06-12 15:35+0200\n" -"Last-Translator: Radosław Kierznowski \n" -"Language: pl\n" +"PO-Revision-Date: 2025-12-08 12:51+0100\n" +"Last-Translator: Daniel Szepietowski \n" "Language-Team: \n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Generated-By: Babel 2.17.0\n" +"X-Generator: Poedit 3.8\n" #: cps/about.py:85 msgid "Statistics" @@ -25,15 +26,15 @@ msgstr "Statystyki" #: cps/admin.py:151 msgid "Server restarted, please reload page." -msgstr "" +msgstr "Serwer został zrestartowany, proszę odświeżyć stronę." #: cps/admin.py:153 msgid "Performing Server shutdown, please close window." -msgstr "" +msgstr "Wyłączanie serwera, proszę zamknąć okno." #: cps/admin.py:161 msgid "Success! Database Reconnected" -msgstr "" +msgstr "Sukces! Ponownie połączono z bazą danych." #: cps/admin.py:164 msgid "Unknown command" @@ -41,7 +42,7 @@ msgstr "Nieznane polecenie" #: cps/admin.py:175 msgid "Success! Books queued for Metadata Backup, please check Tasks for result" -msgstr "" +msgstr "Sukces! Książki zostały umieszczone w kolejce do utworzenia kopii zapasowej metadanych, sprawdź zakładkę " # ??? #: cps/admin.py:208 cps/editbooks.py:808 cps/editbooks.py:852 @@ -66,7 +67,7 @@ msgstr "Konfiguracja Interfejsu" #: cps/web.py:753 #, python-format msgid "Custom Column No.%(column)d does not exist in calibre database" -msgstr "" +msgstr "Niestandardowa kolumna %(column)d nie istnieje w bazie danych calibre." #: cps/admin.py:333 cps/templates/admin.html:51 msgid "Edit Users" @@ -143,11 +144,11 @@ msgstr "Nie znaleziono parametru" #: cps/admin.py:577 msgid "Invalid Read Column" -msgstr "" +msgstr "Nieprawidłowa kolumna do odczytu" #: cps/admin.py:583 msgid "Invalid Restricted Column" -msgstr "" +msgstr "Nieprawidłowa zastrzeżona kolumna" #: cps/admin.py:603 cps/admin.py:1902 msgid "Calibre-Web configuration updated" @@ -167,7 +168,7 @@ msgstr "Czy naprawdę chcesz usunąć tego użytkownika?" #: cps/admin.py:621 msgid "Do you really want to delete this book?" -msgstr "" +msgstr "Czy na pewnochcesz usunąć tą książkę?" #: cps/admin.py:623 msgid "Are you sure you want to delete this shelf?" @@ -187,11 +188,11 @@ msgstr "Czy na pewno chcesz zmienić wybraną rolę dla wybranego użytkownika ( #: cps/admin.py:631 msgid "Are you sure you want to change the archive status for the selected book(s)?" -msgstr "" +msgstr "Czy na pewno chcesz zmienić status archiwizacji dla wybranych książek?" #: cps/admin.py:633 msgid "Are you sure you want to change the read status for the selected book(s)?" -msgstr "" +msgstr "Czy na pewno chcesz zmienić stan przeczytania dla danych książek?" #: cps/admin.py:635 msgid "Are you sure you want to change the selected restrictions for the selected user(s)?" @@ -211,11 +212,11 @@ msgstr "Czy na pewno chcesz zmienić lokalizację biblioteki Calibre?" #: cps/admin.py:644 msgid "Calibre-Web will search for updated Covers and update Cover Thumbnails, this may take a while?" -msgstr "" +msgstr "Calibre-Web wyszuka i zaktualizuje okładki i miniaturki, to może zająć chwilę." #: cps/admin.py:647 msgid "Are you sure you want delete Calibre-Web's sync database to force a full sync with your Kobo Reader?" -msgstr "" +msgstr "Czy na pewno chcesz usunąć bazę synchronizacji Calibre-Web i wymusić ponowną pełną synchronizację z twoim czytnikiem Kobo?" #: cps/admin.py:890 cps/admin.py:896 cps/admin.py:906 cps/admin.py:916 #: cps/templates/modal_dialogs.html:29 cps/templates/user_table.html:41 @@ -232,11 +233,11 @@ msgstr "Zezwalaj" #: cps/admin.py:951 #, python-brace-format msgid "{} sync entries deleted" -msgstr "" +msgstr "Usunięto {} wpisów synchronizacyjnych" #: cps/admin.py:992 msgid "Tag not found" -msgstr "" +msgstr "Nie znaleziono tagu." #: cps/admin.py:1010 msgid "Invalid Action" @@ -307,7 +308,7 @@ msgstr "Zmień ustawienia SMTP" #: cps/admin.py:1319 msgid "Success! Gmail Account Verified." -msgstr "" +msgstr "Sukces! Konto Gmail zostało zweryfikowane." #: cps/admin.py:1339 cps/admin.py:1342 cps/admin.py:1727 cps/admin.py:1886 #: cps/admin.py:1984 cps/admin.py:2105 cps/editbooks.py:169 @@ -321,7 +322,7 @@ msgstr "Błąd bazy danych: %(error)s." #: cps/admin.py:1349 #, python-format msgid "Test e-mail queued for sending to %(email)s, please check Tasks for result" -msgstr "" +msgstr "Email testowy na %(email)s został dodany do kolejki, sprawdź zakładkę Zadania aby sprawdzić rezultat. " #: cps/admin.py:1352 #, python-format @@ -338,19 +339,19 @@ msgstr "Zaktualizowano ustawienia serwera poczty e-mail" #: cps/admin.py:1379 cps/templates/admin.html:195 msgid "Edit Scheduled Tasks Settings" -msgstr "" +msgstr "Zmodyfikuj ustawienia zaplanowanych zadań." #: cps/admin.py:1391 msgid "Invalid start time for task specified" -msgstr "" +msgstr "Ustawiono nieprawidłowy czas rozpoczęcia zadania." #: cps/admin.py:1396 msgid "Invalid duration for task specified" -msgstr "" +msgstr "Ustawiono nieprawidłowy czas trwania zadania." #: cps/admin.py:1406 msgid "Scheduled tasks settings updated" -msgstr "" +msgstr "Zaktualizowano ustawienia zaplanowanych zadań." #: cps/admin.py:1416 cps/admin.py:1465 cps/admin.py:2101 cps/web.py:1330 msgid "Oops! An unknown error occurred. Please try again later." @@ -358,7 +359,7 @@ msgstr "Wystąpił nieznany błąd. Spróbuj ponownie później." #: cps/admin.py:1420 msgid "Settings DB is not Writeable" -msgstr "" +msgstr "Brak uprawnień do zapisu bazy danych ustawień." #: cps/admin.py:1450 cps/admin.py:2093 #, python-format @@ -369,11 +370,11 @@ msgstr "Edytuj użytkownika %(nick)s" #: cps/admin.py:1462 #, python-format msgid "Success! Password for user %(user)s reset" -msgstr "" +msgstr "Sukces! Zresetowano hasło dla użytkownika %(user)s" #: cps/admin.py:1468 msgid "Oops! Please configure the SMTP mail settings." -msgstr "" +msgstr "Ups! Proszę skonfiguruj ustawienia SMTP dla email." #: cps/admin.py:1479 msgid "Logfile viewer" @@ -431,15 +432,15 @@ msgstr "Błąd ogólny" #: cps/admin.py:1556 msgid "Update file could not be saved in temp dir" -msgstr "" +msgstr "Plik aktualizacyjny nie mógł zostać zapisany do tymczasowego folderu" #: cps/admin.py:1557 msgid "Files could not be replaced during update" -msgstr "" +msgstr "Nie udało się zamienić plików podczas aktualizacji" #: cps/admin.py:1581 msgid "Failed to extract at least One LDAP User" -msgstr "" +msgstr "Nie udało się znaleźć żadnego użytkownika z LDAP." #: cps/admin.py:1626 msgid "Failed to Create at Least One LDAP User" @@ -465,7 +466,7 @@ msgstr "{} Użytkownik pomyślnie zaimportowany" #: cps/admin.py:1739 msgid "Books path not valid" -msgstr "" +msgstr "Nieprawidłowa ścieżka do książek." #: cps/admin.py:1745 msgid "DB Location is not Valid, Please Enter Correct Path" @@ -485,11 +486,11 @@ msgstr "Lokalizacja pliku certyfikatu jest nieprawidłowa, wprowadź poprawną #: cps/admin.py:1871 msgid "Password length has to be between 1 and 40" -msgstr "" +msgstr "Hasło musi mieć między 1 a 40 znaków." #: cps/admin.py:1925 msgid "Database Settings updated" -msgstr "" +msgstr "Zaktualizowano ustawienia bazy danych" #: cps/admin.py:1933 msgid "Database Configuration" @@ -523,7 +524,7 @@ msgstr "Użytkownik '%(nick)s' został usunięty" #: cps/admin.py:2013 msgid "Can't delete Guest User" -msgstr "" +msgstr "Nie udało się usunąć konta gościa" #: cps/admin.py:2016 msgid "No admin user remaining, can't delete user" @@ -531,7 +532,7 @@ msgstr "Nie można usunąć użytkownika. Brak na serwerze innego konta z prawam #: cps/admin.py:2071 cps/web.py:1489 msgid "Email can't be empty and has to be a valid Email" -msgstr "" +msgstr "Email nie może być pusty i musi być prawidłowy" #: cps/admin.py:2097 #, python-format @@ -583,11 +584,11 @@ msgstr "Podczas konwersji książki wystąpił błąd: %(res)s" #: cps/editbooks.py:297 msgid "Changes successfully applied" -msgstr "" +msgstr "Zmiany zostały poprawnie zaaplikowane." #: cps/editbooks.py:317 msgid "Value is missing on request" -msgstr "" +msgstr "Brakująca wartość w zapytaniu" #: cps/editbooks.py:319 cps/editbooks.py:326 cps/editbooks.py:627 #: cps/editbooks.py:1146 cps/web.py:535 cps/web.py:1581 cps/web.py:1627 @@ -597,7 +598,7 @@ msgstr "Błąd otwierania e-booka. Plik nie istnieje lub jest niedostępny" #: cps/editbooks.py:673 cps/editbooks.py:1503 msgid "User has no rights to upload cover" -msgstr "" +msgstr "Użytkownik nie ma uprawnień do wgrywania okładek" #: cps/editbooks.py:694 cps/editbooks.py:938 msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier" @@ -606,7 +607,7 @@ msgstr "W identyfikatorach nie jest rozróżniana wielkość liter, nadpisywanie #: cps/editbooks.py:709 cps/editbooks.py:912 cps/editbooks.py:1273 #, python-format msgid "'%(langname)s' is not a valid language" -msgstr "" +msgstr "'%(langname)s' nie jest prawidłowym językiem" #: cps/editbooks.py:737 msgid "Metadata successfully updated" @@ -615,7 +616,7 @@ msgstr "Metadane zostały pomyślnie zaktualizowane" #: cps/editbooks.py:760 #, python-brace-format msgid "Error editing book: {}" -msgstr "" +msgstr "Błąd podczas edycji książki: {}" #: cps/editbooks.py:856 msgid "Uploaded book probably exists in the library, consider to change before upload new: " @@ -623,7 +624,7 @@ msgstr "Wysłana książka prawdopodobnie istnieje w bibliotece, rozważ zmianę #: cps/editbooks.py:950 cps/editbooks.py:1420 msgid "File type isn't allowed to be uploaded to this server" -msgstr "" +msgstr "Ten typ pliku nie jest dozwolony na serwerze." #: cps/editbooks.py:956 cps/editbooks.py:1431 #, python-format @@ -654,12 +655,12 @@ msgstr "Książka została usunięta" #: cps/editbooks.py:1065 msgid "You are missing permissions to delete books" -msgstr "" +msgstr "Nie masz uprawnień do usuwania książek" #: cps/editbooks.py:1139 #, python-brace-format msgid "Book with id \"{}\" could not be deleted: not found" -msgstr "" +msgstr "Książka z id \"{}\" nie mogła zostać usunięta, ponieważ nie istnieje" #: cps/editbooks.py:1181 msgid "edit metadata" @@ -668,11 +669,11 @@ msgstr "edytuj metadane" #: cps/editbooks.py:1234 #, python-format msgid "Seriesindex: %(seriesindex)s is not a valid number, skipping" -msgstr "" +msgstr "Indeks: %(seriesindex)s nie jest prawidłową liczbą, pomijam" #: cps/editbooks.py:1425 msgid "User has no rights to upload additional file formats" -msgstr "" +msgstr "Użytkownik nie ma uprawnień do wgrywania dodatkowych formatów plików" #: cps/editbooks.py:1449 #, python-format @@ -714,19 +715,19 @@ msgstr "%(format)s nie znaleziono: %(fn)s" #: cps/helper.py:104 cps/helper.py:233 cps/templates/detail.html:66 msgid "Send to eReader" -msgstr "" +msgstr "Wysłano na czytnik" #: cps/helper.py:105 cps/helper.py:125 cps/helper.py:235 msgid "This Email has been sent via Calibre-Web." -msgstr "" +msgstr "Ten email został wysłany przez Calibre-Web" #: cps/helper.py:123 msgid "Calibre-Web Test Email" -msgstr "" +msgstr "Calibre-Web Email Testowy" #: cps/helper.py:124 msgid "Test Email" -msgstr "" +msgstr "Email Testowy" # ??? #: cps/helper.py:141 @@ -736,22 +737,22 @@ msgstr "Zacznij korzystać z Calibre-Web" #: cps/helper.py:146 #, python-format msgid "Registration Email for user: %(name)s" -msgstr "" +msgstr "Email rejestracyjny dla użytkownika: %(name)s" #: cps/helper.py:157 cps/helper.py:163 #, python-format msgid "Convert %(orig)s to %(format)s and send to eReader" -msgstr "" +msgstr "Przekonwertuj %(orig)s na %(format)s i wyślij na czytnik" #: cps/helper.py:182 cps/helper.py:186 cps/helper.py:190 #, python-format msgid "Send %(format)s to eReader" -msgstr "" +msgstr "Wyślij %(format)s na czytnik" #: cps/helper.py:230 #, python-format msgid "%(book)s send to eReader" -msgstr "" +msgstr "%(book)s wysłano na czytnik" # ??? #: cps/helper.py:237 @@ -761,7 +762,7 @@ msgstr "Żądany plik nie mógł zostać odczytany. Może brakuje uprawnień?" #: cps/helper.py:350 #, python-brace-format msgid "Read status could not set: {}" -msgstr "" +msgstr "Status przeczytania nie mógł zostać ustawiony jako: {}" #: cps/helper.py:373 #, python-format @@ -776,17 +777,17 @@ msgstr "Usuwanie książki %(id)s zakończyło się błędem: %(message)s" #: cps/helper.py:390 #, python-format msgid "Deleting book %(id)s from database only, book path in database not valid: %(path)s" -msgstr "" +msgstr "Usuwanie książki o ID: %(id)s z bazy danych, ścieżka książki jest nieprawidłowa: %(path)s" #: cps/helper.py:404 #, python-format msgid "Moving book path of Book %(book_id)s to: '%(src)s' failed with error: %(error)s" -msgstr "" +msgstr "Przenoszenie książki o ID: %(book_id)s do: '%(src)s' nie powiodło się z powodu następującego błędu: %(error)s" #: cps/helper.py:443 #, python-format msgid "Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "" +msgstr "Zmiana autora z: '%(src)s' na '%(dest)s' z powodu błędu: %(error)s" #: cps/helper.py:513 cps/helper.py:522 #, python-format @@ -805,7 +806,7 @@ msgstr "Nie znaleziono ścieżki do książki %(path)s na Google Drive" #: cps/helper.py:643 msgid "Found an existing account for this Email address" -msgstr "" +msgstr "Istnieje już konto z podanym adresem email" #: cps/helper.py:651 msgid "This username is already taken" @@ -813,15 +814,15 @@ msgstr "Nazwa użytkownika jest już zajęta" #: cps/helper.py:665 msgid "Invalid Email address format" -msgstr "" +msgstr "Nieprawidłowy format adresu email" #: cps/helper.py:687 msgid "Password doesn't comply with password validation rules" -msgstr "" +msgstr "Hasło nie spełnia wymagań" #: cps/helper.py:833 msgid "Python module 'advocate' is not installed but is needed for cover uploads" -msgstr "" +msgstr "Moduł Python 'advocate' nie jest zainstalowany, a jest wymagany do wgrywania okładek" #: cps/helper.py:843 msgid "Error Downloading Cover" @@ -833,7 +834,7 @@ msgstr "Błędny format okładki" #: cps/helper.py:849 msgid "You are not allowed to access localhost or the local network for cover uploads" -msgstr "" +msgstr "Nie możesz aktualizowań okładek poprzez localhost czy sieć lokalną" #: cps/helper.py:859 msgid "Failed to create path for cover" @@ -849,7 +850,7 @@ msgstr "Jako plik okładki obsługiwane są tylko pliki jpg/jpeg/png/webp/bmp" #: cps/helper.py:898 msgid "Invalid cover file content" -msgstr "" +msgstr "Nieprawidłowa zawartość pliku z okładką" #: cps/helper.py:902 msgid "Only jpg/jpeg files are supported as coverfile" @@ -857,7 +858,7 @@ msgstr "Jako plik okładki dopuszczalne są jedynie pliki jpg/jpeg" #: cps/helper.py:975 cps/helper.py:1135 msgid "Cover" -msgstr "" +msgstr "Okładka" #: cps/helper.py:992 msgid "UnRar binary file not found" @@ -865,41 +866,41 @@ msgstr "Plik wykonywalny programu unrar nie znaleziony" #: cps/helper.py:1003 msgid "Error executing UnRar" -msgstr "" +msgstr "Błąd podczas uruchamiania UnRar" #: cps/helper.py:1011 msgid "Could not find the specified directory" -msgstr "" +msgstr "Nie udało się nie udało się znaleźć wskazanego folderu" #: cps/helper.py:1014 msgid "Please specify a directory, not a file" -msgstr "" +msgstr "Proszę wskaż folder a nie plik." #: cps/helper.py:1028 msgid "Calibre binaries not viable" -msgstr "" +msgstr "Pliki binarne Calibre są nieprawidłowe" #: cps/helper.py:1037 #, python-format msgid "Missing calibre binaries: %(missing)s" -msgstr "" +msgstr "Brakuje plików binarnych calibre: %(missing)s" #: cps/helper.py:1039 #, python-format msgid "Missing executable permissions: %(missing)s" -msgstr "" +msgstr "Brakuje uprawnień do wykonywania: %(missing)s" #: cps/helper.py:1044 msgid "Error executing Calibre" -msgstr "" +msgstr "Błąd podczas uruchamiania Calibre" #: cps/helper.py:1137 cps/templates/admin.html:216 msgid "Queue all books for metadata backup" -msgstr "" +msgstr "Zaplanuj backup metadanych dla wszystkich książek" #: cps/kobo_auth.py:92 msgid "Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device" -msgstr "" +msgstr "Proszę uzyskać dostęp do Calibre-Web z adresu innego niż localhost, aby otrzymać poprawny api_endpoint dla urządzenia Kobo" #: cps/kobo_auth.py:118 msgid "Kobo Setup" @@ -1037,7 +1038,7 @@ msgstr "Przeczytane" #: cps/render_template.py:63 msgid "Show Read and Unread" -msgstr "" +msgstr "Pokaż przeczytanie i nieprzeczytani" #: cps/render_template.py:65 cps/templates/index.xml:70 #: cps/templates/index.xml:74 cps/web.py:775 @@ -1064,7 +1065,7 @@ msgstr "Kategorie" #: cps/render_template.py:73 cps/templates/user_table.html:158 msgid "Show Category Section" -msgstr "" +msgstr "Pokaż sekcję kategorii" #: cps/render_template.py:74 cps/templates/book_edit.html:86 #: cps/templates/book_table.html:94 cps/templates/index.xml:106 @@ -1074,7 +1075,7 @@ msgstr "Cykle" #: cps/render_template.py:76 cps/templates/user_table.html:157 msgid "Show Series Section" -msgstr "" +msgstr "Pokaż sekcję serii" #: cps/render_template.py:77 cps/templates/book_table.html:92 #: cps/templates/index.xml:79 @@ -1083,7 +1084,7 @@ msgstr "Autorzy" #: cps/render_template.py:79 cps/templates/user_table.html:160 msgid "Show Author Section" -msgstr "" +msgstr "Pokaż sekcję autorów" #: cps/render_template.py:81 cps/templates/book_table.html:98 #: cps/templates/index.xml:88 cps/web.py:1004 @@ -1092,7 +1093,7 @@ msgstr "Wydawcy" #: cps/render_template.py:83 cps/templates/user_table.html:163 msgid "Show Publisher Section" -msgstr "" +msgstr "Pokaż sekcję wydawnictw" #: cps/render_template.py:84 cps/templates/book_table.html:96 #: cps/templates/index.xml:115 cps/templates/search_form.html:108 @@ -1102,7 +1103,7 @@ msgstr "Języki" #: cps/render_template.py:87 cps/templates/user_table.html:155 msgid "Show Language Section" -msgstr "" +msgstr "Pokaż sekcję języków" #: cps/render_template.py:88 cps/templates/index.xml:124 msgid "Ratings" @@ -1110,7 +1111,7 @@ msgstr "Oceny" #: cps/render_template.py:90 cps/templates/user_table.html:164 msgid "Show Ratings Section" -msgstr "" +msgstr "Pokaż sekcję ocen" #: cps/render_template.py:91 cps/templates/index.xml:133 msgid "File formats" @@ -1118,7 +1119,7 @@ msgstr "Formaty plików" #: cps/render_template.py:93 cps/templates/user_table.html:165 msgid "Show File Formats Section" -msgstr "" +msgstr "Pokaż sekcję formatów plików" #: cps/render_template.py:95 cps/web.py:798 msgid "Archived Books" @@ -1126,7 +1127,7 @@ msgstr "Zarchiwizowane książki" #: cps/render_template.py:97 cps/templates/user_table.html:166 msgid "Show Archived Books" -msgstr "" +msgstr "Pokaż sekcję zarchiwizowanych książek" #: cps/render_template.py:100 cps/web.py:834 msgid "Books List" @@ -1157,11 +1158,11 @@ msgstr "Ocena >= %(rating)s" #: cps/search.py:234 #, python-format msgid "Read Status = '%(status)s'" -msgstr "" +msgstr "Status przeczytania = '%(status)s'" #: cps/search.py:351 msgid "Error on search for custom columns, please restart Calibre-Web" -msgstr "" +msgstr "Błąd w przeszukiwaniu niestandardowych kolumn, proszę zresetuj Calibre-WEb" #: cps/search.py:370 cps/search.py:402 cps/templates/layout.html:57 msgid "Advanced Search" @@ -1173,7 +1174,7 @@ msgstr "Podano niewłaściwą półkę" #: cps/shelf.py:55 msgid "Sorry you are not allowed to add a book to that shelf" -msgstr "" +msgstr "Niestety nie masz uprawnień do dodawania książek na tą półkę" #: cps/shelf.py:64 #, python-format @@ -1183,7 +1184,7 @@ msgstr "Książka jest już dodana do półki: %(shelfname)s" #: cps/shelf.py:77 #, python-format msgid "%(book_id)s is a invalid Book Id. Could not be added to Shelf" -msgstr "" +msgstr "%(book_id)s jest nieprawidłowym numerem ID książki. Nie udało się dodać na półkę." #: cps/shelf.py:97 #, python-format @@ -1192,7 +1193,7 @@ msgstr "Książka została dodana do półki: %(sname)s" #: cps/shelf.py:116 msgid "You are not allowed to add a book to the shelf" -msgstr "" +msgstr "Nie masz uprawnień do dodawania książek na półkę" #: cps/shelf.py:134 #, python-format @@ -1216,7 +1217,7 @@ msgstr "Książka została usunięta z półki: %(sname)s" #: cps/shelf.py:208 msgid "Sorry you are not allowed to remove a book from this shelf" -msgstr "" +msgstr "Niestety nie masz uprawnień do usuwania książek z tej półki" #: cps/shelf.py:218 cps/templates/layout.html:159 msgid "Create a Shelf" @@ -1224,7 +1225,7 @@ msgstr "Utwórz półkę" #: cps/shelf.py:226 msgid "Sorry you are not allowed to edit this shelf" -msgstr "" +msgstr "Niestety nie masz uprawnień do edytowania tej półki" #: cps/shelf.py:228 msgid "Edit a shelf" @@ -1232,11 +1233,11 @@ msgstr "Edytuj półkę" #: cps/shelf.py:237 msgid "Error deleting Shelf" -msgstr "" +msgstr "Błąd podczas usuwania półki" #: cps/shelf.py:239 msgid "Shelf successfully deleted" -msgstr "" +msgstr "Półka poprawnie usunięta" #: cps/shelf.py:289 #, python-format @@ -1245,7 +1246,7 @@ msgstr "Zmieniono kolejność półki: '%(name)s'" #: cps/shelf.py:324 msgid "Sorry you are not allowed to create a public shelf" -msgstr "" +msgstr "Niestety nie masz uprawnień do tworzenia publicznych półek" #: cps/shelf.py:341 #, python-format @@ -1303,11 +1304,11 @@ msgstr "Zakończone" #: cps/tasks_status.py:71 msgid "Ended" -msgstr "" +msgstr "Zakończone" #: cps/tasks_status.py:73 msgid "Cancelled" -msgstr "" +msgstr "Anulowane" #: cps/tasks_status.py:75 msgid "Unknown Status" @@ -1372,7 +1373,7 @@ msgstr "Cykl: %(serie)s" #: cps/web.py:629 msgid "Rating: None" -msgstr "" +msgstr "Ocena: Brak" #: cps/web.py:638 #, python-format @@ -1408,7 +1409,7 @@ msgstr "Lista formatów" #: cps/web.py:1264 msgid "Please configure the SMTP mail settings first..." -msgstr "" +msgstr "Proszę najpierw skonfigurować ustawienia SMTP poczty." #: cps/web.py:1270 #, python-format @@ -1422,11 +1423,11 @@ msgstr "Wystąpił błąd podczas wysyłania tej książki: %(res)s" #: cps/web.py:1275 msgid "Oops! Please update your profile with a valid eReader Email." -msgstr "" +msgstr "Ups! Zaktualizuj swój profil używając poprawnego adresu email czytnika." #: cps/web.py:1291 msgid "Please wait one minute to register next user" -msgstr "" +msgstr "Proszę poczekać minutę przed rejestracją kolejnego użytkownika." #: cps/templates/layout.html:69 cps/templates/layout.html:104 #: cps/templates/login.html:27 cps/templates/register.html:17 cps/web.py:1292 @@ -1437,7 +1438,7 @@ msgstr "Zarejestruj się" #: cps/web.py:1295 cps/web.py:1398 msgid "Connection error to limiter backend, please contact your administrator" -msgstr "" +msgstr "Błąd połączenia z backendem limitera, skontaktuj się z administratorem." #: cps/web.py:1300 cps/web.py:1347 msgid "Oops! Email server is not configured, please contact your administrator." @@ -1445,7 +1446,7 @@ msgstr "Serwer e-mail nie jest skonfigurowany, skontaktuj się z administratorem #: cps/web.py:1333 msgid "Oops! Your Email is not allowed." -msgstr "Twój e-mail nie może się zarejestrować" +msgstr ".Twój e-mail nie może się zarejestrować" #: cps/web.py:1336 msgid "Success! Confirmation Email has been sent." @@ -1453,47 +1454,47 @@ msgstr "Wiadomość e-mail z potwierdzeniem została wysłana na Twoje konto e-m #: cps/web.py:1381 cps/web.py:1404 msgid "Cannot activate LDAP authentication" -msgstr "" +msgstr "Nie udało się aktywować autoryzacji za pomocą LDAP" #: cps/web.py:1394 msgid "Please wait one minute before next login" -msgstr "" +msgstr "Proszę poczekać minutę przed kolejnym logowaniem" #: cps/web.py:1413 #, python-format msgid "you are now logged in as: '%(nickname)s'" -msgstr "" +msgstr "jesteś zalogowany jako: '%(nickname)s'" #: cps/web.py:1420 #, python-format msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" -msgstr "" +msgstr "Logowanie awaryjne jako: ‘%(nickname)s’, serwer LDAP jest nieosiągalny lub użytkownik nie jest znany." #: cps/web.py:1425 #, python-format msgid "Could not login: %(message)s" -msgstr "" +msgstr "Nie udało się zalogować: %(message)s" #: cps/web.py:1429 cps/web.py:1454 msgid "Wrong Username or Password" -msgstr "" +msgstr "Nieprawidłowy login lub hasło" #: cps/web.py:1436 msgid "New Password was sent to your email address" -msgstr "" +msgstr "Nowe hasło zostało wysłane na twój adres email" #: cps/web.py:1440 msgid "An unknown error occurred. Please try again later." -msgstr "" +msgstr "Wystąpił nieznany błąd. Proszę spróbować później." #: cps/web.py:1442 msgid "Please enter valid username to reset password" -msgstr "" +msgstr "Proszę wprowadzić prawidłowy login do resetu hasła" #: cps/web.py:1450 #, python-format msgid "You are now logged in as: '%(nickname)s'" -msgstr "" +msgstr "Jesteś zalogowany jako: '%(nickname)s'" #: cps/web.py:1515 cps/web.py:1565 #, python-format @@ -1502,11 +1503,11 @@ msgstr "Profil użytkownika %(name)s" #: cps/web.py:1531 msgid "Success! Profile Updated" -msgstr "" +msgstr "Sukces! Zaktualizowano profil" #: cps/web.py:1535 msgid "Oops! An account already exists for this Email." -msgstr "Znaleziono istniejące konto dla tego adresu e-mail" +msgstr "Znaleziono istniejące konto dla tego adresu e-mail." #: cps/services/gmail.py:59 msgid "Found no valid gmail.json file with OAuth information" @@ -1514,12 +1515,12 @@ msgstr "Nie znaleziono poprawnego pliku gmail.json z informacjami OAuth" #: cps/tasks/clean.py:29 msgid "Delete temp folder contents" -msgstr "" +msgstr "Usuń zawartość folderu tymczasowego" #: cps/tasks/convert.py:112 #, python-format msgid "%(book)s send to E-Reader" -msgstr "" +msgstr "%(book)s wysłano na czytnik" #: cps/tasks/convert.py:177 #, python-format @@ -1548,7 +1549,7 @@ msgstr "Konwertowany plik nie został znaleziony, lub więcej niż jeden plik w #: cps/tasks/convert.py:291 cps/tasks/convert.py:342 #, python-format msgid "Calibre failed with error: %(error)s" -msgstr "" +msgstr "Calibre zakończyło się błędem: %(error)s" #: cps/tasks/convert.py:319 #, python-format @@ -1557,38 +1558,38 @@ msgstr "Konwertowanie nie powiodło się: %(error)s" #: cps/tasks/convert.py:347 msgid "Convert" -msgstr "" +msgstr "Konwertuj" #: cps/tasks/database.py:26 msgid "Reconnecting Calibre database" -msgstr "" +msgstr "Ponowne połączenie z bazą danych Calibre" #: cps/tasks/mail.py:283 msgid "E-mail" -msgstr "" +msgstr "E-mail" #: cps/tasks/metadata_backup.py:34 msgid "Backing up Metadata" -msgstr "" +msgstr "Tworzenie kopii zapasowej metadanych" #: cps/tasks/thumbnail.py:97 #, python-format msgid "Generated %(count)s cover thumbnails" -msgstr "" +msgstr "Wygenerowano %(count)s miniaturek" #: cps/tasks/thumbnail.py:233 cps/tasks/thumbnail.py:448 #: cps/tasks/thumbnail.py:518 msgid "Cover Thumbnails" -msgstr "" +msgstr "Miniaturki" #: cps/tasks/thumbnail.py:294 #, python-brace-format msgid "Generated {0} series thumbnails" -msgstr "" +msgstr "Wygenerowano {0} miniaturek dla serii" #: cps/tasks/thumbnail.py:459 msgid "Clearing cover thumbnail cache" -msgstr "" +msgstr "Czyszczenie cache miniaturek" #: cps/tasks/upload.py:39 cps/templates/admin.html:20 #: cps/templates/layout.html:82 cps/templates/user_table.html:145 @@ -1612,7 +1613,7 @@ msgstr "E-mail" #: cps/templates/admin.html:15 msgid "Send to eReader Email" -msgstr "" +msgstr "Wyślij na email czytnika" # ??? #: cps/templates/admin.html:17 cps/templates/layout.html:93 @@ -1684,7 +1685,7 @@ msgstr "Wyślij z adresu e-mail" #: cps/templates/admin.html:90 msgid "Email Service" -msgstr "" +msgstr "Serwis Email" #: cps/templates/admin.html:91 msgid "Gmail via Oauth2" @@ -1752,37 +1753,37 @@ msgstr "Edytuj konfigurację interfejsu" #: cps/templates/admin.html:167 msgid "Scheduled Tasks" -msgstr "" +msgstr "Zaplanowane zadania" #: cps/templates/admin.html:170 cps/templates/schedule_edit.html:12 #: cps/templates/tasks.html:18 msgid "Start Time" -msgstr "" +msgstr "Czas początkowy" #: cps/templates/admin.html:174 cps/templates/schedule_edit.html:20 msgid "Maximum Duration" -msgstr "" +msgstr "Maksymalna długość" #: cps/templates/admin.html:178 cps/templates/schedule_edit.html:29 msgid "Generate Thumbnails" -msgstr "" +msgstr "Wygeneruj miniaturki" #: cps/templates/admin.html:182 msgid "Generate series cover thumbnails" -msgstr "" +msgstr "Wygeneruj miniaturki dla serii" #: cps/templates/admin.html:186 cps/templates/admin.html:208 #: cps/templates/schedule_edit.html:37 msgid "Reconnect Calibre Database" -msgstr "" +msgstr "Połącz ponownie z bazą danych Calibre" #: cps/templates/admin.html:190 cps/templates/schedule_edit.html:41 msgid "Generate Metadata Backup Files" -msgstr "" +msgstr "Wygeneruj kopie zapasową plików z metadanymi" #: cps/templates/admin.html:197 msgid "Refresh Thumbnail Cache" -msgstr "" +msgstr "Zresetuj cache miniaturek" #: cps/templates/admin.html:203 msgid "Administration" @@ -1806,7 +1807,7 @@ msgstr "Zatrzymaj Calibre Web" #: cps/templates/admin.html:221 msgid "Version Information" -msgstr "" +msgstr "Informacje o wersji" #: cps/templates/admin.html:225 msgid "Version" @@ -1911,7 +1912,7 @@ msgstr "Więcej według" #: cps/templates/listenmp3.html:62 #, python-format msgid "Book %(index)s of %(range)s" -msgstr "" +msgstr "Książka o numerze %(index)s z %(range)s" #: cps/templates/basic_detail.html:41 cps/templates/book_edit.html:106 #: cps/templates/detail.html:165 cps/templates/listenmp3.html:69 @@ -1966,7 +1967,7 @@ msgstr "Wyloguj się" #: cps/templates/basic_layout.html:35 msgid "Normal Theme" -msgstr "" +msgstr "Zwykły motyw" #: cps/templates/book_edit.html:11 msgid "Delete Book" @@ -2112,7 +2113,7 @@ msgstr "Słowo kluczowe" #: cps/templates/book_edit.html:240 msgid "Search keyword" -msgstr "" +msgstr "Wyszukaj słowo kluczowe" #: cps/templates/book_edit.html:246 msgid "Click the cover to load metadata to the form" @@ -2142,19 +2143,19 @@ msgstr "To pole jest wymagane" #: cps/templates/book_table.html:25 msgid "Archive selected books" -msgstr "" +msgstr "Zarchiwizuj zaznaczone książki" #: cps/templates/book_table.html:28 msgid "Unarchive selected books" -msgstr "" +msgstr "Odarchiwizuj książki" #: cps/templates/book_table.html:34 msgid "Mark selected books as read" -msgstr "" +msgstr "Oznacz zaznaczone książki jako przeczytane" #: cps/templates/book_table.html:37 msgid "Mark selected books as unread" -msgstr "" +msgstr "Oznacz zaznaczone książki jako nieprzeczytane" #: cps/templates/book_table.html:57 msgid "Merge selected books" @@ -2162,11 +2163,11 @@ msgstr "Łączenie wybranych książek" #: cps/templates/book_table.html:60 cps/templates/user_table.html:124 msgid "Clear selections" -msgstr "" +msgstr "Wyczyść zaznaczenie" #: cps/templates/book_table.html:63 msgid "Edit selected books" -msgstr "" +msgstr "Edytuj zaznaczone książki" #: cps/templates/book_table.html:67 msgid "Exchange author and title" @@ -2174,11 +2175,11 @@ msgstr "Zamień autora i tytuł" #: cps/templates/book_table.html:73 msgid "Update Title Sort automatically" -msgstr "" +msgstr "Automatycznie zaktualizuj sortowanie po tytule" #: cps/templates/book_table.html:77 msgid "Update Author Sort automatically" -msgstr "" +msgstr "Automatycznie zaktualizuj sortowanie po autorze" #: cps/templates/book_table.html:89 cps/templates/book_table.html:95 msgid "Enter Title" @@ -2235,15 +2236,15 @@ msgstr "Wpisz Wydawnictwa" #: cps/templates/book_table.html:99 msgid "Enter comments" -msgstr "" +msgstr "Dodaj komentarze" #: cps/templates/book_table.html:99 msgid "Comments" -msgstr "" +msgstr "Komentarze" #: cps/templates/book_table.html:101 msgid "Archive Status" -msgstr "" +msgstr "Status archiwizacji" #: cps/templates/book_table.html:103 cps/templates/search_form.html:42 msgid "Read Status" @@ -2254,11 +2255,11 @@ msgstr "Status odczytu" #: cps/templates/book_table.html:116 cps/templates/book_table.html:118 #: cps/templates/book_table.html:122 msgid "Enter " -msgstr "" +msgstr "Wprowadź" #: cps/templates/book_table.html:128 msgid "Delete selected books" -msgstr "" +msgstr "Usuń zaznaczone książki" #: cps/templates/book_table.html:148 cps/templates/book_table.html:172 #: cps/templates/book_table.html:192 cps/templates/book_table.html:212 @@ -2269,11 +2270,11 @@ msgstr "Czy jesteś pewny?" #: cps/templates/book_table.html:152 msgid "Books with Title will be merged from:" -msgstr "" +msgstr "Książki o podanym tytule zostaną złączone:" #: cps/templates/book_table.html:156 msgid "Into Book with Title:" -msgstr "" +msgstr "Do książki o tytule:" #: cps/templates/book_table.html:161 msgid "Merge" @@ -2281,39 +2282,39 @@ msgstr "Połącz" #: cps/templates/book_table.html:176 msgid "The following books will be deleted:" -msgstr "" +msgstr "Następujące książki zostaną usunięte:" #: cps/templates/book_table.html:196 msgid "The following books will be archived:" -msgstr "" +msgstr "Następujące książki zostaną zarchiwizowane:" #: cps/templates/book_table.html:200 cps/templates/detail.html:275 msgid "Archive" -msgstr "" +msgstr "Zarchiwizuj" #: cps/templates/book_table.html:216 msgid "The following books will be unarchived:" -msgstr "" +msgstr "Następujące książki zostaną odarchiwizowane:" #: cps/templates/book_table.html:220 msgid "Unarchive" -msgstr "" +msgstr "Odarchiwizuj" #: cps/templates/book_table.html:236 msgid "The following books will be marked read:" -msgstr "" +msgstr "Następujące książki zostaną oznaczone jako przeczytane:" #: cps/templates/book_table.html:240 msgid "Mark as read" -msgstr "" +msgstr "Oznacz jako przeczytane" #: cps/templates/book_table.html:256 msgid "The following books will be marked unread:" -msgstr "" +msgstr "Następujące książki zostaną oznaczone jako nieprzeczytane:" #: cps/templates/book_table.html:260 msgid "Mark as unread" -msgstr "" +msgstr "Oznacz jako nieprzeczytane:" #: cps/templates/book_table.html:272 cps/templates/detail.html:348 msgid "Edit Metadata" @@ -2321,7 +2322,7 @@ msgstr "Edytuj metadane" #: cps/templates/book_table.html:275 msgid "Edit the fields you want changed. Blank fields will be ignored:" -msgstr "" +msgstr "Edytuj pola, które chcesz zmienić. Puste pola zostaną zignorowane:" #: cps/templates/config_db.html:12 msgid "Location of Calibre Database" @@ -2329,7 +2330,7 @@ msgstr "Lokalizacja bazy danych Calibre" #: cps/templates/config_db.html:21 msgid "Separate Book Files from Library" -msgstr "" +msgstr "Oddziel pliki książki od biblioteki" #: cps/templates/config_db.html:34 msgid "Use Google Drive?" @@ -2346,7 +2347,7 @@ msgstr "Folder Calibre na Google Drive" # ??? #: cps/templates/config_db.html:52 msgid "Metadata Watch Channel ID" -msgstr "" +msgstr "Id kanału do monitorowania metadanych" #: cps/templates/config_db.html:55 msgid "Revoke" @@ -2386,7 +2387,7 @@ msgstr "Wydania nocne" #: cps/templates/config_edit.html:50 msgid "Trusted Hosts (Comma Separated)" -msgstr "" +msgstr "Zaufane źródła (oddzielone przecinkiem)" #: cps/templates/config_edit.html:61 msgid "Logfile Configuration" @@ -2410,11 +2411,11 @@ msgstr "Konfiguracja funkcjonalności" #: cps/templates/config_edit.html:104 msgid "Convert non-English characters in title and author while saving to disk" -msgstr "" +msgstr "Konwertuj nieanglojęzyczne znaki w tytule i danych autora podczas zapisywania na dysk" #: cps/templates/config_edit.html:108 msgid "Embed Metadata to Ebook File on Download/Conversion/e-mail (needs Calibre/Kepubify binaries)" -msgstr "" +msgstr "Osadzaj metadane w pliku e-booka podczas pobierania/konwersji/wysyłania e-mailem (wymaga plików wykonywalnych Calibre/Kepubify)" #: cps/templates/config_edit.html:112 msgid "Enable Uploads" @@ -2422,7 +2423,7 @@ msgstr "Włącz wysyłanie" #: cps/templates/config_edit.html:112 msgid "(Please ensure that users also have upload permissions)" -msgstr "" +msgstr "(Proszę upewnij się, że wybrani użytkownicy mają uprawnienia do wgrywania plików)" #: cps/templates/config_edit.html:116 msgid "Allowed Upload Fileformats" @@ -2466,7 +2467,7 @@ msgstr "Klucz API Goodreads" #: cps/templates/config_edit.html:167 msgid "Google Books API Key" -msgstr "" +msgstr "Klucz API do Google Books" #: cps/templates/config_edit.html:172 msgid "Allow Reverse Proxy Authentication" @@ -2609,7 +2610,7 @@ msgstr "Zewnętrzne pliki" #: cps/templates/config_edit.html:329 msgid "Path to Calibre Binaries" -msgstr "" +msgstr "Ścieżka do plików binarnych Calibre" #: cps/templates/config_edit.html:337 msgid "Calibre E-Book Converter Settings" @@ -2621,67 +2622,67 @@ msgstr "Ścieżka do konwertera Kepubify" #: cps/templates/config_edit.html:348 msgid "Location of Unrar binary" -msgstr "" +msgstr "Ścieżka do plików binarnych Unrar" #: cps/templates/config_edit.html:364 msgid "Security Settings" -msgstr "" +msgstr "Ustawienia bezpieczeństwa" #: cps/templates/config_edit.html:372 msgid "Limit failed login attempts" -msgstr "" +msgstr "Ogranicz liczbę nieudanych prób logowania" #: cps/templates/config_edit.html:376 msgid "Configure Backend for Limiter" -msgstr "" +msgstr "Skonfiguruj backend dla limitera" #: cps/templates/config_edit.html:380 msgid "Options for Limiter Backend" -msgstr "" +msgstr "Ustawienia dla backend limitera" #: cps/templates/config_edit.html:386 msgid "Check if file extensions matches file content on upload" -msgstr "" +msgstr "Sprawdź czy rozszerzenie pliku jest zgodne z zawartością pliku podczas wgrywania" #: cps/templates/config_edit.html:389 msgid "Session protection" -msgstr "" +msgstr "Bezpieczeństwo sesji" #: cps/templates/config_edit.html:391 msgid "Basic" -msgstr "" +msgstr "Bazowe" #: cps/templates/config_edit.html:392 msgid "Strong" -msgstr "" +msgstr "Silne" #: cps/templates/config_edit.html:397 msgid "User Password policy" -msgstr "" +msgstr "Polityka haseł użytkowników" #: cps/templates/config_edit.html:401 msgid "Minimum password length" -msgstr "" +msgstr "Minimalna długość hasła" #: cps/templates/config_edit.html:406 msgid "Enforce number" -msgstr "" +msgstr "Wymuś cyfrę" #: cps/templates/config_edit.html:410 msgid "Enforce lowercase characters" -msgstr "" +msgstr "Wymuś małe znaki" #: cps/templates/config_edit.html:414 msgid "Enforce uppercase characters" -msgstr "" +msgstr "Wymuś duże znaki" #: cps/templates/config_edit.html:418 msgid "Enforce characters (needed For Chinese/Japanese/Korean Characters)" -msgstr "" +msgstr "Wymuś znaki (wymagane dla Chińskich/Japońskich/Koreańskich Znaków)" #: cps/templates/config_edit.html:422 msgid "Enforce special characters" -msgstr "" +msgstr "Wymuś znaki specjalne" #: cps/templates/config_view_edit.html:17 msgid "View Configuration" @@ -2761,11 +2762,11 @@ msgstr "Zezwalaj na edycję półek publicznych" #: cps/templates/config_view_edit.html:123 msgid "Default Language" -msgstr "" +msgstr "Podstawowy język" #: cps/templates/config_view_edit.html:131 msgid "Default Visible Language of Books" -msgstr "" +msgstr "Podstawowy język widoczny dla książek" #: cps/templates/config_view_edit.html:147 msgid "Default Visibilities for New Users" @@ -2802,7 +2803,7 @@ msgstr "Oznacz jako przeczytane" #: cps/templates/detail.html:262 msgid "Mark Book as Read or Unread" -msgstr "" +msgstr "Oznacz książkę jako przeczytana lub nieprzeczytania" #: cps/templates/detail.html:262 cps/templates/listenmp3.html:159 msgid "Read" @@ -2818,7 +2819,7 @@ msgstr "Dodaj do archiwum" #: cps/templates/detail.html:275 msgid "Mark Book as archived or not, to hide it in Calibre-Web and delete it from Kobo Reader" -msgstr "" +msgstr "Oznacz książkę jako zarchiwizowana lub nie, aby ukryć ją w Calibre-Web i usunąć z czytnika Kobo" #: cps/templates/detail.html:301 cps/templates/listenmp3.html:190 #: cps/templates/search.html:16 @@ -2838,15 +2839,15 @@ msgstr "Wybierz typ serwera" #: cps/templates/email_edit.html:15 msgid "Standard Email Account" -msgstr "" +msgstr "Podstawowe konto email" #: cps/templates/email_edit.html:16 msgid "Gmail Account" -msgstr "" +msgstr "Konto Gmail" #: cps/templates/email_edit.html:22 msgid "Setup Gmail Account" -msgstr "" +msgstr "Skonfiguruj konto Gmail" #: cps/templates/email_edit.html:24 msgid "Revoke Gmail Access" @@ -2870,7 +2871,7 @@ msgstr "Limit rozmiaru załącznika" #: cps/templates/email_edit.html:66 msgid "Save and Send Test Email" -msgstr "" +msgstr "Zapisz i wyślij email testowy" #: cps/templates/email_edit.html:70 cps/templates/layout.html:26 #: cps/templates/shelf_order.html:42 cps/templates/user_table.html:174 @@ -2904,11 +2905,11 @@ msgstr "Otwórz plik .kobo/Kobo/Kobo eReader.conf w edytorze tekstu i dodaj (lub #: cps/templates/generate_kobo_auth_url.html:11 msgid "Kobo Token:" -msgstr "" +msgstr "Token Kobo:" #: cps/templates/grid.html:21 msgid "List" -msgstr "" +msgstr "Lista" #: cps/templates/http_error.html:34 msgid "Calibre-Web Instance is unconfigured, please contact your administrator" @@ -2921,7 +2922,7 @@ msgstr "Zgłoś błąd" #: cps/templates/http_error.html:52 msgid "Return to Database config" -msgstr "" +msgstr "Wróć do ustawień bazy danych" # ??? #: cps/templates/http_error.html:54 @@ -2930,15 +2931,15 @@ msgstr "Powrót do głównego menu" #: cps/templates/http_error.html:57 msgid "Logout User" -msgstr "" +msgstr "Wyloguj użytkownika" #: cps/templates/index.html:71 msgid "Sort ascending according to download count" -msgstr "" +msgstr "Sortuj rosnąco na podstawie liczby pobrań" #: cps/templates/index.html:72 msgid "Sort descending according to download count" -msgstr "" +msgstr "Sortuj malejąco na podstawie liczby pobrań" #: cps/templates/index.html:78 cps/templates/search.html:35 #: cps/templates/shelf.html:24 @@ -3033,7 +3034,7 @@ msgstr "Przełącz nawigację" #: cps/templates/layout.html:58 msgid "Simple Theme" -msgstr "" +msgstr "Motyw podstawowy" #: cps/templates/layout.html:66 cps/templates/layout.html:96 msgid "Account" @@ -3062,7 +3063,7 @@ msgstr "Szczegóły książki" #: cps/templates/list.html:22 msgid "Grid" -msgstr "" +msgstr "Siatka" #: cps/templates/listenmp3.html:167 msgid "Archived" @@ -3134,7 +3135,7 @@ msgstr "Ten format książki zostanie trwale usunięty z bazy danych" #: cps/templates/modal_dialogs.html:51 msgid "This book will be permanently erased from database and hard disk" -msgstr "" +msgstr "Ta książka zostanie usunięta na stałe z bazy danych i dysku" #: cps/templates/modal_dialogs.html:55 msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." @@ -3178,11 +3179,11 @@ msgstr "Katalog e-booków Calibre-Web" #: cps/templates/read.html:7 msgid "epub Reader" -msgstr "" +msgstr "Czytnik ebooków" #: cps/templates/read.html:85 msgid "Choose a theme below:" -msgstr "" +msgstr "Wybierz motyw poniżej:" #: cps/templates/read.html:89 cps/templates/readcbr.html:104 msgid "Light" @@ -3194,15 +3195,15 @@ msgstr "Ciemny" #: cps/templates/read.html:93 msgid "Sepia" -msgstr "" +msgstr "Sepia" #: cps/templates/read.html:95 msgid "Amber" -msgstr "" +msgstr "Bursztyn" #: cps/templates/read.html:97 msgid "Black" -msgstr "" +msgstr "Czarny" #: cps/templates/read.html:109 msgid "Reflow text when sidebars are open." @@ -3210,52 +3211,52 @@ msgstr "Przepływ tekstu, gdy paski boczne są otwarte." #: cps/templates/read.html:114 msgid "Show pages count" -msgstr "" +msgstr "Pokaż liczbę stron" #: cps/templates/read.html:118 msgid "Font Size" -msgstr "" +msgstr "Rozmiar czcionki" #: cps/templates/read.html:126 msgid "Font" -msgstr "" +msgstr "Czcionka" # ??? #: cps/templates/read.html:127 msgid "Default" -msgstr "" +msgstr "Podstawowa" #: cps/templates/read.html:128 msgid "Yahei" -msgstr "" +msgstr "Yahei" #: cps/templates/read.html:129 msgid "SimSun" -msgstr "" +msgstr "SimSun" #: cps/templates/read.html:130 msgid "KaiTi" -msgstr "" +msgstr "KaiTi" #: cps/templates/read.html:131 msgid "Arial" -msgstr "" +msgstr "Arial" #: cps/templates/read.html:134 msgid "Spread" -msgstr "" +msgstr "Rozkład" #: cps/templates/read.html:135 msgid "Two columns" -msgstr "" +msgstr "Dwie kolumny" #: cps/templates/read.html:136 msgid "One column" -msgstr "" +msgstr "Jedna kolumna" #: cps/templates/readcbr.html:8 msgid "Comic Reader" -msgstr "" +msgstr "Czytnik komiksów" #: cps/templates/readcbr.html:75 msgid "Keyboard Shortcuts" @@ -3271,11 +3272,11 @@ msgstr "Następna strona" #: cps/templates/readcbr.html:80 msgid "Single Page Display" -msgstr "" +msgstr "Wyświetlanie jednej strony" #: cps/templates/readcbr.html:81 msgid "Long Strip Display" -msgstr "" +msgstr "Wyświetlanie długiego paska" #: cps/templates/readcbr.html:82 msgid "Scale to Best" @@ -3307,15 +3308,15 @@ msgstr "Odwórć obraz" #: cps/templates/readcbr.html:110 msgid "Display" -msgstr "" +msgstr "Wyświetl" #: cps/templates/readcbr.html:113 msgid "Single Page" -msgstr "" +msgstr "Pojedynczą strona" #: cps/templates/readcbr.html:114 msgid "Long Strip" -msgstr "" +msgstr "Długi pasek" #: cps/templates/readcbr.html:119 msgid "Scale" @@ -3367,11 +3368,11 @@ msgstr "Od prawej do lewej" #: cps/templates/readcbr.html:162 msgid "Reset to Top" -msgstr "" +msgstr "Przenieś na górę" #: cps/templates/readcbr.html:163 msgid "Remember Position" -msgstr "" +msgstr "Zapamiętaj pozycję" #: cps/templates/readcbr.html:168 msgid "Scrollbar" @@ -3387,15 +3388,15 @@ msgstr "Ukryj" #: cps/templates/readdjvu.html:5 msgid "DJVU Reader" -msgstr "" +msgstr "Czytnik DJVU" #: cps/templates/readpdf.html:31 msgid "PDF Reader" -msgstr "" +msgstr "Czytnik PDF" #: cps/templates/readtxt.html:6 msgid "txt Reader" -msgstr "" +msgstr "Czytnik txt" #: cps/templates/register.html:4 msgid "Register New Account" @@ -3427,7 +3428,7 @@ msgstr "Link wygaśnie po 10 minutach." #: cps/templates/schedule_edit.html:33 msgid "Generate Series Cover Thumbnails" -msgstr "" +msgstr "Wygeneruj miniaturki dla Serii" #: cps/templates/search.html:7 msgid "Search Term:" @@ -3447,11 +3448,11 @@ msgstr "Data publikacji do" #: cps/templates/search_form.html:44 cps/templates/search_form.html:165 msgid "Any" -msgstr "" +msgstr "Dowolny" #: cps/templates/search_form.html:45 cps/templates/search_form.html:166 msgid "Empty" -msgstr "" +msgstr "Pusty" #: cps/templates/search_form.html:60 msgid "Exclude Tags" @@ -3517,11 +3518,11 @@ msgstr "Włącz polecenie zmiany" #: cps/templates/shelf.html:28 msgid "Sort according to book added to shelf, newest first" -msgstr "" +msgstr "Sortuj książki według daty dodania do półki, najnowsze jako pierwsze" #: cps/templates/shelf.html:29 msgid "Sort according to book added to shelf, oldest first" -msgstr "" +msgstr "Sortuj książki według daty dodania do półki, najstarsze jako pierwsze" #: cps/templates/shelf_edit.html:14 msgid "Share with Everyone" @@ -3565,7 +3566,7 @@ msgstr "Statystyki systemu" #: cps/templates/stats.html:33 msgid "Program" -msgstr "" +msgstr "Program" #: cps/templates/stats.html:34 msgid "Installed Version" @@ -3594,19 +3595,19 @@ msgstr "Czas wykonania" #: cps/templates/tasks.html:19 msgid "Message" -msgstr "" +msgstr "Wiadomość" #: cps/templates/tasks.html:21 msgid "Actions" -msgstr "" +msgstr "Akcje" #: cps/templates/tasks.html:41 msgid "This task will be cancelled. Any progress made by this task will be saved." -msgstr "" +msgstr "To zadanie zostanie anulowane. Wszystkie dotychczasowe postępy zostaną zapisane." #: cps/templates/tasks.html:42 msgid "If this is a scheduled task, it will be re-ran during the next scheduled time." -msgstr "" +msgstr "Jeśli jest to zadanie zaplanowane, zostanie uruchomione ponownie przy następnym zaplanowanym terminie." #: cps/templates/user_edit.html:20 msgid "Reset user Password" @@ -3614,7 +3615,7 @@ msgstr "Zresetuj hasło użytkownika" #: cps/templates/user_edit.html:28 msgid "Send to eReader Email Address. Use comma to separate emails for multiple eReaders" -msgstr "" +msgstr "Wyślij na adres e‑mail czytnika. Użyj przecinka, aby oddzielić adresy e‑mail dla wielu czytników." #: cps/templates/user_edit.html:43 msgid "Language of Books" @@ -3642,7 +3643,7 @@ msgstr "Utwórz/Przeglądaj" #: cps/templates/user_edit.html:70 msgid "Force full kobo sync" -msgstr "" +msgstr "Wymuś pełną synchronizację kobo" #: cps/templates/user_edit.html:88 msgid "Add allowed/Denied Custom Column Values" @@ -3674,19 +3675,19 @@ msgstr "Wprowadź nazwę użytkownika" #: cps/templates/user_table.html:135 msgid "Enter Email" -msgstr "" +msgstr "Wprowadź email" #: cps/templates/user_table.html:136 msgid "Enter eReader Email" -msgstr "" +msgstr "Wprowadź email czytnika" #: cps/templates/user_table.html:136 msgid "eReader Email" -msgstr "" +msgstr "Email czytnika" #: cps/templates/user_table.html:137 msgid "Locale" -msgstr "" +msgstr "Język" #: cps/templates/user_table.html:138 msgid "Visible Book Languages" @@ -3722,7 +3723,7 @@ msgstr "Edycja zabronionych wartości kolumn" #: cps/templates/user_table.html:142 msgid "Denied Column Values" -msgstr "" +msgstr "Wykluczone wartości kolumn" #: cps/templates/user_table.html:144 msgid "Change Password" @@ -3738,9 +3739,8 @@ msgstr "Edytuj półki publiczne" #: cps/templates/user_table.html:152 msgid "Sync selected Shelves with Kobo" -msgstr "" +msgstr "Synchronizuj wybrane półki z czytnikiem Kobo" #: cps/templates/user_table.html:156 msgid "Show Read/Unread Section" -msgstr "" - +msgstr "Pokaż sekcję Przeczytane/Nieprzeczytane" From 0569c644a39cf61e779528ba7c0c8d281f769788 Mon Sep 17 00:00:00 2001 From: lb803 <47544021+lb803@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:44:36 +0000 Subject: [PATCH 14/19] change archive button icon from glyphicon-trash to glyphicon-folder-open --- cps/render_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/render_template.py b/cps/render_template.py index bf0356d0d..0f0a2894c 100644 --- a/cps/render_template.py +++ b/cps/render_template.py @@ -92,7 +92,7 @@ def get_sidebar_config(kwargs=None): "visibility": constants.SIDEBAR_FORMAT, 'public': True, "page": "format", "show_text": _('Show File Formats Section'), "config_show": True}) sidebar.append( - {"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived", + {"glyph": "glyphicon-folder-open", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived", "visibility": constants.SIDEBAR_ARCHIVED, 'public': (not current_user.is_anonymous), "page": "archived", "show_text": _('Show Archived Books'), "config_show": content}) if not simple: From 97a8fc661f365005c89aa4fbc545c2252d61598d Mon Sep 17 00:00:00 2001 From: Evan Goldenberg Date: Wed, 10 Dec 2025 00:06:32 -0800 Subject: [PATCH 15/19] Send WWW-Authenticate header with 401 responses --- cps/error_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cps/error_handler.py b/cps/error_handler.py index 0df983329..54942ec22 100644 --- a/cps/error_handler.py +++ b/cps/error_handler.py @@ -33,6 +33,7 @@ log = logger.create() # custom error page def error_http(error): + headers = {'WWW-Authenticate': 'Basic realm="calibre-web"'} if error.code == 401 else {} return render_template('http_error.html', error_code="Error {0}".format(error.code), error_name=error.name, @@ -40,7 +41,7 @@ def error_http(error): goto_admin=False, unconfigured=not config.db_configured, instance=config.config_calibre_web_title - ), error.code + ), error.code, headers def internal_error(error): From 38b0af9ea27a86a694d485df04aff3e0addb8267 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 20 Dec 2025 13:22:20 +0100 Subject: [PATCH 16/19] Remove debug code --- cps/opds.py | 1 - test/Calibre-Web TestSummary_Linux.html | 598 +++++++++++++++--------- 2 files changed, 367 insertions(+), 232 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index c5be8cd8d..329a6a0e5 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -438,7 +438,6 @@ def feed_shelf(book_id): @opds.route("/opds/download///") @requires_basic_auth_if_no_ano def opds_download_link(book_id, book_format): - return abort(401) if not auth.current_user().role_download(): return abort(401) client = "kobo" if "Kobo" in request.headers.get('User-Agent') else "" diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index d6831bdc3..8a59cf232 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
    -

    Start Time: 2025-12-06 19:29:38

    +

    Start Time: 2025-12-15 19:42:30

    -

    Stop Time: 2025-12-07 02:46:00

    +

    Stop Time: 2025-12-16 03:03:48

    -

    Duration: 6h 5 min

    +

    Duration: 6h 4 min

    @@ -102,11 +102,11 @@ - + TestAnonymous 13 - 13 - 0 + 12 + 1 0 0 @@ -197,11 +197,33 @@ - +
    TestAnonymous - test_guest_change_visibility_series
    - PASS + +
    + FAIL +
    + + + + @@ -1670,11 +1692,11 @@ - + TestEditAuthorsGdrive 7 - 7 - 0 + 6 + 1 0 0 @@ -1711,11 +1733,38 @@ - +
    TestEditAuthorsGdrive - test_change_capital_one_author_two_books_coauthor
    - PASS + +
    + FAIL +
    + + + + @@ -2280,9 +2329,9 @@
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_edit_metadata_scholar.py", line 74, in test_load_metadata
    -    self.assertEqual(30, len(results))
    -AssertionError: 30 != 20
    + File "/home/ozzie/Development/calibre-web-test/test/test_edit_metadata_scholar.py", line 65, in test_load_metadata + self.assertTrue(google_scholar) +AssertionError: False is not true
    @@ -2736,11 +2785,11 @@ IndexError: list index out of range - + TestKoboSync 12 - 12 - 0 + 11 + 1 0 0 @@ -2831,11 +2880,31 @@ IndexError: list index out of range - +
    TestKoboSync - test_sync_shelf
    - PASS + +
    + FAIL +
    + + + + @@ -2928,12 +2997,12 @@ IndexError: list index out of range - + TestLdapLogin 13 - 13 - 0 - 0 + 11 + 1 + 1 0 Detail @@ -3041,20 +3110,71 @@ IndexError: list index out of range - +
    TestLdapLogin - test_ldap_opds_anonymous
    - PASS + +
    + FAIL +
    + + + + - +
    TestLdapLogin - test_ldap_opds_download_book
    - PASS + +
    + ERROR +
    + + + + @@ -3767,12 +3887,12 @@ IndexError: list index out of range - + TestOPDSFeed 26 - 26 - 0 - 0 + 20 + 4 + 2 0 Detail @@ -3853,11 +3973,31 @@ IndexError: list index out of range - +
    TestOPDSFeed - test_opds_download_book
    - PASS + +
    + FAIL +
    + + + + @@ -3871,20 +4011,60 @@ IndexError: list index out of range - +
    TestOPDSFeed - test_opds_guest_user
    - PASS + +
    + FAIL +
    + + + + - +
    TestOPDSFeed - test_opds_hot
    - PASS + +
    + FAIL +
    + + + + @@ -3943,11 +4123,31 @@ IndexError: list index out of range - +
    TestOPDSFeed - test_opds_search
    - PASS + +
    + FAIL +
    + + + + @@ -3997,32 +4197,90 @@ IndexError: list index out of range - +
    TestOPDSFeed - test_opds_unicode_user
    - PASS + +
    + ERROR +
    + + + + - +
    TestOPDSFeed - test_recently_added
    - PASS + +
    + ERROR +
    + + + + - + TestUploadPDF 1 - 0 1 0 0 + 0 Detail @@ -4030,33 +4288,11 @@ IndexError: list index out of range - +
    TestUploadPDF - test_upload_invalid_pdf
    - -
    - FAIL -
    - - - - + PASS @@ -4365,8 +4601,8 @@ AssertionError: False is not true TestShelf 17 - 15 - 1 + 14 + 2 0 1 @@ -4477,11 +4713,31 @@ AssertionError: 7 != 2 - +
    TestShelf - test_public_shelf
    - PASS + +
    + FAIL +
    + + + + @@ -4726,11 +4982,11 @@ AssertionError: 7 != 2 - + TestThumbnails 8 - 6 - 1 + 7 + 0 0 1 @@ -4803,31 +5059,11 @@ AssertionError: 7 != 2 - +
    TestThumbnails - test_sideloaded_book
    - -
    - FAIL -
    - - - - + PASS @@ -4949,8 +5185,8 @@ AssertionError: 0.049274509803921576 != 0.0 within 0.0001 delta (0.0492745098039 TestUploadAudio 12 - 1 - 11 + 5 + 7 0 0 @@ -4960,31 +5196,11 @@ AssertionError: 0.049274509803921576 != 0.0 within 0.0001 delta (0.0492745098039 - +
    TestUploadAudio - test_upload_aac
    - -
    - FAIL -
    - - - - + PASS @@ -5006,7 +5222,7 @@ AssertionError: 0.0026100147661403746 != 0.0 within 0.001 delta (0.0026100147661
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 251, in test_upload_aiff
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 252, in test_upload_aiff
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5018,31 +5234,11 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972 - +
    TestUploadAudio - test_upload_asf
    - -
    - FAIL -
    - - - - + PASS @@ -5064,7 +5260,7 @@ AssertionError: 0.0026100147661403746 != 0.0 within 0.001 delta (0.0026100147661
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 379, in test_upload_flac
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 380, in test_upload_flac
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5076,60 +5272,20 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972 - +
    TestUploadAudio - test_upload_m4a
    - -
    - FAIL -
    - - - - + PASS - +
    TestUploadAudio - test_upload_m4b
    - -
    - FAIL -
    - - - - + PASS @@ -5151,7 +5307,7 @@ AssertionError: 0.0026100147661403746 != 0.0 within 0.001 delta (0.0026100147661
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 120, in test_upload_mp3
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 121, in test_upload_mp3
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.006)
     AssertionError: 0.0075997182498730486 != 0.0 within 0.006 delta (0.0075997182498730486 difference)
    @@ -5180,7 +5336,7 @@ AssertionError: 0.0075997182498730486 != 0.0 within 0.006 delta (0.0075997182498
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 525, in test_upload_mp4
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 526, in test_upload_mp4
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5209,7 +5365,7 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 316, in test_upload_oggvorbis
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 317, in test_upload_oggvorbis
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5238,7 +5394,7 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 710, in test_upload_ogv
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 711, in test_upload_ogv
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5267,7 +5423,7 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 655, in test_upload_opus
    +  File "/home/ozzie/Development/calibre-web-test/test/test_upload_audio.py", line 656, in test_upload_opus
         self.assertAlmostEqual(diff(BytesIO(self.png_original), BytesIO(cover_image), delete_diff_file=True), 0.0, delta=0.001)
     AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.004005578839723024 difference)
    @@ -5292,8 +5448,8 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972 TestUploadEPubs 6 - 4 - 2 + 5 + 1 0 0 @@ -5321,31 +5477,11 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972 - +
    TestUploadEPubs - test_upload_epub_cover_formats
    - -
    - FAIL -
    - - - - + PASS @@ -6294,9 +6430,9 @@ AssertionError: 12 != 1 Total 538 - 513 - 17 - 1 + 508 + 19 + 4 7   @@ -6325,7 +6461,7 @@ AssertionError: 12 != 1 Platform - Linux 6.8.0-88-generic #89-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 01:02:46 UTC 2025 x86_64 x86_64 + Linux 6.8.0-90-generic #91-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 18 14:14:30 UTC 2025 x86_64 x86_64 Basic @@ -6439,7 +6575,7 @@ AssertionError: 12 != 1 pypdf - 6.4.0 + 6.4.2 Basic @@ -6469,13 +6605,13 @@ AssertionError: 12 != 1 SQLAlchemy - 2.0.44 + 2.0.45 Basic tornado - 6.5.2 + 6.5.3 Basic @@ -6487,7 +6623,7 @@ AssertionError: 12 != 1 urllib3 - 2.6.0 + 2.6.2 Basic @@ -6823,7 +6959,7 @@ AssertionError: 12 != 1 SQLAlchemy-Utils - 0.42.0 + 0.42.1 TestOAuthLogin @@ -6847,7 +6983,7 @@ AssertionError: 12 != 1 From eb8b0096a132b1219c38376df49d90598f53d231 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 23 Dec 2025 14:25:21 +0100 Subject: [PATCH 17/19] Update Teststatus --- test/Calibre-Web TestSummary_Linux.html | 743 +++++++++--------------- 1 file changed, 261 insertions(+), 482 deletions(-) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 8a59cf232..cb530a5ef 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
    -

    Start Time: 2025-12-15 19:42:30

    +

    Start Time: 2025-12-20 19:34:51

    -

    Stop Time: 2025-12-16 03:03:48

    +

    Stop Time: 2025-12-21 03:04:35

    -

    Duration: 6h 4 min

    +

    Duration: 6h 12 min

    @@ -105,8 +105,8 @@ TestAnonymous 13 - 12 - 1 + 11 + 2 0 0 @@ -143,11 +143,33 @@ - +
    TestAnonymous - test_guest_change_visibility_format
    - PASS + +
    + FAIL +
    + + + + @@ -161,11 +183,33 @@ - +
    TestAnonymous - test_guest_change_visibility_language
    - PASS + +
    + FAIL +
    + + + + @@ -197,33 +241,11 @@ - +
    TestAnonymous - test_guest_change_visibility_series
    - -
    - FAIL -
    - - - - + PASS @@ -616,12 +638,12 @@ AssertionError: 'stored' != '2' - + TestCliGdrivedb 4 - 4 - 0 - 0 + 2 + 1 + 1 0 Detail @@ -648,20 +670,132 @@ AssertionError: 'stored' != '2' - +
    TestCliGdrivedb - test_gdrive_db_nonwrite
    - PASS + +
    + ERROR +
    + + + + - +
    TestCliGdrivedb - test_no_database
    - PASS + +
    + FAIL +
    + + + + @@ -1692,11 +1826,11 @@ AssertionError: 'stored' != '2' - + TestEditAuthorsGdrive 7 - 6 - 1 + 7 + 0 0 0 @@ -1733,38 +1867,11 @@ AssertionError: 'stored' != '2' - +
    TestEditAuthorsGdrive - test_change_capital_one_author_two_books_coauthor
    - -
    - FAIL -
    - - - - + PASS @@ -2088,13 +2195,13 @@ First extra element 0: - + TestLoadMetadata 1 + 0 + 0 1 0 - 0 - 0 Detail @@ -2102,11 +2209,32 @@ First extra element 0: - +
    TestLoadMetadata - test_load_metadata
    - PASS + +
    + ERROR +
    + + + + @@ -2329,9 +2457,9 @@ First extra element 0:
    Traceback (most recent call last):
    -  File "/home/ozzie/Development/calibre-web-test/test/test_edit_metadata_scholar.py", line 65, in test_load_metadata
    -    self.assertTrue(google_scholar)
    -AssertionError: False is not true
    + File "/home/ozzie/Development/calibre-web-test/test/test_edit_metadata_scholar.py", line 74, in test_load_metadata + self.assertEqual(30, len(results)) +AssertionError: 30 != 20
    @@ -2785,11 +2913,11 @@ IndexError: list index out of range - + TestKoboSync 12 - 11 - 1 + 12 + 0 0 0 @@ -2880,31 +3008,11 @@ IndexError: list index out of range - +
    TestKoboSync - test_sync_shelf
    - -
    - FAIL -
    - - - - + PASS @@ -2997,12 +3105,12 @@ AssertionError: 1 != 0 : [] - + TestLdapLogin 13 - 11 - 1 - 1 + 13 + 0 + 0 0 Detail @@ -3110,71 +3218,20 @@ AssertionError: 1 != 0 : [] - +
    TestLdapLogin - test_ldap_opds_anonymous
    - -
    - FAIL -
    - - - - + PASS - +
    TestLdapLogin - test_ldap_opds_download_book
    - -
    - ERROR -
    - - - - + PASS @@ -3887,12 +3944,12 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - + TestOPDSFeed 26 - 20 - 4 - 2 + 26 + 0 + 0 0 Detail @@ -3973,31 +4030,11 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
    TestOPDSFeed - test_opds_download_book
    - -
    - FAIL -
    - - - - + PASS @@ -4011,60 +4048,20 @@ AssertionError: 1240 != 28590 - +
    TestOPDSFeed - test_opds_guest_user
    - -
    - FAIL -
    - - - - + PASS - +
    TestOPDSFeed - test_opds_hot
    - -
    - FAIL -
    - - - - + PASS @@ -4123,31 +4120,11 @@ AssertionError: 0 != 1 - +
    TestOPDSFeed - test_opds_search
    - -
    - FAIL -
    - - - - + PASS @@ -4197,78 +4174,20 @@ AssertionError: 401 != 200 - +
    TestOPDSFeed - test_opds_unicode_user
    - -
    - ERROR -
    - - - - + PASS - +
    TestOPDSFeed - test_recently_added
    - -
    - ERROR -
    - - - - + PASS @@ -4601,8 +4520,8 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 TestShelf 17 - 14 - 2 + 15 + 1 0 1 @@ -4713,31 +4632,11 @@ AssertionError: 7 != 2 - +
    TestShelf - test_public_shelf
    - -
    - FAIL -
    - - - - + PASS @@ -5185,8 +5084,8 @@ AssertionError: False != True TestUploadAudio 12 - 5 - 7 + 11 + 1 0 0 @@ -5205,31 +5104,11 @@ AssertionError: False != True - +
    TestUploadAudio - test_upload_aiff
    - -
    - FAIL -
    - - - - + PASS @@ -5243,31 +5122,11 @@ AssertionError: 0.004005578839723024 != 0.0 within 0.001 delta (0.00400557883972 - +
    TestUploadAudio - test_upload_flac
    - -
    - FAIL -
    - - - - + PASS @@ -5319,118 +5178,38 @@ AssertionError: 0.0075997182498730486 != 0.0 within 0.006 delta (0.0075997182498 - +
    TestUploadAudio - test_upload_mp4
    - -
    - FAIL -
    - - - - + PASS - +
    TestUploadAudio - test_upload_oggvorbis
    - -
    - FAIL -
    - - - - + PASS - +
    TestUploadAudio - test_upload_ogv
    - -
    - FAIL -
    - - - - + PASS - +
    TestUploadAudio - test_upload_opus
    - -
    - FAIL -
    - - - - + PASS @@ -6430,9 +6209,9 @@ AssertionError: 12 != 1 Total 538 - 508 - 19 - 4 + 521 + 7 + 3 7   @@ -6611,7 +6390,7 @@ AssertionError: 12 != 1 tornado - 6.5.3 + 6.5.4 Basic @@ -6983,7 +6762,7 @@ AssertionError: 12 != 1 From 02fbef3dbacd456fe80721d81381c1a72ae387e5 Mon Sep 17 00:00:00 2001 From: Julian_Chu Date: Mon, 29 Dec 2025 18:35:09 +0900 Subject: [PATCH 18/19] Move progress css style to dedicated file In kthoom implementation, there is also a DOM `
    ` If this part of css style stays in main.css, it also effects the kthoom implementation, therefore the progress bar will disappear when reading comic files (such as cbz). --- cps/static/css/main.css | 7 ------- cps/static/css/reader.css | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cps/static/css/main.css b/cps/static/css/main.css index f590eda3a..160735b82 100644 --- a/cps/static/css/main.css +++ b/cps/static/css/main.css @@ -918,10 +918,3 @@ input:-moz-placeholder { .icon-resize-small::before { content: "\e808"; } /* '' */ - -#progress { - right: 4rem; - bottom: 2px; - width: fit-content; - position: absolute; -} diff --git a/cps/static/css/reader.css b/cps/static/css/reader.css index a369cd97b..e43611796 100644 --- a/cps/static/css/reader.css +++ b/cps/static/css/reader.css @@ -72,3 +72,10 @@ right: 50px; // position: absolute; } + +#progress { + right: 4rem; + bottom: 2px; + width: fit-content; + position: absolute; +} From 8105f54ebbfb37c75619c382f11660a411862075 Mon Sep 17 00:00:00 2001 From: mapi68 <41143572+mapi68@users.noreply.github.com> Date: Sun, 4 Jan 2026 02:24:15 +0100 Subject: [PATCH 19/19] Update Italian language --- cps/translations/it/LC_MESSAGES/messages.po | 446 ++++++++++++++------ 1 file changed, 319 insertions(+), 127 deletions(-) diff --git a/cps/translations/it/LC_MESSAGES/messages.po b/cps/translations/it/LC_MESSAGES/messages.po index aacb38532..cc224b09a 100644 --- a/cps/translations/it/LC_MESSAGES/messages.po +++ b/cps/translations/it/LC_MESSAGES/messages.po @@ -2,21 +2,22 @@ # Copyright (C) 2016 Smart Cities Community # This file is distributed under the same license as the Calibre-Web # Juan F. Villa , 2016. -# SPDX-FileCopyrightText: 2023, 2024, 2025 Massimo Pissarello +# SPDX-FileCopyrightText: 2023, 2024, 2025, 2026 Massimo Pissarello msgid "" msgstr "" "Project-Id-Version: Calibre-Web\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2025-12-14 14:44+0100\n" -"PO-Revision-Date: 2025-12-07 04:05+0100\n" +"PO-Revision-Date: 2026-01-04 02:22+0100\n" "Last-Translator: Massimo Pissarello \n" -"Language: it\n" "Language-Team: Italian <>\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: it\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.17.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Generated-By: Babel 2.13.1\n" +"X-Generator: Lokalize 25.12.0\n" #: cps/about.py:85 msgid "Statistics" @@ -39,8 +40,11 @@ msgid "Unknown command" msgstr "Comando sconosciuto" #: cps/admin.py:175 -msgid "Success! Books queued for Metadata Backup, please check Tasks for result" -msgstr "Tutto OK! Libri in coda per il backup dei metadati, controlla le attività per il risultato" +msgid "" +"Success! Books queued for Metadata Backup, please check Tasks for result" +msgstr "" +"Tutto OK! Libri in coda per il backup dei metadati, controlla le attività " +"per il risultato" #: cps/admin.py:208 cps/editbooks.py:808 cps/editbooks.py:852 #: cps/editbooks.py:1520 cps/updater.py:615 cps/uploader.py:108 @@ -64,7 +68,8 @@ msgstr "Configurazione dell'interfaccia utente" #: cps/web.py:753 #, python-format msgid "Custom Column No.%(column)d does not exist in calibre database" -msgstr "La colonna personalizzata no.%(column)d non esiste nel database di Calibre" +msgstr "" +"La colonna personalizzata no.%(column)d non esiste nel database di Calibre" #: cps/admin.py:333 cps/templates/admin.html:51 msgid "Edit Users" @@ -103,7 +108,9 @@ msgstr "L'utente Guest (ospite) non può avere questo ruolo" #: cps/admin.py:500 cps/admin.py:2031 msgid "No admin user remaining, can't remove admin role" -msgstr "Non rimarrebbe nessun utente amministratore, non è possibile rimuovere il ruolo di amministratore" +msgstr "" +"Non rimarrebbe nessun utente amministratore, non è possibile rimuovere il " +"ruolo di amministratore" #: cps/admin.py:504 cps/admin.py:518 msgid "Value has to be true or false" @@ -123,7 +130,9 @@ msgstr "Visualizzazione non valida" #: cps/admin.py:523 msgid "Guest's Locale is determined automatically and can't be set" -msgstr "Le impostazioni locali dell'utente Guest (ospite) sono determinate automaticamente e non possono essere configurate" +msgstr "" +"Le impostazioni locali dell'utente Guest (ospite) sono determinate " +"automaticamente e non possono essere configurate" #: cps/admin.py:527 msgid "No Valid Locale Given" @@ -172,47 +181,77 @@ msgstr "Sei sicuro di voler eliminare questo scaffale?" #: cps/admin.py:625 msgid "Are you sure you want to change locales of selected user(s)?" -msgstr "Sei sicuro di voler cambiare le impostazioni internazionali degli utenti selezionati?" +msgstr "" +"Sei sicuro di voler cambiare le impostazioni internazionali degli utenti " +"selezionati?" #: cps/admin.py:627 -msgid "Are you sure you want to change visible book languages for selected user(s)?" -msgstr "Sei sicuro di voler cambiare le lingue visibili del libro per gli utenti selezionati?" +msgid "" +"Are you sure you want to change visible book languages for selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le lingue visibili del libro per gli utenti " +"selezionati?" #: cps/admin.py:629 -msgid "Are you sure you want to change the selected role for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare il ruolo selezionato per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected role for the selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare il ruolo selezionato per gli utenti selezionati?" #: cps/admin.py:631 -msgid "Are you sure you want to change the archive status for the selected book(s)?" -msgstr "Sei sicuro di voler cambiare lo stato dell'archivio per i libri selezionati?" +msgid "" +"Are you sure you want to change the archive status for the selected book(s)?" +msgstr "" +"Sei sicuro di voler cambiare lo stato dell'archivio per i libri selezionati?" #: cps/admin.py:633 -msgid "Are you sure you want to change the read status for the selected book(s)?" -msgstr "Sei sicuro di voler cambiare lo stato di lettura per i libri selezionati?" +msgid "" +"Are you sure you want to change the read status for the selected book(s)?" +msgstr "" +"Sei sicuro di voler cambiare lo stato di lettura per i libri selezionati?" #: cps/admin.py:635 -msgid "Are you sure you want to change the selected restrictions for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare le restrizioni selezionate per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected restrictions for the selected " +"user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le restrizioni selezionate per gli utenti " +"selezionati?" #: cps/admin.py:637 -msgid "Are you sure you want to change the selected visibility restrictions for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare le restrizioni di visibilità selezionate per gli utenti selezionati?" +msgid "" +"Are you sure you want to change the selected visibility restrictions for the " +"selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare le restrizioni di visibilità selezionate per " +"gli utenti selezionati?" #: cps/admin.py:640 -msgid "Are you sure you want to change shelf sync behavior for the selected user(s)?" -msgstr "Sei sicuro di voler cambiare il comportamento di sincronizzazione dello scaffale per gli utenti selezionati?" +msgid "" +"Are you sure you want to change shelf sync behavior for the selected user(s)?" +msgstr "" +"Sei sicuro di voler cambiare il comportamento di sincronizzazione dello " +"scaffale per gli utenti selezionati?" #: cps/admin.py:642 msgid "Are you sure you want to change Calibre library location?" msgstr "Sei sicuro di voler cambiare la posizione della biblioteca di Calibre?" #: cps/admin.py:644 -msgid "Calibre-Web will search for updated Covers and update Cover Thumbnails, this may take a while?" -msgstr "Calibre-Web cercherà le copertine aggiornate e aggiornerà le miniature delle copertine, ma ci vorrà un po' di tempo." +msgid "" +"Calibre-Web will search for updated Covers and update Cover Thumbnails, this " +"may take a while?" +msgstr "" +"Calibre-Web cercherà le copertine aggiornate e aggiornerà le miniature delle " +"copertine, ma ci vorrà un po' di tempo." #: cps/admin.py:647 -msgid "Are you sure you want delete Calibre-Web's sync database to force a full sync with your Kobo Reader?" -msgstr "Sei sicuro di voler eliminare il database sincronizzato di Calibre-Web e forzare una sincronizzazione completa con il tuo lettore Kobo?" +msgid "" +"Are you sure you want delete Calibre-Web's sync database to force a full " +"sync with your Kobo Reader?" +msgstr "" +"Sei sicuro di voler eliminare il database sincronizzato di Calibre-Web e " +"forzare una sincronizzazione completa con il tuo lettore Kobo?" #: cps/admin.py:890 cps/admin.py:896 cps/admin.py:906 cps/admin.py:916 #: cps/templates/modal_dialogs.html:29 cps/templates/user_table.html:41 @@ -245,15 +284,21 @@ msgstr "client_secrets.json non è configurato per Web Application" #: cps/admin.py:1182 msgid "Logfile Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del file di log non è valida, per favore indica il percorso corretto" +msgstr "" +"La posizione del file di log non è valida, per favore indica il percorso " +"corretto" #: cps/admin.py:1188 msgid "Access Logfile Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del file del log di accesso non è valida, indica il percorso corretto" +msgstr "" +"La posizione del file del log di accesso non è valida, indica il percorso " +"corretto" #: cps/admin.py:1222 msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier" -msgstr "Inserisci un provider LDAP, una porta, un DN e un identificatore oggetto utente" +msgstr "" +"Inserisci un provider LDAP, una porta, un DN e un identificatore oggetto " +"utente" #: cps/admin.py:1228 msgid "Please Enter a LDAP Service Account and Password" @@ -266,7 +311,8 @@ msgstr "Inserisci un account di servizio LDAP" #: cps/admin.py:1236 #, python-format msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro oggetto gruppo LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro oggetto gruppo LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1238 msgid "LDAP Group Object Filter Has Unmatched Parenthesis" @@ -275,7 +321,8 @@ msgstr "Il filtro oggetto gruppo LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1242 #, python-format msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro oggetto utente LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro oggetto utente LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1244 msgid "LDAP User Object Filter Has Unmatched Parenthesis" @@ -284,15 +331,20 @@ msgstr "Il filtro oggetto utente LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1251 #, python-format msgid "LDAP Member User Filter needs to Have One \"%s\" Format Identifier" -msgstr "Il filtro utente membro LDAP deve avere un identificatore di formato \"%s\"" +msgstr "" +"Il filtro utente membro LDAP deve avere un identificatore di formato \"%s\"" #: cps/admin.py:1253 msgid "LDAP Member User Filter Has Unmatched Parenthesis" msgstr "Il filtro utente membro LDAP ha parentesi senza corrispondenza" #: cps/admin.py:1260 -msgid "LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter Correct Path" -msgstr "Il certificato CA LDAP, il certificato o la posizione della chiave non sono validi. Inserisci il percorso corretto" +msgid "" +"LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter " +"Correct Path" +msgstr "" +"Il certificato CA LDAP, il certificato o la posizione della chiave non sono " +"validi. Inserisci il percorso corretto" #: cps/admin.py:1291 cps/templates/admin.html:53 msgid "Add New User" @@ -317,8 +369,11 @@ msgstr "Errore nel database: %(error)s." #: cps/admin.py:1349 #, python-format -msgid "Test e-mail queued for sending to %(email)s, please check Tasks for result" -msgstr "L'e-mail di prova è stato accodata correttamente per essere spedita a %(email)s, controlla il risultato in Attività" +msgid "" +"Test e-mail queued for sending to %(email)s, please check Tasks for result" +msgstr "" +"L'e-mail di prova è stato accodata correttamente per essere spedita a " +"%(email)s, controlla il risultato in Attività" #: cps/admin.py:1352 #, python-format @@ -426,7 +481,8 @@ msgstr "Errore generale" #: cps/admin.py:1556 msgid "Update file could not be saved in temp dir" -msgstr "Il file di aggiornamento non può essere salvato nella cartella temporanea" +msgstr "" +"Il file di aggiornamento non può essere salvato nella cartella temporanea" #: cps/admin.py:1557 msgid "Files could not be replaced during update" @@ -464,7 +520,8 @@ msgstr "Percorso dei libri non valido" #: cps/admin.py:1745 msgid "DB Location is not Valid, Please Enter Correct Path" -msgstr "La posizione del DB non è valida, per favore indica il percorso corretto" +msgstr "" +"La posizione del DB non è valida, per favore indica il percorso corretto" #: cps/admin.py:1773 msgid "DB is not Writeable" @@ -522,7 +579,9 @@ msgstr "Impossibile eliminare l'utente Guest (ospite)" #: cps/admin.py:2016 msgid "No admin user remaining, can't delete user" -msgstr "Non rimarrebbe nessun utente amministratore, non è possibile eliminare l'utente" +msgstr "" +"Non rimarrebbe nessun utente amministratore, non è possibile eliminare " +"l'utente" #: cps/admin.py:2071 cps/web.py:1489 msgid "Email can't be empty and has to be a valid Email" @@ -587,8 +646,11 @@ msgstr "Il valore non è presente nella richiesta" #: cps/editbooks.py:319 cps/editbooks.py:326 cps/editbooks.py:627 #: cps/editbooks.py:1146 cps/web.py:535 cps/web.py:1581 cps/web.py:1627 #: cps/web.py:1677 -msgid "Oops! Selected book is unavailable. File does not exist or is not accessible" -msgstr "Il libro selezionato non è disponibile. Il file non esiste o non è accessibile" +msgid "" +"Oops! Selected book is unavailable. File does not exist or is not accessible" +msgstr "" +"Il libro selezionato non è disponibile. Il file non esiste o non è " +"accessibile" #: cps/editbooks.py:673 cps/editbooks.py:1503 msgid "User has no rights to upload cover" @@ -596,7 +658,9 @@ msgstr "L'utente non ha i permessi per caricare le copertine" #: cps/editbooks.py:694 cps/editbooks.py:938 msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier" -msgstr "Gli identificatori non fanno distinzione tra maiuscole e minuscole e sovrascrivono il vecchio identificatore" +msgstr "" +"Gli identificatori non fanno distinzione tra maiuscole e minuscole e " +"sovrascrivono il vecchio identificatore" #: cps/editbooks.py:709 cps/editbooks.py:912 cps/editbooks.py:1273 #, python-format @@ -613,8 +677,12 @@ msgid "Error editing book: {}" msgstr "Errore nella modifica del libro: {}" #: cps/editbooks.py:856 -msgid "Uploaded book probably exists in the library, consider to change before upload new: " -msgstr "Probabilmente il libro caricato esiste già nella biblioteca, cambialo prima di caricarlo di nuovo:" +msgid "" +"Uploaded book probably exists in the library, consider to change before " +"upload new: " +msgstr "" +"Probabilmente il libro caricato esiste già nella biblioteca, cambialo prima " +"di caricarlo di nuovo:" #: cps/editbooks.py:950 cps/editbooks.py:1420 msgid "File type isn't allowed to be uploaded to this server" @@ -623,7 +691,8 @@ msgstr "Non è consentito caricare questo tipo di file su questo server" #: cps/editbooks.py:956 cps/editbooks.py:1431 #, python-format msgid "File extension '%(ext)s' is not allowed to be uploaded to this server" -msgstr "Non è consentito caricare l'estensione del file '%(ext)s' su questo server" +msgstr "" +"Non è consentito caricare l'estensione del file '%(ext)s' su questo server" #: cps/editbooks.py:960 cps/editbooks.py:1436 msgid "File to be uploaded must have an extension" @@ -685,12 +754,20 @@ msgid "File format %(ext)s added to %(book)s" msgstr "Formato file %(ext)s aggiunto a %(book)s" #: cps/gdrive.py:58 -msgid "Google Drive setup not completed, try to deactivate and activate Google Drive again" -msgstr "Configurazione di Google Drive non completata, prova a disattivare e attivare nuovamente Google Drive" +msgid "" +"Google Drive setup not completed, try to deactivate and activate Google " +"Drive again" +msgstr "" +"Configurazione di Google Drive non completata, prova a disattivare e " +"attivare nuovamente Google Drive" #: cps/gdrive.py:96 -msgid "Callback domain is not verified, please follow steps to verify domain in google developer console" -msgstr "Il dominio di callback non è stato verificato, segui i passaggi per verificare il dominio nella console per sviluppatori di Google" +msgid "" +"Callback domain is not verified, please follow steps to verify domain in " +"google developer console" +msgstr "" +"Il dominio di callback non è stato verificato, segui i passaggi per " +"verificare il dominio nella console per sviluppatori di Google" #: cps/helper.py:87 #, python-format @@ -758,8 +835,11 @@ msgstr "Impossibile impostare lo stato di lettura: {}" #: cps/helper.py:373 #, python-format -msgid "Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" -msgstr "Eliminazione della cartella di libri per il libro %(id)s non riuscita, il percorso ha sottocartelle: %(path)s" +msgid "" +"Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" +msgstr "" +"Eliminazione della cartella di libri per il libro %(id)s non riuscita, il " +"percorso ha sottocartelle: %(path)s" #: cps/helper.py:379 #, python-format @@ -768,18 +848,29 @@ msgstr "Eliminazione del libro %(id)s non riuscita: %(message)s" #: cps/helper.py:390 #, python-format -msgid "Deleting book %(id)s from database only, book path in database not valid: %(path)s" -msgstr "Eliminazione del libro %(id)s solo dal database, percorso del libro nel database non valido: %(path)s" +msgid "" +"Deleting book %(id)s from database only, book path in database not valid: " +"%(path)s" +msgstr "" +"Eliminazione del libro %(id)s solo dal database, percorso del libro nel " +"database non valido: %(path)s" #: cps/helper.py:404 #, python-format -msgid "Moving book path of Book %(book_id)s to: '%(src)s' failed with error: %(error)s" -msgstr "Spostamento del percorso del libro %(book_id)s a: '%(src)s' non riuscito con errore: %(error)s" +msgid "" +"Moving book path of Book %(book_id)s to: '%(src)s' failed with error: " +"%(error)s" +msgstr "" +"Spostamento del percorso del libro %(book_id)s a: '%(src)s' non riuscito con " +"errore: %(error)s" #: cps/helper.py:443 #, python-format -msgid "Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "La modifica dell'autore da '%(src)s' a '%(dest)s' è terminata con l'errore: %(error)s" +msgid "" +"Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgstr "" +"La modifica dell'autore da '%(src)s' a '%(dest)s' è terminata con l'errore: " +"%(error)s" #: cps/helper.py:513 cps/helper.py:522 #, python-format @@ -789,7 +880,9 @@ msgstr "Il file %(file) non è stato trovato su Google Drive" #: cps/helper.py:564 #, python-format msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "La modifica del titolo da '%(src)s' a '%(dest)s' è terminata con l'errore: %(error)s" +msgstr "" +"La modifica del titolo da '%(src)s' a '%(dest)s' è terminata con l'errore: " +"%(error)s" #: cps/helper.py:583 #, python-format @@ -813,8 +906,11 @@ msgid "Password doesn't comply with password validation rules" msgstr "La password non è conforme alle regole di convalida della password" #: cps/helper.py:833 -msgid "Python module 'advocate' is not installed but is needed for cover uploads" -msgstr "Il modulo Python \"advocate\" non è installato ma è necessario per il caricamento delle copertine" +msgid "" +"Python module 'advocate' is not installed but is needed for cover uploads" +msgstr "" +"Il modulo Python \"advocate\" non è installato ma è necessario per il " +"caricamento delle copertine" #: cps/helper.py:843 msgid "Error Downloading Cover" @@ -825,8 +921,12 @@ msgid "Cover Format Error" msgstr "Errore nel formato della copertina" #: cps/helper.py:849 -msgid "You are not allowed to access localhost or the local network for cover uploads" -msgstr "Non ti è consentito accedere all'host locale o alla rete locale per caricare le copertine" +msgid "" +"You are not allowed to access localhost or the local network for cover " +"uploads" +msgstr "" +"Non ti è consentito accedere all'host locale o alla rete locale per caricare " +"le copertine" #: cps/helper.py:859 msgid "Failed to create path for cover" @@ -834,11 +934,14 @@ msgstr "Impossibile creare il percorso per la copertina" #: cps/helper.py:875 msgid "Cover-file is not a valid image file, or could not be stored" -msgstr "Il file della copertina non è in un formato di immagine valido o non può essere salvato" +msgstr "" +"Il file della copertina non è in un formato di immagine valido o non può " +"essere salvato" #: cps/helper.py:886 msgid "Only jpg/jpeg/png/webp/bmp files are supported as coverfile" -msgstr "Solo i file jpg/jpeg/png/webp/bmp sono supportati come file di copertina" +msgstr "" +"Solo i file jpg/jpeg/png/webp/bmp sono supportati come file di copertina" #: cps/helper.py:898 msgid "Invalid cover file content" @@ -891,8 +994,12 @@ msgid "Queue all books for metadata backup" msgstr "Metti in coda tutti i libri per il backup dei metadati" #: cps/kobo_auth.py:92 -msgid "Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device" -msgstr "Accedi a Calibre-Web da un host non locale per ottenere un api_endpoint valido per il dispositivo Kobo" +msgid "" +"Please access Calibre-Web from non localhost to get valid api_endpoint for " +"kobo device" +msgstr "" +"Accedi a Calibre-Web da un host non locale per ottenere un api_endpoint " +"valido per il dispositivo Kobo" #: cps/kobo_auth.py:118 msgid "Kobo Setup" @@ -915,7 +1022,8 @@ msgstr "Collegamento riuscito a %(oauth)s" #: cps/oauth_bb.py:156 msgid "Login failed, No User Linked With OAuth Account" -msgstr "Accesso non riuscito, non c'è nessun utente collegato all'account OAuth" +msgstr "" +"Accesso non riuscito, non c'è nessun utente collegato all'account OAuth" #: cps/oauth_bb.py:198 #, python-format @@ -1153,7 +1261,9 @@ msgstr "Stato di lettura = '%(status)s'" #: cps/search.py:351 msgid "Error on search for custom columns, please restart Calibre-Web" -msgstr "Errore nella ricerca delle colonne personalizzate. Per favore riavvia Calibre-Web" +msgstr "" +"Errore nella ricerca delle colonne personalizzate. Per favore riavvia " +"Calibre-Web" #: cps/search.py:370 cps/search.py:402 cps/templates/layout.html:57 msgid "Advanced Search" @@ -1165,7 +1275,8 @@ msgstr "Scaffale specificato non valido" #: cps/shelf.py:55 msgid "Sorry you are not allowed to add a book to that shelf" -msgstr "Spiacente, ma non sei autorizzato ad aggiungere libri a questo scaffale" +msgstr "" +"Spiacente, ma non sei autorizzato ad aggiungere libri a questo scaffale" #: cps/shelf.py:64 #, python-format @@ -1175,7 +1286,8 @@ msgstr "Il libro è gia presente nello scaffale: %(shelfname)s" #: cps/shelf.py:77 #, python-format msgid "%(book_id)s is a invalid Book Id. Could not be added to Shelf" -msgstr "%(book_id)s non è un valido ID libro. Impossibile aggiungerlo allo scaffale" +msgstr "" +"%(book_id)s non è un valido ID libro. Impossibile aggiungerlo allo scaffale" #: cps/shelf.py:97 #, python-format @@ -1270,7 +1382,9 @@ msgstr "Scaffale: '%(name)s'" #: cps/shelf.py:487 msgid "Error opening shelf. Shelf does not exist or is not accessible" -msgstr "Errore nell'apertura dello scaffale. Lo scaffale non esiste o non è accessibile" +msgstr "" +"Errore nell'apertura dello scaffale. Lo scaffale non esiste o non è " +"accessibile" #: cps/tasks_status.py:47 cps/templates/layout.html:90 #: cps/templates/tasks.html:7 @@ -1314,8 +1428,12 @@ msgid "No update available. You already have the latest version installed" msgstr "Nessun aggiornamento disponibile. Hai già l'ultima versione installata" #: cps/updater.py:459 -msgid "A new update is available. Click on the button below to update to the latest version." -msgstr "È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per aggiornare all'ultima versione" +msgid "" +"A new update is available. Click on the button below to update to the latest " +"version." +msgstr "" +"È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per " +"aggiornare all'ultima versione" #: cps/updater.py:476 msgid "Could not fetch update information" @@ -1323,12 +1441,18 @@ msgstr "Impossibile recuperare le informazioni sull'aggiornamento" #: cps/updater.py:487 msgid "Click on the button below to update to the latest stable version." -msgstr "Fai clic sul pulsante in basso per eseguire l'aggiornamento all'ultima versione stabile." +msgstr "" +"Fai clic sul pulsante in basso per eseguire l'aggiornamento all'ultima " +"versione stabile." #: cps/updater.py:495 cps/updater.py:509 cps/updater.py:520 #, python-format -msgid "A new update is available. Click on the button below to update to version: %(version)s" -msgstr "È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per aggiornare alla versione:%(version)s" +msgid "" +"A new update is available. Click on the button below to update to version: " +"%(version)s" +msgstr "" +"È disponibile un nuovo aggiornamento. Fai clic sul pulsante in basso per " +"aggiornare alla versione:%(version)s" #: cps/updater.py:538 msgid "No release information available" @@ -1429,11 +1553,14 @@ msgstr "Registrati" #: cps/web.py:1295 cps/web.py:1398 msgid "Connection error to limiter backend, please contact your administrator" -msgstr "Il server e-mail non è configurato, per favore contatta l'amministratore" +msgstr "" +"Il server e-mail non è configurato, per favore contatta l'amministratore" #: cps/web.py:1300 cps/web.py:1347 -msgid "Oops! Email server is not configured, please contact your administrator." -msgstr "Il server e-mail non è configurato, per favore contatta l'amministratore" +msgid "" +"Oops! Email server is not configured, please contact your administrator." +msgstr "" +"Il server e-mail non è configurato, per favore contatta l'amministratore" #: cps/web.py:1333 msgid "Oops! Your Email is not allowed." @@ -1458,8 +1585,12 @@ msgstr "ora sei connesso come: '%(nickname)s'" #: cps/web.py:1420 #, python-format -msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" -msgstr "Accesso di riserva come: '%(nickname)s', il server LDAP non è raggiungibile o l'utente è sconosciuto" +msgid "" +"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not " +"known" +msgstr "" +"Accesso di riserva come: '%(nickname)s', il server LDAP non è raggiungibile " +"o l'utente è sconosciuto" #: cps/web.py:1425 #, python-format @@ -2059,8 +2190,11 @@ msgid "Add Identifier" msgstr "Aggiungi identificatore" #: cps/templates/book_edit.html:133 -msgid "Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)" -msgstr "Recupera la copertina dall'URL (JPEG: l'immagine verrà scaricata e archiviata nel database)" +msgid "" +"Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)" +msgstr "" +"Recupera la copertina dall'URL (JPEG: l'immagine verrà scaricata e " +"archiviata nel database)" #: cps/templates/book_edit.html:137 msgid "Upload Cover from Local Disk" @@ -2307,7 +2441,8 @@ msgstr "Modifica i metadati" #: cps/templates/book_table.html:275 msgid "Edit the fields you want changed. Blank fields will be ignored:" -msgstr "Modifica i campi che desideri modificare. I campi vuoti saranno ignorati:" +msgstr "" +"Modifica i campi che desideri modificare. I campi vuoti saranno ignorati:" #: cps/templates/config_db.html:12 msgid "Location of Calibre Database" @@ -2339,7 +2474,8 @@ msgstr "Revoca" #: cps/templates/config_db.html:80 msgid "New db location is invalid, please enter valid path" -msgstr "La nuova posizione del database non è valida, inserisci un percorso valido" +msgstr "" +"La nuova posizione del database non è valida, inserisci un percorso valido" #: cps/templates/config_edit.html:18 msgid "Server Configuration" @@ -2351,11 +2487,15 @@ msgstr "Porta del server" #: cps/templates/config_edit.html:28 msgid "SSL certfile location (leave it empty for non-SSL Servers)" -msgstr "Posizione del file del certificato SSL (lascia vuoto per una configurazione del server senza SSL)" +msgstr "" +"Posizione del file del certificato SSL (lascia vuoto per una configurazione " +"del server senza SSL)" #: cps/templates/config_edit.html:35 msgid "SSL Keyfile location (leave it empty for non-SSL Servers)" -msgstr "Posizione del file della chiave SSL (lascia vuoto per una configurazione del server senza SSL)" +msgstr "" +"Posizione del file della chiave SSL (lascia vuoto per una configurazione del " +"server senza SSL)" #: cps/templates/config_edit.html:43 msgid "Update Channel" @@ -2379,7 +2519,8 @@ msgstr "Configurazione del file di log" #: cps/templates/config_edit.html:77 msgid "Location and name of logfile (calibre-web.log for no entry)" -msgstr "Posizione e nome del file di log (se non specificato sarà calibre-web.log)" +msgstr "" +"Posizione e nome del file di log (se non specificato sarà calibre-web.log)" #: cps/templates/config_edit.html:82 msgid "Enable Access Log" @@ -2387,7 +2528,9 @@ msgstr "Abilita il log degli accessi" #: cps/templates/config_edit.html:85 msgid "Location and name of access logfile (access.log for no entry)" -msgstr "Posizione e nome del file di log degli accessi (se non specificato sarà access.log)" +msgstr "" +"Posizione e nome del file di log degli accessi (se non specificato sarà " +"access.log)" #: cps/templates/config_edit.html:96 msgid "Feature Configuration" @@ -2395,11 +2538,17 @@ msgstr "Configurazione funzionalità" #: cps/templates/config_edit.html:104 msgid "Convert non-English characters in title and author while saving to disk" -msgstr "Converti caratteri non inglesi nel titolo e nell'autore durante il salvataggio su disco" +msgstr "" +"Converti caratteri non inglesi nel titolo e nell'autore durante il " +"salvataggio su disco" #: cps/templates/config_edit.html:108 -msgid "Embed Metadata to Ebook File on Download/Conversion/e-mail (needs Calibre/Kepubify binaries)" -msgstr "Incorpora metadati nel file del libro al momento del download e della conversione per e-mail (sono necessari gli eseguibili Calibre/Kepubify)" +msgid "" +"Embed Metadata to Ebook File on Download/Conversion/e-mail (needs Calibre/" +"Kepubify binaries)" +msgstr "" +"Incorpora metadati nel file del libro al momento del download e della " +"conversione per e-mail (sono necessari gli eseguibili Calibre/Kepubify)" #: cps/templates/config_edit.html:112 msgid "Enable Uploads" @@ -2407,7 +2556,9 @@ msgstr "Abilita il caricamento" #: cps/templates/config_edit.html:112 msgid "(Please ensure that users also have upload permissions)" -msgstr "(assicurati che gli utenti dispongano anche delle autorizzazioni di caricamento)" +msgstr "" +"(assicurati che gli utenti dispongano anche delle autorizzazioni di " +"caricamento)" #: cps/templates/config_edit.html:116 msgid "Allowed Upload Fileformats" @@ -2494,16 +2645,24 @@ msgid "SSL" msgstr "SSL" #: cps/templates/config_edit.html:213 -msgid "LDAP CACertificate Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso certificato CA LDAP (necessario solo per l'autenticazione del certificato client)" +msgid "" +"LDAP CACertificate Path (Only needed for Client Certificate Authentication)" +msgstr "" +"Percorso certificato CA LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:220 -msgid "LDAP Certificate Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso certificato LDAP (necessario solo per l'autenticazione del certificato client)" +msgid "" +"LDAP Certificate Path (Only needed for Client Certificate Authentication)" +msgstr "" +"Percorso certificato LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:227 msgid "LDAP Keyfile Path (Only needed for Client Certificate Authentication)" -msgstr "Percorso del file di chiavi LDAP (necessario solo per l'autenticazione del certificato client)" +msgstr "" +"Percorso del file di chiavi LDAP (necessario solo per l'autenticazione del " +"certificato client)" #: cps/templates/config_edit.html:236 msgid "LDAP Authentication" @@ -2543,7 +2702,8 @@ msgstr "Il server LDAP è OpenLDAP?" #: cps/templates/config_edit.html:267 msgid "Following Settings are Needed For User Import" -msgstr "Per l'importazione degli utenti sono necessarie le seguenti impostazioni" +msgstr "" +"Per l'importazione degli utenti sono necessarie le seguenti impostazioni" #: cps/templates/config_edit.html:269 msgid "LDAP Group Object Filter" @@ -2626,7 +2786,9 @@ msgstr "Opzioni per il limitatore del Backend" #: cps/templates/config_edit.html:386 msgid "Check if file extensions matches file content on upload" -msgstr "Controlla se le estensioni dei file corrispondono al contenuto del file al momento del caricamento" +msgstr "" +"Controlla se le estensioni dei file corrispondono al contenuto del file al " +"momento del caricamento" #: cps/templates/config_edit.html:389 msgid "Session protection" @@ -2662,7 +2824,8 @@ msgstr "Obbliga caratteri maiuscoli" #: cps/templates/config_edit.html:418 msgid "Enforce characters (needed For Chinese/Japanese/Korean Characters)" -msgstr "Obbliga caratteri (necessario per caratteri cinesi, giapponesi e coreani)" +msgstr "" +"Obbliga caratteri (necessario per caratteri cinesi, giapponesi e coreani)" #: cps/templates/config_edit.html:422 msgid "Enforce special characters" @@ -2678,7 +2841,8 @@ msgstr "Numero di libri casuali da mostrare" #: cps/templates/config_view_edit.html:36 msgid "No. of Authors to Display Before Hiding (0=Disable Hiding)" -msgstr "Numero di autori da mostrare prima di nascondere (0=disabilita nascondere)" +msgstr "" +"Numero di autori da mostrare prima di nascondere (0=disabilita nascondere)" #: cps/templates/config_view_edit.html:40 cps/templates/readcbr.html:101 msgid "Theme" @@ -2802,8 +2966,12 @@ msgid "Add to archive" msgstr "Aggiungi all'archivio" #: cps/templates/detail.html:275 -msgid "Mark Book as archived or not, to hide it in Calibre-Web and delete it from Kobo Reader" -msgstr "Contrassegna il libro come archiviato o no per nasconderlo in Calibre-Web ed eliminarlo da Kobo Reader" +msgid "" +"Mark Book as archived or not, to hide it in Calibre-Web and delete it from " +"Kobo Reader" +msgstr "" +"Contrassegna il libro come archiviato o no per nasconderlo in Calibre-Web ed " +"eliminarlo da Kobo Reader" #: cps/templates/detail.html:301 cps/templates/listenmp3.html:190 #: cps/templates/search.html:16 @@ -2884,8 +3052,12 @@ msgid "Denied Domains (Blacklist)" msgstr "Domini non consentiti (lista nera)" #: cps/templates/generate_kobo_auth_url.html:6 -msgid "Open the .kobo/Kobo/Kobo eReader.conf file in a text editor and add (or edit):" -msgstr "Apri il file .kobo/Kobo/Kobo eReader.conf in un editor di testo e aggiungi (o modifica):" +msgid "" +"Open the .kobo/Kobo/Kobo eReader.conf file in a text editor and add (or " +"edit):" +msgstr "" +"Apri il file .kobo/Kobo/Kobo eReader.conf in un editor di testo e aggiungi " +"(o modifica):" #: cps/templates/generate_kobo_auth_url.html:11 msgid "Kobo Token:" @@ -2897,7 +3069,8 @@ msgstr "Elenco" #: cps/templates/http_error.html:34 msgid "Calibre-Web Instance is unconfigured, please contact your administrator" -msgstr "L'istanza Calibre-Web non è configurata, per favore contatta l'amministratore" +msgstr "" +"L'istanza Calibre-Web non è configurata, per favore contatta l'amministratore" #: cps/templates/http_error.html:44 msgid "Create Issue" @@ -3101,7 +3274,8 @@ msgstr "Seleziona le categorie consentite/negate per l'utente" #: cps/templates/modal_dialogs.html:9 msgid "Select Allowed/Denied Custom Column Values of User" -msgstr "Seleziona i valori personali consentiti/negati per le colonne dell'utente" +msgstr "" +"Seleziona i valori personali consentiti/negati per le colonne dell'utente" #: cps/templates/modal_dialogs.html:15 msgid "Enter Tag" @@ -3117,15 +3291,23 @@ msgstr "Questo formato di libro verrà definitivamente cancellato dal database" #: cps/templates/modal_dialogs.html:51 msgid "This book will be permanently erased from database and hard disk" -msgstr "Questo libro verrà cancellato definitivamente dal database e dal disco rigido" +msgstr "" +"Questo libro verrà cancellato definitivamente dal database e dal disco rigido" #: cps/templates/modal_dialogs.html:55 -msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." -msgstr "Nota importante su Kobo: i libri eliminati rimarranno su qualsiasi dispositivo Kobo associato." +msgid "" +"Important Kobo Note: deleted books will remain on any paired Kobo device." +msgstr "" +"Nota importante su Kobo: i libri eliminati rimarranno su qualsiasi " +"dispositivo Kobo associato." #: cps/templates/modal_dialogs.html:56 -msgid "Books must first be archived and the device synced before a book can safely be deleted." -msgstr "I libri devono essere prima archiviati e il dispositivo sincronizzato prima che un libro possa essere eliminato in sicurezza." +msgid "" +"Books must first be archived and the device synced before a book can safely " +"be deleted." +msgstr "" +"I libri devono essere prima archiviati e il dispositivo sincronizzato prima " +"che un libro possa essere eliminato in sicurezza." #: cps/templates/modal_dialogs.html:75 msgid "Choose File Location" @@ -3181,7 +3363,7 @@ msgstr "Seppia" #: cps/templates/read.html:95 msgid "Amber" -msgstr "" +msgstr "Ambra" #: cps/templates/read.html:97 msgid "Black" @@ -3193,11 +3375,11 @@ msgstr "Adatta il testo quando le barre laterali sono aperte." #: cps/templates/read.html:114 msgid "Show pages count" -msgstr "" +msgstr "Mostra conteggio delle pagine" #: cps/templates/read.html:118 msgid "Font Size" -msgstr "" +msgstr "Dimensione carattere" #: cps/templates/read.html:126 msgid "Font" @@ -3582,20 +3764,31 @@ msgid "Actions" msgstr "Azioni" #: cps/templates/tasks.html:41 -msgid "This task will be cancelled. Any progress made by this task will be saved." -msgstr "Questa attività sarà annullata. Tutti i progressi compiuti da questa attività saranno salvati." +msgid "" +"This task will be cancelled. Any progress made by this task will be saved." +msgstr "" +"Questa attività sarà annullata. Tutti i progressi compiuti da questa " +"attività saranno salvati." #: cps/templates/tasks.html:42 -msgid "If this is a scheduled task, it will be re-ran during the next scheduled time." -msgstr "Se si tratta di un'attività pianificata, verrà eseguita nuovamente all'orario pianificato successivo." +msgid "" +"If this is a scheduled task, it will be re-ran during the next scheduled " +"time." +msgstr "" +"Se si tratta di un'attività pianificata, verrà eseguita nuovamente " +"all'orario pianificato successivo." #: cps/templates/user_edit.html:20 msgid "Reset user Password" msgstr "Reimposta la password dell'utente" #: cps/templates/user_edit.html:28 -msgid "Send to eReader Email Address. Use comma to separate emails for multiple eReaders" -msgstr "Invia all'indirizzo e-mail dell'eReader. Usa la virgola per separare le email per più eReader" +msgid "" +"Send to eReader Email Address. Use comma to separate emails for multiple " +"eReaders" +msgstr "" +"Invia all'indirizzo e-mail dell'eReader. Usa la virgola per separare le " +"email per più eReader" #: cps/templates/user_edit.html:43 msgid "Language of Books" @@ -3724,4 +3917,3 @@ msgstr "Sincronizza gli scaffali selezionati con Kobo" #: cps/templates/user_table.html:156 msgid "Show Read/Unread Section" msgstr "Mostra sezione Libri letti e Libri da leggere" -