diff --git a/.gitignore b/.gitignore index 2f7ff706..1fd4a446 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ composer.lock www/manual/* !www/manual/index.php config/php/fpm/standardebooks.org-secrets.ini +www/patrons-circle/downloads/*.zip diff --git a/scripts/generate-monthly-downloads b/scripts/generate-monthly-downloads new file mode 100755 index 00000000..72f176a8 --- /dev/null +++ b/scripts/generate-monthly-downloads @@ -0,0 +1,97 @@ +#!/usr/bin/php +Created->format('Y-m'); + $updatedTimestamp = $ebook->Updated->getTimestamp(); + + if(!isset($ebooksByMonth[$timestamp])){ + $ebooksByMonth[$timestamp] = []; + $lastUpdatedTimestamps[$timestamp] = $updatedTimestamp; + } + + $ebooksByMonth[$timestamp][] = $ebook; + if($updatedTimestamp > $lastUpdatedTimestamps[$timestamp]){ + $lastUpdatedTimestamps[$timestamp] = $updatedTimestamp; + } + } + catch(\Exception $ex){ + print('Failed to generate download for `' . $ebookWwwFilesystemPath . '`. Exception: ' . $ex->getMessage()); + continue; + } +} + +foreach($ebooksByMonth as $month => $ebooks){ + $filename = 'se-ebooks-' . $month . '.zip'; + $filePath = $webRoot . '/www/patrons-circle/downloads/' . $filename; + + // If the file doesn't exist, or if the content.opf last updated time is newer than the file creation time + if(!file_exists($filePath) || filemtime($filePath) < $lastUpdatedTimestamps[$month]){ + print('Creating ' . $filePath . "\n"); + + $tempFilename = tempnam(sys_get_temp_dir(), "se-ebooks"); + + $zip = new ZipArchive(); + + if($zip->open($tempFilename, ZipArchive::CREATE) !== true){ + print('Can\'t open file: ' . $tempFilename . "\n"); + continue; + } + + foreach($ebooks as $ebook){ + if($ebook->EpubUrl !== null){ + $ebookFilePath = $webRoot . '/www' . $ebook->EpubUrl; + $zip->addFile($ebookFilePath, $ebook->UrlSafeIdentifier . '/' . basename($ebookFilePath)); + } + + if($ebook->Azw3Url !== null){ + $ebookFilePath = $webRoot . '/www' . $ebook->Azw3Url; + $zip->addFile($ebookFilePath, $ebook->UrlSafeIdentifier . '/' . basename($ebookFilePath)); + } + + if($ebook->KepubUrl !== null){ + $ebookFilePath = $webRoot . '/www' . $ebook->KepubUrl; + $zip->addFile($ebookFilePath, $ebook->UrlSafeIdentifier . '/' . basename($ebookFilePath)); + } + + if($ebook->AdvancedEpubUrl !== null){ + $ebookFilePath = $webRoot . '/www' . $ebook->AdvancedEpubUrl; + $zip->addFile($ebookFilePath, $ebook->UrlSafeIdentifier . '/' . basename($ebookFilePath)); + } + + if($ebook->TextSinglePageUrl !== null){ + $ebookFilePath = $webRoot . '/www' . $ebook->TextSinglePageUrl . '.xhtml'; + $zip->addFile($ebookFilePath, $ebook->UrlSafeIdentifier . '/' . str_replace('single-page', $ebook->UrlSafeIdentifier, basename($ebookFilePath))); + } + } + + $zip->close(); + + rename($tempFilename, $filePath); + } +} diff --git a/templates/Footer.php b/templates/Footer.php index 342e2bb1..4435f112 100644 --- a/templates/Footer.php +++ b/templates/Footer.php @@ -19,6 +19,9 @@
  • GitHub
  • +
  • + Bulk downloads +
  • Ebook Feeds
  • diff --git a/www/css/core.css b/www/css/core.css index 41ad7677..75119b56 100644 --- a/www/css/core.css +++ b/www/css/core.css @@ -246,6 +246,7 @@ main > section.narrow > *{ main > section.narrow > h1, main > section.narrow > hgroup, main > section.narrow.has-hero > picture{ + box-sizing: border-box; max-width: none; } @@ -671,10 +672,49 @@ ul.message.error li:only-child{ margin-left: 0; } +.bulk-downloads > p{ + width: 100%; + max-width: 40rem; + margin-left: auto; + margin-right: auto; +} + +.bulk-downloads i{ + color: var(--sub-text); +} + +.bulk-downloads > ul{ + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 4rem; + list-style: none; + margin-top: 4rem; + margin-left: auto; + margin-right: auto; +} + +.bulk-downloads > ul > li{ + margin: 0; +} + +.bulk-downloads ul ul{ + list-style: none; + margin: 0; +} + +.bulk-downloads > ul > li li:first-of-type{ + margin-top: 1rem; +} + +.bulk-downloads h2{ + margin-top: 0; +} + .ebooks nav li.highlighted a:focus, a.button:focus, input[type="email"]:focus, input[type="text"]:focus, +input[type="month"]:focus, input[type="search"]:focus, label.checkbox:focus-within, select:focus, @@ -1199,6 +1239,11 @@ footer > p{ display: inlie-flex; align-items: center; } + +footer > p:first-child{ + font-size: .9rem; +} + footer > p:first-child::before{ font-family: "Fork Awesome"; content: "\f0e0"; @@ -1571,6 +1616,7 @@ label.search{ select, input[type="text"], +input[type="month"], input[type="email"], input[type="search"]{ -webkit-appearance: none; @@ -1674,6 +1720,7 @@ nav a[rel], a.button, button, input[type="text"], +input[type="month"], input[type="email"], input[type="search"], select{ @@ -1689,6 +1736,8 @@ button:hover{ input[type="text"]:focus, input[type="text"]:hover, +input[type="month"]:focus, +input[type="month"]:hover, input[type="email"]:focus, input[type="email"]:hover, input[type="search"]:focus, @@ -1701,6 +1750,7 @@ select:hover{ } input[type="text"]:user-invalid, +input[type="month"]:user-invalid, input[type="email"]:user-invalid, input[type="search"]:user-invalid{ border-color: #ff0000; @@ -1708,6 +1758,7 @@ input[type="search"]:user-invalid{ } input[type="text"]:-moz-ui-invalid, +input[type="month"]:-moz-ui-invalid, input[type="email"]:-moz-ui-invalid, input[type="search"]:-moz-ui-invalid{ border-color: #ff0000; @@ -2824,6 +2875,12 @@ ul.feed p{ } } +@media(max-width: 800px){ + .bulk-downloads > ul{ + grid-template-columns: 1fr 1fr; + } +} + @media(max-width: 730px){ form[action="/ebooks"]{ grid-template-columns: auto auto 1fr 1fr; @@ -3119,6 +3176,10 @@ ul.feed p{ .votes tr + tr{ margin-top: 2rem; } + + .bulk-downloads > ul{ + grid-template-columns: 1fr; + } } @media(max-width: 470px){ @@ -3281,6 +3342,7 @@ ul.feed p{ a.button, button, input[type="text"], + input[type="month"], input[type="email"], input[type="search"], select, @@ -3290,6 +3352,8 @@ ul.feed p{ button:hover, input[type="text"]:focus, input[type="text"]:hover, + input[type="month"]:focus, + input[type="month"]:hover, input[type="email"]:focus, input[type="email"]:hover, input[type="search"]:focus, diff --git a/www/css/dark.css b/www/css/dark.css index 84df020c..dfa21fe9 100644 --- a/www/css/dark.css +++ b/www/css/dark.css @@ -55,6 +55,7 @@ article.ebook section aside.donation{ select, input[type="text"], +input[type="month"], input[type="email"], input[type="search"]{ box-shadow: 1px 1px 0 rgba(0, 0, 0, .5) inset; diff --git a/www/images/still-life-with-books.avif b/www/images/still-life-with-books.avif new file mode 100644 index 00000000..6a7ea0f1 Binary files /dev/null and b/www/images/still-life-with-books.avif differ diff --git a/www/images/still-life-with-books.jpg b/www/images/still-life-with-books.jpg new file mode 100644 index 00000000..6e7e959b Binary files /dev/null and b/www/images/still-life-with-books.jpg differ diff --git a/www/images/still-life-with-books@2x.avif b/www/images/still-life-with-books@2x.avif new file mode 100644 index 00000000..cda6b502 Binary files /dev/null and b/www/images/still-life-with-books@2x.avif differ diff --git a/www/images/still-life-with-books@2x.jpg b/www/images/still-life-with-books@2x.jpg new file mode 100644 index 00000000..085243c4 Binary files /dev/null and b/www/images/still-life-with-books@2x.jpg differ diff --git a/www/images/the-shop-of-the-bookdealer.avif b/www/images/the-shop-of-the-bookdealer.avif new file mode 100644 index 00000000..a3b10f2b Binary files /dev/null and b/www/images/the-shop-of-the-bookdealer.avif differ diff --git a/www/images/the-shop-of-the-bookdealer.jpg b/www/images/the-shop-of-the-bookdealer.jpg new file mode 100644 index 00000000..cede7a52 Binary files /dev/null and b/www/images/the-shop-of-the-bookdealer.jpg differ diff --git a/www/images/the-shop-of-the-bookdealer@2x.avif b/www/images/the-shop-of-the-bookdealer@2x.avif new file mode 100644 index 00000000..32d3d178 Binary files /dev/null and b/www/images/the-shop-of-the-bookdealer@2x.avif differ diff --git a/www/images/the-shop-of-the-bookdealer@2x.jpg b/www/images/the-shop-of-the-bookdealer@2x.jpg new file mode 100644 index 00000000..7d63ad12 Binary files /dev/null and b/www/images/the-shop-of-the-bookdealer@2x.jpg differ diff --git a/www/patrons-circle/downloads/index.php b/www/patrons-circle/downloads/index.php new file mode 100644 index 00000000..eb61c6e3 --- /dev/null +++ b/www/patrons-circle/downloads/index.php @@ -0,0 +1,58 @@ +Month = $date->format('F'); + $obj->Url = '/patrons-circle/downloads/' . basename($file); + $obj->Updated = $updated->format('M i'); + + if($updated->format('Y') != gmdate('Y')){ + $obj->Updated = $obj->Updated . $updated->format(', Y'); + } + + $year = $date->format('Y'); + + if(!isset($years[$year])){ + $years[$year] = []; + } + + $years[$year][] = $obj; +} + +?> 'Download Ebooks by Month', 'highlight' => '', 'description' => 'Download zip files containing all of the Standard Ebooks released in a given month.']) ?> +
    +
    +

    Download Ebooks by Month

    + + + + A pile of moldering books. + +

    Patrons circle members can download zip files containing all of the ebooks that were released in a given month of Standard Ebooks history. You can join the Patrons Circle with a small donation in support of our continuing mission to create free, beautiful digital literature.

    +

    These zip files contain each ebook in every format we offer, and are updated once daily with the latest versions of each ebook.

    +

    If you’re a Patrons Circle member, when prompted enter your email address and no password to download these files.

    + +
    +
    +