diff --git a/cps.py b/cps.py index 039c2a5e1..85c061b04 100755 --- a/cps.py +++ b/cps.py @@ -42,9 +42,26 @@ 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, after reboot of calibre-web + # (initally the SIGINT signal CTRL+C is catched in server.py via the signal callback). + 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() diff --git a/cps/static/css/epub_themes.css b/cps/static/css/epub_themes.css index b9cbcedbe..209e65515 100644 --- a/cps/static/css/epub_themes.css +++ b/cps/static/css/epub_themes.css @@ -13,6 +13,11 @@ color: #000; } +.amberTheme { + background: #e8bf5e; + color: #000; +} + .blackTheme { background: #000; color: #fff; diff --git a/cps/static/css/main.css b/cps/static/css/main.css index 571f546c9..f590eda3a 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,21 +513,33 @@ 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; top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 100vw; - height: auto; + width: 97vw; + height: 90dvh; max-width: 630px; z-index: 2000; visibility: hidden; @@ -539,7 +565,7 @@ input:-moz-placeholder { color: #454545; } } .md-show ~ .overlay { - opacity: 1; + /* opacity: 1; */ visibility: visible; } @@ -554,7 +580,7 @@ input:-moz-placeholder { color: #454545; } } .md-content > div { - padding: 15px 40px 30px; + padding: 10px 20px; margin: 0; font-weight: 300; font-size: 14px; @@ -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/static/css/reader.css b/cps/static/css/reader.css index 7229a6e3b..a369cd97b 100644 --- a/cps/static/css/reader.css +++ b/cps/static/css/reader.css @@ -1,47 +1,74 @@ .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: 14px; + font-weight: 600; + 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, +#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/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(``);return o.swatches.appendChild(s),t.push({el:s,color:n}),this._eventBindings.push(i(s,"click",(()=>{this.setHSVA(...n.toHSVA(),!0),this._emit("swatchselect",n),this._emit("change",n,"swatch",this)}))),!0}return!1}removeSwatch(t){const e=this._swatchColors[t];if(e){const{el:o}=e;return this._root.swatches.removeChild(o),this._swatchColors.splice(t,1),!0}return!1}applyColor(t=!1){const{preview:e,button:o}=this._root,n=this._color.toRGBA().toString(0);return e.lastColor.style.setProperty("--pcr-color",n),this.options.useAsButton||o.style.setProperty("--pcr-color",n),o.classList.remove("clear"),this._lastColor=this._color.clone(),this._initializingActive||t||this._emit("save",this._color),this}destroy(){cancelAnimationFrame(this._setupAnimationFrame),this._eventBindings.forEach((t=>s(...t))),Object.keys(this._components).forEach((t=>this._components[t].destroy()))}destroyAndRemove(){this.destroy();const{root:t,app:e}=this._root;t.parentElement&&t.parentElement.removeChild(t),e.parentElement.removeChild(e),Object.keys(this).forEach((t=>this[t]=null))}hide(){return!!this.isOpen()&&(this._root.app.classList.remove("visible"),this._emit("hide"),!0)}show(){return!this.options.disabled&&!this.isOpen()&&(this._root.app.classList.add("visible"),this._rePositioningPicker(),this._emit("show",this._color),this)}isOpen(){return this._root.app.classList.contains("visible")}setHSVA(t=360,e=0,o=0,n=1,i=!1){const s=this._recalc;if(this._recalc=!1,t<0||t>360||e<0||e>100||o<0||o>100||n<0||n>1)return!1;this._color=A(t,e,o,n);const{hue:r,opacity:a,palette:l}=this._components;return r.update(t/360),a.update(n),l.update(e/100,1-o/100),i||this.applyColor(),s&&this._updateOutput(),this._recalc=s,!0}setColor(t,e=!1){if(null===t)return this._clearColor(e),!0;const{values:o,type:n}=this._parseLocalColor(t);if(o){const t=n.toUpperCase(),{options:i}=this._root.interaction,s=i.find((e=>e.getAttribute("data-type")===t));if(s&&!s.hidden)for(const t of i)t.classList[t===s?"add":"remove"]("active");return!!this.setHSVA(...o,e)&&this.setColorRepresentation(t)}return!1}setColorRepresentation(t){return t=t.toUpperCase(),!!this._root.interaction.options.find((e=>e.getAttribute("data-type").startsWith(t)&&!e.click()))}getColorRepresentation(){return this._representation}getColor(){return this._color}getSelectedColor(){return this._lastColor}getRoot(){return this._root}disable(){return this.hide(),this.options.disabled=!0,this._root.button.classList.add("disabled"),this}enable(){return this.options.disabled=!1,this._root.button.classList.remove("disabled"),this}}return e=e.default})())); +//# sourceMappingURL=pickr.min.js.map diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 8d299ce8b..bb56ef610 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -2,7 +2,7 @@ var reader; -(function() { +(function () { "use strict"; EPUBJS.filePath = calibre.filePath; @@ -10,7 +10,7 @@ var reader; reader = ePubReader(calibre.bookUrl, { restore: true, - bookmarks: calibre.bookmark ? [calibre.bookmark] : [] + bookmarks: calibre.bookmark ? [calibre.bookmark] : [], }); Object.keys(themes).forEach(function (theme) { @@ -54,29 +54,92 @@ var reader; // Update progress percentage let progressDiv = document.getElementById("progress"); - reader.book.ready.then((()=>{ - let locations_key = reader.book.key()+'-locations'; + // 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"; + // 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) { - make_locations = Promise.resolve(reader.book.locations.load(stored_locations)); + make_locations = Promise.resolve( + reader.book.locations.load(stored_locations) + ); // No-op because locations are already saved - save_locations = ()=>{}; + save_locations = () => {}; } else { make_locations = reader.book.locations.generate(); - save_locations = ()=>{ - localStorage.setItem(locations_key, reader.book.locations.save()); + save_locations = () => { + localStorage.setItem( + locations_key, + reader.book.locations.save() + ); }; } - make_locations.then(()=>{ - reader.rendition.on('relocated', (location)=>{ - let percentage = Math.round(location.end.percentage*100); - progressDiv.textContent=percentage+"%"; - }); - reader.rendition.reportLocation(); - progressDiv.style.visibility = "visible"; - }).then(save_locations); - })); + 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 + "%"; + + // 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; + + if (total > 0) { + pagesDiv.textContent = current + "/" + total; + pagesDiv.style.visibility = "visible"; + } else { + 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"; + }) + .then(save_locations); + }); /** * @param {string} action - Add or remove bookmark @@ -85,26 +148,43 @@ var reader; function updateBookmark(action, location) { // Remove other bookmarks (there can only be one) if (action === "add") { - this.settings.bookmarks.filter(function (bookmark) { - return bookmark && bookmark !== location; - }).map(function (bookmark) { - this.removeBookmark(bookmark); - }.bind(this)); + this.settings.bookmarks + .filter(function (bookmark) { + return bookmark && bookmark !== location; + }) + .map( + function (bookmark) { + this.removeBookmark(bookmark); + }.bind(this) + ); } - + var csrftoken = $("input[name='csrf_token']").val(); // Save to database $.ajax(calibre.bookmarkUrl, { method: "post", data: { bookmark: location || "" }, - headers: { "X-CSRFToken": csrftoken } + headers: { "X-CSRFToken": csrftoken }, }).fail(function (xhr, status, error) { alert(error); }); } - + // Default settings load const theme = localStorage.getItem("calibre.reader.theme") ?? "lightTheme"; selectTheme(theme); + + // 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 9c6e4bcac..86d7347e9 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -29,6 +29,7 @@ TOC Bookmarks + Books @@ -49,7 +50,6 @@ - Menu @@ -70,37 +70,58 @@ ‹ › - 0% - - + + + + + {{_('Settings')}} {{_('Choose a theme below:')}}} - + ✓{{_('Light')}} {{_('Dark')}} {{_('Sepia')}} + {{_('Amber')}} {{_('Black')}} - + + + Custom: + + + + + {{_('Reflow text when sidebars are open.')}} - - - {{ _('Font Sizes') }} - - - + + + {{_('Show pages count')}} + + + + {{ _('Font Size') }} + + − + 100% + + + + {{_('Font')}}: ✓{{_('Default')}} @@ -119,8 +140,7 @@ - + +
{{_('Reflow text when sidebars are open.')}}
+ {{_('Show pages count')}} +