diff --git a/LICENSE.txt b/LICENSE.txt index 83b89fc85..4522be651 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -367,4 +367,85 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------- +This product bundles the Hyde theme, a port of the Hyde theme for Jekyll, +which is available under a "MIT" license. For details, see +themes/hyde/theme.css +https://github.com/poole/hyde/blob/master/LICENSE.md + +Copyright (c) 2013 Mark Otto. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- +This product bundles the PaperMod theme, a port of the PaperMod theme for +Hugo, which is available under a "MIT" license. For details, see +themes/papermod/theme.css +https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE + +MIT License + +Copyright (c) 2020 nanxiaobei and adityatelange +Copyright (c) 2021-2026 adityatelange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------- +This product bundles the Terminal theme, a port of the Terminal theme for +Hugo, which is available under a "MIT" license. For details, see +themes/terminal/theme.css +https://github.com/panr/hugo-theme-terminal/blob/master/LICENSE.md + +The MIT License (MIT) + +Copyright (c) 2024 panr + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- diff --git a/app/src/main/webapp/themes/hyde/archives.vm b/app/src/main/webapp/themes/hyde/archives.vm new file mode 100644 index 000000000..f53eda9aa --- /dev/null +++ b/app/src/main/webapp/themes/hyde/archives.vm @@ -0,0 +1,62 @@ + + + + #includeTemplate($model.weblog "standard_head") + Archives · $model.weblog.name + + + #includeTemplate($model.weblog "standard_sidebar") + +
+
+

Archives

+ + + #set($archiveEntries = $model.weblog.getRecentWeblogEntries("nil", 100)) + #set($currentYear = "") + #foreach($archiveEntry in $archiveEntries) + #set($entryYear = $utils.formatDate($archiveEntry.pubTime, "yyyy")) + #if($entryYear != $currentYear) + #if($currentYear != "") + + #end + #set($currentYear = $entryYear) +

$entryYear

+ + #end + + #set($archiveCategories = $model.weblog.weblogCategories) + #if($archiveCategories.size() > 0) +

Categories

+ + #end + + #set($archiveTags = $model.weblog.getPopularTags(-1, 50)) + #if($archiveTags.size() > 0) +

Tags

+ + #end +
+
+ + diff --git a/app/src/main/webapp/themes/hyde/custom.css b/app/src/main/webapp/themes/hyde/custom.css new file mode 100644 index 000000000..b102dc949 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/custom.css @@ -0,0 +1,7 @@ +/* Add weblog-specific Hyde overrides here. */ +/* To recolor the sidebar and links, set --hyde-accent to one of Hyde's + Base16 colors, for example: + body { --hyde-accent: #6a9fb5; } + red #ac4142 (default), orange #d28445, yellow #f4bf75, green #90a959, + cyan #75b5aa, blue #6a9fb5, magenta #aa759f, brown #8f5536, or Hyde's + original dark gray #202020. */ diff --git a/app/src/main/webapp/themes/hyde/entry.vm b/app/src/main/webapp/themes/hyde/entry.vm new file mode 100644 index 000000000..daba9a3d5 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/entry.vm @@ -0,0 +1,91 @@ + + + + #includeTemplate($model.weblog "standard_head") + $model.weblogEntry.title · $model.weblog.name + + + #includeTemplate($model.weblog "standard_sidebar") + +
+ #set($entry = $model.weblogEntry) +
+

$entry.title

+ + $entry.displayContent + + +
+ + ## Hyde's related posts: other recent entries in this entry's category. + #set($relatedEntries = []) + #if($entry.category) + #set($candidates = $model.weblog.getRecentWeblogEntries($entry.category.name, 4)) + #else + #set($candidates = $model.weblog.getRecentWeblogEntries("nil", 4)) + #end + #foreach($candidate in $candidates) + #if($candidate.anchor != $entry.anchor && $relatedEntries.size() < 3) + #set($added = $relatedEntries.add($candidate)) + #end + #end + #if($relatedEntries.size() > 0) + + #end + + #set($pager = $model.getWeblogEntriesPager()) + #if($pager.prevLink || $pager.nextLink) + + #end + +
+ #showWeblogEntryComments($entry) + #showWeblogEntryCommentForm($entry) +
+
+ + diff --git a/app/src/main/webapp/themes/hyde/head.vm b/app/src/main/webapp/themes/hyde/head.vm new file mode 100644 index 000000000..3281b5d6f --- /dev/null +++ b/app/src/main/webapp/themes/hyde/head.vm @@ -0,0 +1,10 @@ + + + + + + + +#showAutodiscoveryLinks($model.weblog) +#showMetaDescription() +#showAnalyticsTrackingCode($model.weblog) diff --git a/app/src/main/webapp/themes/hyde/post-list.vm b/app/src/main/webapp/themes/hyde/post-list.vm new file mode 100644 index 000000000..38b33c63f --- /dev/null +++ b/app/src/main/webapp/themes/hyde/post-list.vm @@ -0,0 +1,45 @@ +## Hyde's index.html post list and pagination, shared by the weblog, tag, and +## search templates. Expects $pager to be set by the including template. +## Like Hyde, each post is shown in full; entries with a summary show the +## summary and Roller's "Read More" link instead. +#set($listEntries = []) +#foreach($day in $pager.entries.keySet()) + #foreach($entry in $pager.entries.get($day)) + #set($added = $listEntries.add($entry)) + #end +#end +
+ #foreach($entry in $listEntries) +
+

+ $entry.title +

+ + + + #if($model.searchResults) +

$utils.truncateNicely($utils.removeHTML($entry.transformedText), 240, 300, "…")

+ #else + $entry.displayContent($url.entry($entry.anchor)) + #end +
+ #end + #if($listEntries.size() == 0) +

No posts here yet.

+ #end +
+ +#if($pager.prevLink || $pager.nextLink) + +#end diff --git a/app/src/main/webapp/themes/hyde/preview.png b/app/src/main/webapp/themes/hyde/preview.png new file mode 100644 index 000000000..1d604622e Binary files /dev/null and b/app/src/main/webapp/themes/hyde/preview.png differ diff --git a/app/src/main/webapp/themes/hyde/search.vm b/app/src/main/webapp/themes/hyde/search.vm new file mode 100644 index 000000000..cb8f3d773 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/search.vm @@ -0,0 +1,35 @@ + + + + #includeTemplate($model.weblog "standard_head") + Search: $model.term · $model.weblog.name + + + #includeTemplate($model.weblog "standard_sidebar") + +
+
+

Search

+ + #if($utils.isNotEmpty($model.errorMessage)) +

$model.errorMessage

+ #elseif($model.hits > 0) + #set($firstHit = $model.offset + 1) + #set($lastHit = $model.offset + $model.limit) + #if($lastHit > $model.hits)#set($lastHit = $model.hits)#end + + #elseif($utils.isNotEmpty($model.term)) +

No posts matched “$model.term”. Try a different word.

+ #end +
+ #if($model.hits > 0) + #set($pager = $model.getWeblogEntriesPager()) + #includeTemplate($model.weblog "_post_list") + #end +
+ + diff --git a/app/src/main/webapp/themes/hyde/sidebar.vm b/app/src/main/webapp/themes/hyde/sidebar.vm new file mode 100644 index 000000000..159c5a5bd --- /dev/null +++ b/app/src/main/webapp/themes/hyde/sidebar.vm @@ -0,0 +1,29 @@ +## Hyde's _includes/sidebar.html. + diff --git a/app/src/main/webapp/themes/hyde/tags.vm b/app/src/main/webapp/themes/hyde/tags.vm new file mode 100644 index 000000000..684820211 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/tags.vm @@ -0,0 +1,16 @@ + + + + #includeTemplate($model.weblog "standard_head") + #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end · $model.weblog.name + + + #includeTemplate($model.weblog "standard_sidebar") + +
+

Tagged #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end

+ #set($pager = $model.getWeblogEntriesPager()) + #includeTemplate($model.weblog "_post_list") +
+ + diff --git a/app/src/main/webapp/themes/hyde/theme.css b/app/src/main/webapp/themes/hyde/theme.css new file mode 100644 index 000000000..99c8486b1 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/theme.css @@ -0,0 +1,1026 @@ +/*! + * Hyde for Apache Roller + * + * Ported from Hyde (https://github.com/poole/hyde, commit 7c7f755): + * public/css/poole.css and public/css/hyde.css, unchanged, followed by the + * Apache Roller additions at the end of this file. + * + * Released under MIT License + * + * Copyright (c) 2013 Mark Otto. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * ___ + * /\_ \ + * _____ ___ ___\//\ \ __ + * /\ '__`\ / __`\ / __`\\ \ \ /'__`\ + * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/ + * \ \ ,__/\ \____/\ \____//\____\ \____\ + * \ \ \/ \/___/ \/___/ \/____/\/____/ + * \ \_\ + * \/_/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/poole. + */ + + +/* + * Contents + * + * Body resets + * Custom type + * Messages + * Container + * Masthead + * Posts and pages + * Pagination + * Reverse layout + * Themes + */ + + +/* + * Body resets + * + * Update the foundational and global aspects of the page. + */ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +html { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; +} +@media (min-width: 38em) { + html { + font-size: 20px; + } +} + +body { + color: #515151; + background-color: #fff; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +/* No `:visited` state is required by default (browsers will use `a`) */ +a { + color: #268bd2; + text-decoration: none; +} +a strong { + color: inherit; +} +/* `:focus` is linked to `:hover` for basic accessibility */ +a:hover, +a:focus { + text-decoration: underline; +} + +/* Headings */ +h1, h2, h3, h4, h5, h6 { + margin-bottom: .5rem; + font-weight: bold; + line-height: 1.25; + color: #313131; + text-rendering: optimizeLegibility; +} +h1 { + font-size: 2rem; +} +h2 { + margin-top: 1rem; + font-size: 1.5rem; +} +h3 { + margin-top: 1.5rem; + font-size: 1.25rem; +} +h4, h5, h6 { + margin-top: 1rem; + font-size: 1rem; +} + +/* Body text */ +p { + margin-top: 0; + margin-bottom: 1rem; +} + +strong { + color: #303030; +} + + +/* Lists */ +ul, ol, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +dt { + font-weight: bold; +} +dd { + margin-bottom: .5rem; +} + +/* Misc */ +hr { + position: relative; + margin: 1.5rem 0; + border: 0; + border-top: 1px solid #eee; + border-bottom: 1px solid #fff; +} + +abbr { + font-size: 85%; + font-weight: bold; + color: #555; + text-transform: uppercase; +} +abbr[title] { + cursor: help; + border-bottom: 1px dotted #e5e5e5; +} + +/* Code */ +code, +pre { + font-family: Menlo, Monaco, "Courier New", monospace; +} +code { + padding: .25em .5em; + font-size: 85%; + color: #bf616a; + background-color: #f9f9f9; + border-radius: 3px; +} +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + padding: 1rem; + font-size: .8rem; + line-height: 1.4; + white-space: pre; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; + background-color: #f9f9f9; +} +pre code { + padding: 0; + font-size: 100%; + color: inherit; + background-color: transparent; +} + +/* Pygments via Jekyll */ +.highlight { + margin-bottom: 1rem; + border-radius: 4px; +} +.highlight pre { + margin-bottom: 0; +} + +/* Gist via GitHub Pages */ +.gist .gist-file { + font-family: Menlo, Monaco, "Courier New", monospace !important; +} +.gist .markdown-body { + padding: 15px; +} +.gist pre { + padding: 0; + background-color: transparent; +} +.gist .gist-file .gist-data { + font-size: .8rem !important; + line-height: 1.4; +} +.gist code { + padding: 0; + color: inherit; + background-color: transparent; + border-radius: 0; +} + +/* Quotes */ +blockquote { + padding: .5rem 1rem; + margin: .8rem 0; + color: #7a7a7a; + border-left: .25rem solid #e5e5e5; +} +blockquote p:last-child { + margin-bottom: 0; +} +@media (min-width: 30em) { + blockquote { + padding-right: 5rem; + padding-left: 1.25rem; + } +} + +img { + display: block; + max-width: 100%; + margin: 0 0 1rem; + border-radius: 5px; +} + +/* Tables */ +table { + margin-bottom: 1rem; + width: 100%; + border: 1px solid #e5e5e5; + border-collapse: collapse; +} +td, +th { + padding: .25rem .5rem; + border: 1px solid #e5e5e5; +} +tbody tr:nth-child(odd) td, +tbody tr:nth-child(odd) th { + background-color: #f9f9f9; +} + + +/* + * Custom type + * + * Extend paragraphs with `.lead` for larger introductory text. + */ + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + + +/* + * Messages + * + * Show alert messages to users. You may add it to single elements like a `

`, + * or to a parent if there are multiple elements to show. + */ + +.message { + margin-bottom: 1rem; + padding: 1rem; + color: #717171; + background-color: #f9f9f9; +} + + +/* + * Container + * + * Center the page content. + */ + +.container { + max-width: 38rem; + padding-left: 1rem; + padding-right: 1rem; + margin-left: auto; + margin-right: auto; +} + + +/* + * Masthead + * + * Super small header above the content for site name and short description. + */ + +.masthead { + padding-top: 1rem; + padding-bottom: 1rem; + margin-bottom: 3rem; +} +.masthead-title { + margin-top: 0; + margin-bottom: 0; + color: #505050; +} +.masthead-title a { + color: #505050; +} +.masthead-title small { + font-size: 75%; + font-weight: 400; + color: #c0c0c0; + letter-spacing: 0; +} + + +/* + * Posts and pages + * + * Each post is wrapped in `.post` and is used on default and post layouts. Each + * page is wrapped in `.page` and is only used on the page layout. + */ + +.page, +.post { + margin-bottom: 4em; +} + +/* Blog post or page title */ +.page-title, +.post-title, +.post-title a { + color: #303030; +} +.page-title, +.post-title { + margin-top: 0; +} + +/* Meta data line below post title */ +.post-date { + display: block; + margin-top: -.5rem; + margin-bottom: 1rem; + color: #9a9a9a; +} + +/* Related posts */ +.related { + padding-top: 2rem; + padding-bottom: 2rem; + border-top: 1px solid #eee; +} +.related-posts { + padding-left: 0; + list-style: none; +} +.related-posts h3 { + margin-top: 0; +} +.related-posts li small { + font-size: 75%; + color: #999; +} +.related-posts li a:hover { + color: #268bd2; + text-decoration: none; +} +.related-posts li a:hover small { + color: inherit; +} + + +/* + * Pagination + * + * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when + * there are no more previous or next posts to show. + */ + +.pagination { + overflow: hidden; /* clearfix */ + margin-left: -1rem; + margin-right: -1rem; + font-family: "PT Sans", Helvetica, Arial, sans-serif; + color: #ccc; + text-align: center; +} + +/* Pagination items can be `span`s or `a`s */ +.pagination-item { + display: block; + padding: 1rem; + border: 1px solid #eee; +} +.pagination-item:first-child { + margin-bottom: -1px; +} + +/* Only provide a hover state for linked pagination items */ +a.pagination-item:hover { + background-color: #f5f5f5; +} + +@media (min-width: 30em) { + .pagination { + margin: 3rem 0; + } + .pagination-item { + float: left; + width: 50%; + } + .pagination-item:first-child { + margin-bottom: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .pagination-item:last-child { + margin-left: -1px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} + +/* + * __ __ + * /\ \ /\ \ + * \ \ \___ __ __ \_\ \ __ + * \ \ _ `\/\ \/\ \ /'_` \ /'__`\ + * \ \ \ \ \ \ \_\ \/\ \_\ \/\ __/ + * \ \_\ \_\/`____ \ \___,_\ \____\ + * \/_/\/_/`/___/> \/__,_ /\/____/ + * /\___/ + * \/__/ + * + * Designed, built, and released under MIT license by @mdo. Learn more at + * https://github.com/poole/hyde. + */ + + +/* + * Contents + * + * Global resets + * Sidebar + * Container + * Reverse layout + * Themes + */ + + +/* + * Global resets + * + * Update the foundational and global aspects of the page. + */ + +html { + font-family: "PT Sans", Helvetica, Arial, sans-serif; +} +@media (min-width: 48em) { + html { + font-size: 16px; + } +} +@media (min-width: 58em) { + html { + font-size: 20px; + } +} + + +/* + * Sidebar + * + * Flexible banner for housing site name, intro, and "footer" content. Starts + * out above content in mobile and later moves to the side with wider viewports. + */ + +.sidebar { + text-align: center; + padding: 2rem 1rem; + color: rgba(255,255,255,.5); + background-color: #202020; +} +@media (min-width: 48em) { + .sidebar { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 18rem; + text-align: left; + } +} + +/* Sidebar links */ +.sidebar a { + color: #fff; +} + +/* About section */ +.sidebar-about h1 { + color: #fff; + margin-top: 0; + font-family: "Abril Fatface", serif; + font-size: 3.25rem; +} + +/* Sidebar nav */ +.sidebar-nav { + margin-bottom: 1rem; +} +.sidebar-nav-item { + display: block; + line-height: 1.75; +} +a.sidebar-nav-item:hover, +a.sidebar-nav-item:focus { + text-decoration: underline; +} +.sidebar-nav-item.active { + font-weight: bold; +} + +/* Sticky sidebar + * + * Add the `sidebar-sticky` class to the sidebar's container to affix it the + * contents to the bottom of the sidebar in tablets and up. + */ + +@media (min-width: 48em) { + .sidebar-sticky { + position: absolute; + right: 1rem; + bottom: 1rem; + left: 1rem; + } +} + + +/* Container + * + * Align the contents of the site above the proper threshold with some margin-fu + * with a 25%-wide `.sidebar`. + */ + +.content { + padding-top: 4rem; + padding-bottom: 4rem; +} + +@media (min-width: 48em) { + .content { + max-width: 38rem; + margin-left: 20rem; + margin-right: 2rem; + } +} + +@media (min-width: 64em) { + .content { + margin-left: 22rem; + margin-right: 4rem; + } +} + + +/* + * Reverse layout + * + * Flip the orientation of the page by placing the `.sidebar` on the right. + */ + +@media (min-width: 48em) { + .layout-reverse .sidebar { + left: auto; + right: 0; + } + .layout-reverse .content { + margin-left: 2rem; + margin-right: 20rem; + } +} + +@media (min-width: 64em) { + .layout-reverse .content { + margin-left: 4rem; + margin-right: 22rem; + } +} + + + +/* + * Themes + * + * As of v1.1, Hyde includes optional themes to color the sidebar and links + * within blog posts. To use, add the class of your choosing to the `body`. + */ + +/* Base16 (http://chriskempson.github.io/base16/#default) */ + +/* Red */ +.theme-base-08 .sidebar { + background-color: #ac4142; +} +.theme-base-08 .content a, +.theme-base-08 .related-posts li a:hover { + color: #ac4142; +} + +/* Orange */ +.theme-base-09 .sidebar { + background-color: #d28445; +} +.theme-base-09 .content a, +.theme-base-09 .related-posts li a:hover { + color: #d28445; +} + +/* Yellow */ +.theme-base-0a .sidebar { + background-color: #f4bf75; +} +.theme-base-0a .content a, +.theme-base-0a .related-posts li a:hover { + color: #f4bf75; +} + +/* Green */ +.theme-base-0b .sidebar { + background-color: #90a959; +} +.theme-base-0b .content a, +.theme-base-0b .related-posts li a:hover { + color: #90a959; +} + +/* Cyan */ +.theme-base-0c .sidebar { + background-color: #75b5aa; +} +.theme-base-0c .content a, +.theme-base-0c .related-posts li a:hover { + color: #75b5aa; +} + +/* Blue */ +.theme-base-0d .sidebar { + background-color: #6a9fb5; +} +.theme-base-0d .content a, +.theme-base-0d .related-posts li a:hover { + color: #6a9fb5; +} + +/* Magenta */ +.theme-base-0e .sidebar { + background-color: #aa759f; +} +.theme-base-0e .content a, +.theme-base-0e .related-posts li a:hover { + color: #aa759f; +} + +/* Brown */ +.theme-base-0f .sidebar { + background-color: #8f5536; +} +.theme-base-0f .content a, +.theme-base-0f .related-posts li a:hover { + color: #8f5536; +} + +/* ========================================================================== + Apache Roller additions + ========================================================================== */ + +/* + * Sidebar color + * + * Hyde colors the sidebar and content links with a body class; the port uses + * a variable instead so custom.css can recolor it in one line. The default is + * Hyde's Base16 red (theme-base-08). The theme-base-* classes above still work. + */ + +body { + --hyde-accent: #ac4142; +} +.sidebar { + background-color: var(--hyde-accent); +} +.content a, +.related-posts li a:hover { + color: var(--hyde-accent); +} +.sidebar .lead, +.sidebar p { + color: rgba(255,255,255,.75); +} + +/* Fonts: PT Sans and Abril Fatface are not bundled, so fall back to local + faces with a similar feel */ +html { + font-family: "PT Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +} +.sidebar-about h1 { + font-family: "Abril Fatface", "Bodoni 72", "Bodoni MT", Didot, "Playfair Display", Georgia, serif; + overflow-wrap: break-word; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Page headings on tag, category, search, and archive pages */ +.content > .page-title { + margin-bottom: 2rem; +} +.message > :last-child { + margin-bottom: 0; +} + +/* Entry footer: author, category, tags, edit link */ +.post-meta { + margin-top: 1.5rem; + font-size: .8rem; + color: #9a9a9a; +} +.post-meta > * + * { + margin-left: .25rem; +} +.post-tags a { + display: inline-block; + padding: 0 .5rem; + margin-right: .25rem; + border: 1px solid #e5e5e5; + border-radius: 3px; +} +.post-tags a:hover { + text-decoration: none; + border-color: var(--hyde-accent); +} +.post-edit { + margin-left: .5rem; +} +.readmore { + font-weight: bold; +} + +/* Entry pager: allow long entry titles in Hyde's two-up pagination */ +.pagination-item { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Comments (markup from Roller's #showWeblogEntryComments and + #showWeblogEntryCommentForm) */ +.post-comments { + padding-top: 2rem; + border-top: 1px solid #eee; +} +.post-comments br { + display: none; +} +.comments-head { + margin: 0 0 1rem; + font-size: 1.25rem; + font-weight: bold; + line-height: 1.25; + color: #313131; +} +.comments { + margin-bottom: 2rem; +} +.comments:not(:has(.comment)) { + display: none; +} +.comment { + margin-bottom: 1rem; + padding: .5rem 1rem; + border-left: .25rem solid #e5e5e5; +} +.comment-details { + margin-bottom: 0; + font-size: .8rem; + color: #9a9a9a; +} +.comment .entrypermalink { + margin-left: .25rem; +} +.comments-form ul { + padding-left: 0; + list-style: none; +} +.comments-form li { + margin-bottom: 1rem; +} +.comments-form label.desc { + display: block; + margin-bottom: .25rem; + font-size: .8rem; + font-weight: bold; +} +.comments-form label.choice { + margin-left: .25rem; + font-size: .8rem; +} +.comments-form li.info { + font-size: .8rem; + color: #9a9a9a; +} +.comments-form .status, +.comments-form .error { + display: block; + margin-bottom: 1rem; + padding: 1rem; + color: #717171; + background-color: #f9f9f9; +} +.comments-form .error { + color: #ac4142; +} + +/* Form controls */ +input:not([type]), +input[type=text], +input[type=search], +textarea { + max-width: 100%; + padding: .5rem .75rem; + font: inherit; + font-size: .9rem; + color: #515151; + background-color: #fff; + border: 1px solid #e5e5e5; + border-radius: 4px; +} +.comments-form input.text, +.comments-form textarea { + width: 100%; +} +input:not([type]):focus, +input[type=text]:focus, +input[type=search]:focus, +textarea:focus { + border-color: var(--hyde-accent); + outline: none; +} +button, +input[type=button], +input[type=submit] { + padding: .5rem 1rem; + font: inherit; + font-size: .9rem; + font-weight: bold; + color: #fff; + background-color: var(--hyde-accent); + border: 0; + border-radius: 4px; + cursor: pointer; +} +button:hover, +input[type=button]:hover, +input[type=submit]:hover { + filter: brightness(1.1); +} + +/* Search */ +.search-form { + display: flex; + gap: .5rem; + margin-bottom: 1rem; +} +.search-form input { + flex: 1; + min-width: 0; +} + +/* Archives */ +.archives h2 { + margin-top: 2rem; +} +.archive-list h3 { + margin-bottom: .25rem; + font-size: 1rem; +} +.archive-terms { + padding-left: 0; + list-style: none; +} +.archive-terms li { + display: inline-block; + margin: 0 1rem .25rem 0; +} +.archive-terms small { + color: #9a9a9a; +} + +/* + * Dark mode + * + * Hyde has no dark mode; this keeps the colored sidebar and inverts Poole's + * grays for the content column when the reader's system prefers dark. + */ + +@media (prefers-color-scheme: dark) { + body { + color: #c5c5c5; + background-color: #1c1c1e; + } + h1, h2, h3, h4, h5, h6, + strong, + .page-title, + .post-title, + .post-title a, + .comments-head { + color: #ececec; + } + .content a, + .related-posts li a:hover { + color: color-mix(in srgb, var(--hyde-accent) 60%, #fff); + } + code { + color: #e0828a; + background-color: #2a2a2d; + } + pre { + background-color: #2a2a2d; + } + blockquote { + color: #9a9a9a; + border-left-color: #3a3a3d; + } + hr { + border-top-color: #3a3a3d; + border-bottom-color: transparent; + } + table, + td, + th { + border-color: #3a3a3d; + } + tbody tr:nth-child(odd) td, + tbody tr:nth-child(odd) th { + background-color: #242427; + } + .message, + .comments-form .status, + .comments-form .error { + color: #a5a5a5; + background-color: #242427; + } + .comments-form .error { + color: color-mix(in srgb, var(--hyde-accent) 60%, #fff); + } + .pagination { + color: #555; + } + .pagination-item, + .related, + .post-comments { + border-color: #2f2f32; + } + a.pagination-item:hover { + background-color: #242427; + } + .comment, + .post-tags a { + border-color: #3a3a3d; + } + input:not([type]), + input[type=text], + input[type=search], + textarea { + color: #c5c5c5; + background-color: #242427; + border-color: #3a3a3d; + } +} diff --git a/app/src/main/webapp/themes/hyde/theme.xml b/app/src/main/webapp/themes/hyde/theme.xml new file mode 100644 index 000000000..30df631cc --- /dev/null +++ b/app/src/main/webapp/themes/hyde/theme.xml @@ -0,0 +1,19 @@ + + + + + hydeHyde + A port of Hyde, the two-column Jekyll theme built on Poole: a bold colored sidebar with the blog's title and navigation beside the content, with automatic light and dark modes. + Mark Otto (ported for Apache Roller) + + custom.cssHyde custom stylescustom.csstext/csscustom.cssvelocity + + + + + + + + + + diff --git a/app/src/main/webapp/themes/hyde/weblog.vm b/app/src/main/webapp/themes/hyde/weblog.vm new file mode 100644 index 000000000..baf331a51 --- /dev/null +++ b/app/src/main/webapp/themes/hyde/weblog.vm @@ -0,0 +1,33 @@ + + + + #includeTemplate($model.weblog "standard_head") + #if($model.tags && $model.tags.size() > 0) + #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end · $model.weblog.name + #elseif($model.weblogCategory) + $model.weblogCategory.name · $model.weblog.name + #elseif($utils.isNotEmpty($model.weblog.tagline)) + $model.weblog.name · $model.weblog.tagline + #else + $model.weblog.name + #end + + + #includeTemplate($model.weblog "standard_sidebar") + +

+ #set($pager = $model.getWeblogEntriesPager()) + #if($model.tags && $model.tags.size() > 0) +

Tagged #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end

+ #elseif($model.weblogCategory) +

$model.weblogCategory.name

+ #if($utils.isNotEmpty($model.weblogCategory.description)) +

$model.weblogCategory.description

+ #end + #elseif(!$pager.prevLink && $utils.isNotEmpty($model.weblog.about)) +
$model.weblog.about
+ #end + #includeTemplate($model.weblog "_post_list") +
+ + diff --git a/app/src/main/webapp/themes/papermod/archives.vm b/app/src/main/webapp/themes/papermod/archives.vm new file mode 100644 index 000000000..8ae608aad --- /dev/null +++ b/app/src/main/webapp/themes/papermod/archives.vm @@ -0,0 +1,76 @@ + + + + #includeTemplate($model.weblog "standard_head") + Archives | $model.weblog.name + + + #includeTemplate($model.weblog "standard_header") +
+ + ## Roller caps recent entries at 100; count per year and month first so + ## the headers can show totals, then render in publication order. + #set($archiveEntries = $model.weblog.getRecentWeblogEntries("nil", 100)) + #set($yearCounts = {}) + #set($monthCounts = {}) + #foreach($entry in $archiveEntries) + #set($y = $utils.formatDate($entry.pubTime, "yyyy")) + #set($m = $utils.formatDate($entry.pubTime, "yyyy-MM")) + #if($yearCounts.containsKey($y)) + #set($ignore = $yearCounts.put($y, $yearCounts.get($y) + 1)) + #else + #set($ignore = $yearCounts.put($y, 1)) + #end + #if($monthCounts.containsKey($m)) + #set($ignore = $monthCounts.put($m, $monthCounts.get($m) + 1)) + #else + #set($ignore = $monthCounts.put($m, 1)) + #end + #end + #set($currentYear = "") + #set($currentMonth = "") + #foreach($entry in $archiveEntries) + #set($y = $utils.formatDate($entry.pubTime, "yyyy")) + #set($m = $utils.formatDate($entry.pubTime, "yyyy-MM")) + #if($m != $currentMonth && $currentMonth != "") + + + #end + #if($y != $currentYear) + #if($currentYear != "") + + #end +
+

+ $y +  $yearCounts.get($y) +

+ #set($currentYear = $y) + #end + #if($m != $currentMonth) + #set($monthName = $utils.formatDate($entry.pubTime, "MMMM")) +
+

+ $monthName +  $monthCounts.get($m) +

+
+ #set($currentMonth = $m) + #end +
+

$entry.title

+
#includeTemplate($model.weblog "_post_meta")
+ +
+ #end + #if($currentMonth != "") +
+
+
+ #end +
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/papermod/custom.css b/app/src/main/webapp/themes/papermod/custom.css new file mode 100644 index 000000000..26192db36 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/custom.css @@ -0,0 +1 @@ +/* Add your PaperMod overrides here. */ diff --git a/app/src/main/webapp/themes/papermod/entry.vm b/app/src/main/webapp/themes/papermod/entry.vm new file mode 100644 index 000000000..15b54ab1f --- /dev/null +++ b/app/src/main/webapp/themes/papermod/entry.vm @@ -0,0 +1,80 @@ + + + + #includeTemplate($model.weblog "standard_head") + $model.weblogEntry.title | $model.weblog.name + + + #includeTemplate($model.weblog "standard_header") +
+ #set($entry = $model.weblogEntry) +
+
+

$entry.title

+ +
+
+ $entry.displayContent +
+
+ #if($entry.tags && $entry.tags.size() > 0) + + #end + ## PaperMod's "« Prev" is the newer entry and "Next »" the older one. + #set($pager = $model.getWeblogEntriesPager()) + #if($pager.nextLink || $pager.prevLink) + #set($newerEntry = false) + #set($olderEntry = false) + #set($recentEntries = $model.weblog.getRecentWeblogEntries("nil", 100)) + #foreach($recentEntry in $recentEntries) + #if($recentEntry.anchor == $entry.anchor) + #if($foreach.index > 0) + #set($newerEntry = $recentEntries.get($foreach.index - 1)) + #end + #if($foreach.hasNext) + #set($olderEntry = $recentEntries.get($foreach.index + 1)) + #end + #end + #end + + #end +
+ #set($hasComments = $entry.commentCount > 0 || $model.commentForm.preview) + #if($hasComments || $entry.commentsStillAllowed) +
+ #if($hasComments) + #showWeblogEntryComments($entry) + #end + #if($entry.commentsStillAllowed) + #showWeblogEntryCommentForm($entry) + #end +
+ #end +
+
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/papermod/footer.vm b/app/src/main/webapp/themes/papermod/footer.vm new file mode 100644 index 000000000..11782d5d9 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/footer.vm @@ -0,0 +1,26 @@ + + diff --git a/app/src/main/webapp/themes/papermod/head.vm b/app/src/main/webapp/themes/papermod/head.vm new file mode 100644 index 000000000..2a96408e0 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/head.vm @@ -0,0 +1,10 @@ + + + + + + + +#showAutodiscoveryLinks($model.weblog) +#showMetaDescription() +#showAnalyticsTrackingCode($model.weblog) diff --git a/app/src/main/webapp/themes/papermod/header.vm b/app/src/main/webapp/themes/papermod/header.vm new file mode 100644 index 000000000..a05727f28 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/header.vm @@ -0,0 +1,54 @@ +
+ +
diff --git a/app/src/main/webapp/themes/papermod/post-list.vm b/app/src/main/webapp/themes/papermod/post-list.vm new file mode 100644 index 000000000..50edd1666 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/post-list.vm @@ -0,0 +1,45 @@ +## Entry cards for $pager, like PaperMod's list.html. +## Set $firstEntry = true before including to give the first card the larger +## "first-entry" treatment used on page 1 of the home page. +#foreach($day in $pager.entries.keySet()) +#foreach($entry in $pager.entries.get($day)) +#if($firstEntry) +#set($entryClass = "first-entry") +#set($firstEntry = false) +#elseif($model.tags) +#set($entryClass = "post-entry tag-entry") +#else +#set($entryClass = "post-entry") +#end +#if($utils.isNotEmpty($entry.summary)) +#set($entrySummary = $utils.removeHTML($entry.summary)) +#else +#set($entrySummary = $utils.removeHTML("$!entry.text")) +#end +#set($entrySummary = $utils.truncateNicely($entrySummary.trim(), 240, 300, "...")) +
+
+

$entry.title

+
+ #if($utils.isNotEmpty($entrySummary)) +
+

$entrySummary

+
+ #end + + +
+#end +#end +#if($pager.prevLink || $pager.nextLink) + +#end diff --git a/app/src/main/webapp/themes/papermod/post-meta.vm b/app/src/main/webapp/themes/papermod/post-meta.vm new file mode 100644 index 000000000..e7e517e13 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/post-meta.vm @@ -0,0 +1,17 @@ +## Date, reading time, and author for $entry, like PaperMod's post_meta.html. +## Set $metaShowCategory = true to append the category link. +## Reading time follows Hugo: words / 213, rounded up. +#if($utils.isNotEmpty($entry.text)) +#set($metaText = $utils.removeHTML($entry.text)) +#else +#set($metaText = $utils.removeHTML("$!entry.summary")) +#end +#set($metaWords = $metaText.trim().split('\s+').size()) +#set($metaMinutes = ($metaWords + 212) / 213) +#if($metaMinutes < 1)#set($metaMinutes = 1)#end +#if($utils.isNotEmpty($entry.creator.fullName)) +#set($metaAuthor = $entry.creator.fullName) +#else +#set($metaAuthor = $entry.creator.screenName) +#end +$utils.formatDate($entry.pubTime, "MMMM d, yyyy") · $metaMinutes min · $metaAuthor#if($metaShowCategory) · $entry.category.name#end diff --git a/app/src/main/webapp/themes/papermod/preview.png b/app/src/main/webapp/themes/papermod/preview.png new file mode 100644 index 000000000..f7d80284f Binary files /dev/null and b/app/src/main/webapp/themes/papermod/preview.png differ diff --git a/app/src/main/webapp/themes/papermod/search.vm b/app/src/main/webapp/themes/papermod/search.vm new file mode 100644 index 000000000..ad32e77a4 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/search.vm @@ -0,0 +1,59 @@ + + + + #includeTemplate($model.weblog "standard_head") + Search | $model.weblog.name + + + #includeTemplate($model.weblog "standard_header") +
+ + + #if($utils.isNotEmpty($model.term)) + #if($model.hits > 0) + #set($min = $model.offset + 1) + #set($max = $model.offset + $model.limit) + #if($max > $model.hits)#set($max = $model.hits)#end +

#if($model.hits == 1)1 result#{else}$min–$max of $model.hits results#end for “$model.term”

+ #set($pager = $model.getWeblogEntriesPager()) + + #if($pager.prevLink || $pager.nextLink) + + #end + #else +

No entries matched “$model.term”.

+ #end + #end +
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/papermod/tags.vm b/app/src/main/webapp/themes/papermod/tags.vm new file mode 100644 index 000000000..c5c7ac347 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/tags.vm @@ -0,0 +1,19 @@ + + + + #includeTemplate($model.weblog "standard_head") + #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end | $model.weblog.name + + + #includeTemplate($model.weblog "standard_header") +
+ + #set($pager = $model.getWeblogEntriesPager()) + #set($firstEntry = false) + #includeTemplate($model.weblog "_post_list") +
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/papermod/terms.vm b/app/src/main/webapp/themes/papermod/terms.vm new file mode 100644 index 000000000..7941cfa70 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/terms.vm @@ -0,0 +1,23 @@ + + + + #includeTemplate($model.weblog "standard_head") + Tags | $model.weblog.name + + + #includeTemplate($model.weblog "standard_header") +
+ + +
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/papermod/theme.css b/app/src/main/webapp/themes/papermod/theme.css new file mode 100644 index 000000000..64c124e5f --- /dev/null +++ b/app/src/main/webapp/themes/papermod/theme.css @@ -0,0 +1,1179 @@ +/* + PaperMod v8+ + License: MIT https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE + Copyright (c) 2020 nanxiaobei and adityatelange + Copyright (c) 2021-2026 adityatelange + + Ported to Apache Roller. The rules below follow upstream's + assets/css/core and assets/css/common files in Hugo's bundle order; + Hugo-only features (TOC, breadcrumbs, share buttons, cover images, + language menu, profile mode) are omitted. Roller-specific rules for + comments and forms are at the end, before the media queries. +*/ + + +/* theme-vars.css */ + +:root { + --gap: 24px; + --content-gap: 20px; + --nav-width: 1024px; + --main-width: 720px; + --header-height: 60px; + --footer-height: 60px; + --radius: 8px; + --theme: rgb(255, 255, 255); + --entry: rgb(255, 255, 255); + --primary: rgb(30, 30, 30); + --secondary: rgb(108, 108, 108); + --tertiary: rgb(214, 214, 214); + --content: rgb(31, 31, 31); + --code-block-bg: rgb(28, 29, 33); + --code-bg: rgb(245, 245, 245); + --border: rgb(238, 238, 238); + color-scheme: light; +} + +:root[data-theme="dark"] { + --theme: rgb(29, 30, 32); + --entry: rgb(46, 46, 51); + --primary: rgb(218, 218, 219); + --secondary: rgb(155, 156, 157); + --tertiary: rgb(65, 66, 68); + --content: rgb(196, 196, 197); + --code-block-bg: rgb(46, 46, 51); + --code-bg: rgb(55, 56, 62); + --border: rgb(51, 51, 51); + color-scheme: dark; +} + +.list { + background: var(--code-bg); +} + +[data-theme="dark"] .list { + background: var(--theme); +} + +/* "auto" before the theme script runs (or without JavaScript): follow the OS */ +@media (prefers-color-scheme: dark) { + :root[data-theme="auto"] { + --theme: rgb(29, 30, 32); + --entry: rgb(46, 46, 51); + --primary: rgb(218, 218, 219); + --secondary: rgb(155, 156, 157); + --tertiary: rgb(65, 66, 68); + --content: rgb(196, 196, 197); + --code-block-bg: rgb(46, 46, 51); + --code-bg: rgb(55, 56, 62); + --border: rgb(51, 51, 51); + color-scheme: dark; + } + + [data-theme="auto"] .list { + background: var(--theme); + } +} + +[data-theme="auto"] #theme-toggle, +[data-theme="auto"] .top-link { + display: none; +} + + +/* reset.css */ + +*, +::after, +::before { + box-sizing: border-box; +} + +html { + -webkit-tap-highlight-color: transparent; + overflow-y: scroll; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +a, +button, +body, +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--primary); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 18px; + line-height: 1.6; + word-break: break-word; + background: var(--theme); +} + +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +table { + display: block; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 1.2; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin-top: 0; + margin-bottom: 0; +} + +ul { + padding: 0; +} + +a { + text-decoration: none; +} + +body, +figure, +ul { + margin: 0; +} + +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + overflow-x: auto; + word-break: keep-all; +} + +button, +input, +textarea { + padding: 0; + font: inherit; + background: 0 0; + border: 0; +} + +input, +textarea { + outline: 0; +} + +button, +input[type=button], +input[type=submit] { + cursor: pointer; +} + +input:-webkit-autofill, +textarea:-webkit-autofill { + box-shadow: 0 0 0 50px var(--theme) inset; +} + +img { + display: block; + max-width: 100%; +} + + +/* archive.css */ + +.archive-posts { + width: 100%; + font-size: 16px; +} + +.archive-year { + margin-top: 40px; +} + +.archive-year:not(:last-of-type) { + border-bottom: 2px solid var(--border); +} + +.archive-month { + display: flex; + align-items: flex-start; + padding: 10px 0; +} + +.archive-month-header { + margin: 25px 0; + width: 200px; +} + +.archive-month:not(:last-of-type) { + border-bottom: 1px solid var(--border); +} + +.archive-entry { + position: relative; + padding: 5px; + margin: 10px 0; +} + +.archive-entry-title { + margin: 5px 0; + font-weight: 400; +} + +.archive-count, +.archive-meta { + color: var(--secondary); + font-size: 14px; +} + + +/* footer.css */ + +.footer, +.top-link { + font-size: 12px; + color: var(--secondary); +} + +.footer { + max-width: calc(var(--main-width) + var(--gap) * 2); + margin: auto; + padding: calc((var(--footer-height) - var(--gap)) / 2) var(--gap); + text-align: center; + line-height: 24px; +} + +.footer span { + margin-inline-start: 1px; + margin-inline-end: 1px; +} + +.footer span:last-child { + white-space: nowrap; +} + +.footer a { + color: inherit; + text-underline-offset: 0.25rem; + text-decoration: underline; +} + +.top-link { + position: fixed; + bottom: 4rem; + right: 2rem; + z-index: 99; + background: var(--tertiary); + width: 2.5rem; + height: 2.5rem; + padding: 10px; + border-radius: 64px; + transition: visibility .3s, opacity .3s cubic-bezier(0.4, 0, 1, 1); +} + +.hidden { + visibility: hidden; + opacity: 0; +} + +.top-link, +.top-link svg { + filter: drop-shadow(0px 0px 0px var(--theme)); +} + +.footer a:hover, +.top-link:hover { + color: var(--primary); +} + + +/* header.css */ + +.header-nav { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: calc(var(--nav-width) + var(--gap) * 2); + margin: auto; + line-height: var(--header-height); + padding: 0 var(--gap); + column-gap: var(--gap); +} + +.header-nav a { + display: block; +} + +.logo, +.menu { + display: flex; +} + +.logo { + align-items: center; + column-gap: 0.55rem; + flex-wrap: wrap; +} + +.logo a { + font-size: 24px; + font-weight: 700; + display: flex; + align-items: center; + column-gap: 0.55rem; +} + +.theme-toggle { + padding: 0 0.4rem; +} + +[data-theme="dark"] .moon { + display: none; +} + +[data-theme="light"] .sun { + display: none; +} + +.logo-switches { + display: inline-flex; + gap: 0.4rem; + align-items: inherit; + min-height: stretch; + flex-wrap: inherit; +} + +.logo-switches>* { + min-height: inherit; + align-items: center; + display: inline-flex; +} + +.menu { + list-style: none; + word-break: keep-all; + overflow-x: auto; + white-space: nowrap; + column-gap: var(--gap); +} + +.menu a { + font-size: 16px; +} + +.menu .active { + font-weight: 500; + text-decoration: underline; + text-underline-offset: 0.3rem; + text-decoration-thickness: 2px; +} + + +/* main.css */ + +.main { + position: relative; + min-height: calc(100vh - var(--header-height) - var(--footer-height)); + max-width: calc(var(--main-width) + var(--gap) * 2); + margin: auto; + padding: var(--gap); +} + +.page-header h1 { + font-size: 40px; +} + +.pagination { + display: flex; +} + +.pagination a { + color: var(--theme); + font-size: 13px; + line-height: 36px; + background: var(--primary); + border-radius: calc(36px / 2); + padding: 0 16px; +} + +.pagination .next { + margin-inline-start: auto; +} + + +code { + direction: ltr; +} + +div.highlight, +pre { + position: relative; +} + +/* md-content.css */ + +.md-content h3, +.md-content h4, +.md-content h5, +.md-content h6 { + margin: 24px 0 16px; +} + +.md-content h1 { + margin: 40px auto 32px; + font-size: 40px; +} + +.md-content h2 { + margin: 32px auto 24px; + font-size: 32px; +} + +.md-content h3 { + font-size: 24px; +} + +.md-content h4 { + font-size: 16px; +} + +.md-content h5 { + font-size: 14px; +} + +.md-content h6 { + font-size: 12px; +} + +.md-content a:not(.anchor) { + text-underline-offset: 0.3rem; + text-decoration: underline; +} + +.md-content del { + text-decoration: line-through; +} + +.md-content dl:not(:last-child), +.md-content ol:not(:last-child), +.md-content p:not(:last-child), +.md-content figure:not(:last-child), +.md-content ul:not(:last-child) { + margin-bottom: var(--content-gap); +} + +.md-content ol, +.md-content ul { + padding-inline-start: 1.25rem; +} + +.md-content li { + margin-top: 0.3rem; +} + +.md-content li p { + margin-bottom: 0; +} + +.md-content dl { + display: flex; + flex-wrap: wrap; + margin: 0; +} + +.md-content dt { + width: 25%; + font-weight: 700; +} + +.md-content dd { + width: 75%; + margin-inline-start: 0; + padding-inline-start: 10px; +} + +.md-content dd~dd, +.md-content dt~dt { + margin-top: 10px; +} + +.md-content table { + margin-bottom: var(--content-gap); +} + +.md-content table th, +.md-content table:not(.highlight table, .gist .highlight) td { + min-width: 80px; + padding: 6px 13px; + line-height: 1.5; + border: 1px solid var(--border); +} + +.md-content table th { + text-align: start; +} + +.md-content table td code:only-child { + margin: auto 0; +} + +.md-content .highlight table { + border-radius: var(--radius); +} + +.md-content .highlight:not(table) { + margin-bottom: var(--content-gap); + background: var(--code-block-bg) !important; + border-radius: var(--radius); + direction: ltr; +} + +.md-content .highlight pre { + margin: 0; +} + +.post-content code { + padding: 0.2rem 0.3rem; + font-size: 0.78em; + line-height: 1.5; + background: var(--code-bg); + border-radius: 0.2rem; +} + +.md-content pre code { + display: grid; + margin: auto 0; + padding: 10px; + color: rgb(213, 213, 214); + background: var(--code-block-bg) !important; + border-radius: var(--radius); + overflow-x: auto; + word-break: break-all; +} + +.md-content blockquote { + margin: 1rem 0; + padding-inline-start: 1rem; + border-inline-start: 0.3rem solid var(--content); +} + +.md-content hr { + margin: 30px 0; + height: 2px; + background: var(--tertiary); + border: 0; +} + +.md-content iframe { + max-width: 100%; +} + +.md-content img { + border-radius: var(--radius); + margin: 1rem 0; +} + +.md-content img[src*="#center"] { + margin: 1rem auto; +} + +.md-content figure.align-center { + text-align: center; +} + +.md-content figure>figcaption { + color: var(--primary); + font-size: 16px; + font-weight: bold; + margin: 8px 0 16px; +} + +.md-content figure>figcaption>p { + color: var(--secondary); + font-size: 14px; + font-weight: normal; +} + +.md-content h1:hover .anchor, +.md-content h2:hover .anchor, +.md-content h3:hover .anchor, +.md-content h4:hover .anchor, +.md-content h5:hover .anchor, +.md-content h6:hover .anchor { + display: inline-flex; + color: var(--secondary); + margin-inline-start: 0.5em; + font-weight: 500; + user-select: none; +} + +.anchor:hover { + color: var(--content) !important; +} + +.md-content img.in-text { + display: inline; + margin: auto; +} + +mark { + border-radius: 2px; + padding: 0 2px; +} + +audio { + display: block; + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + height: 2.5rem; + margin-bottom: var(--content-gap); +} + +audio::-webkit-media-controls-enclosure { + border-radius: 0; +} + +video { + border: 1px solid var(--code-bg); + border-radius: var(--radius); + max-width: 100%; +} + + +/* post-entry.css */ + +.first-entry { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + min-height: 320px; + margin: var(--gap) 0 calc(var(--gap) * 2) 0; +} + +.first-entry .entry-header { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.first-entry .entry-header h1 { + font-size: 34px; + line-height: 1.3; +} + +.first-entry .entry-header h2 { + font-size: 40px; +} + +.first-entry .entry-content { + margin: 14px 0; + font-size: 16px; + -webkit-line-clamp: 3; +} + +.first-entry .entry-footer { + font-size: 14px; +} + +.home-info .entry-content { + --content-gap: 0.5rem; + -webkit-line-clamp: unset; + margin: 0; +} + +.post-entry { + position: relative; + margin-bottom: var(--gap); + padding: var(--gap); + background: var(--entry); + border-radius: var(--radius); + transition: transform 0.25s ease; + border: 1px solid var(--border); +} + +.post-entry:hover, +.post-entry:focus-within { + transform: translateY(-2px); + border-color: var(--tertiary); +} + +.entry-header h2 { + font-size: 24px; + line-height: 1.3; +} + +.entry-content { + margin: 8px 0; + color: var(--secondary); + font-size: 14px; + line-height: 1.6; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.home-info .entry-content p { + margin-block-start: 1em; + margin-block-end: 1em; +} + +.entry-footer { + color: var(--secondary); + font-size: 13px; +} + +.entry-link { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + border-radius: var(--radius); +} + +.entry-hint { + color: var(--secondary); +} + +.entry-hint-parent { + display: flex; + justify-content: space-between; +} + +/* post-single.css */ + +.page-header, +.post-header { + margin: 24px auto var(--content-gap) auto; +} + +.post-title { + font-size: 40px; +} + +.post-description { + margin-top: 10px; +} + +.post-meta { + margin-top: 5px; +} + +.post-meta { + color: var(--secondary); + font-size: 14px; +} + +.post-meta a { + text-underline-offset: 0.3rem; + text-decoration: underline; +} + +.post-meta a { + color: var(--secondary); + text-decoration-style: dotted; +} + +.post-content { + color: var(--content); + margin: 30px 0; +} + +.post-footer { + margin-top: var(--content-gap); +} + +.post-footer>* { + margin-bottom: 10px; +} + +.post-tags { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.post-tags li { + display: inline-block; +} + +.post-tags a, +.paginav { + border-radius: var(--radius); + background: var(--code-bg); + border: 1px solid var(--border); +} + +.post-tags a { + display: block; + padding: 0 14px; + color: var(--secondary); + font-size: 14px; + line-height: 34px; + background: var(--code-bg); +} + +.post-tags a:hover, +.paginav a:hover { + background: var(--border); +} + +.paginav { + display: flex; + line-height: 1.2; +} + +.paginav .title { + letter-spacing: 1px; + text-transform: uppercase; + font-size: 0.8rem; + color: var(--secondary); +} + +.paginav a { + width: 50%; + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.8rem; + border-radius: var(--radius); +} + +.paginav span:hover:not(.title) { + text-underline-offset: 0.2rem; + text-decoration: underline; +} + +.paginav .next { + margin-inline-start: auto; + text-align: right; +} + +[dir="rtl"] .paginav .next { + text-align: left; +} + +h1>a>svg { + display: inline; +} + + +/* search.css */ + +.searchbox input { + padding: 4px 10px; + width: 100%; + color: var(--primary); + font-weight: bold; + border: 2px solid var(--tertiary); + border-radius: var(--radius); +} + +.searchResults li { + list-style: none; + border-radius: var(--radius); + padding: 10px 15px; + position: relative; + font-weight: 500; + display: flex; + align-items: center; + justify-content: space-between; + background: var(--entry); + transition: transform .25s ease; + border: 1px solid var(--border); +} + +.searchResults { + margin: var(--content-gap) 0; + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; +} + +.searchResults li:hover, +.searchResults li:focus-within { + transform: translateY(-2px); + border-color: var(--tertiary); +} + +.searchResults li .entry-link:focus { + outline: 2px solid var(--secondary); + outline-offset: -2px; +} + + +/* terms.css */ + +.terms-tags { + display: flex; + flex-wrap: wrap; + gap: 1em; + margin-top: var(--content-gap); +} + +.terms-tags li { + display: inline-block; + font-weight: 500; +} + +.terms-tags a { + display: block; + padding: 4px 10px; + background: var(--tertiary); + border-radius: var(--radius); + transition: transform 0.1s; +} + + +/* roller.css: Apache Roller markup styled in PaperMod's visual language */ + +.page-footer { + margin-top: var(--gap); +} + +.post-meta .entry-edit::before { + content: "|"; + margin: 0 0.4em; +} + +.searchbox { + margin-top: var(--content-gap); +} + +.search-summary { + margin: var(--content-gap) 0 0; +} + +.empty-state { + margin: var(--content-gap) 0; + color: var(--secondary); +} + +.searchResults .entry-link { + border-radius: var(--radius); +} + +.searchResults svg { + flex-shrink: 0; +} + +.post-comments { + margin-top: calc(var(--gap) * 2); + padding-top: var(--gap); + border-top: 1px solid var(--border); +} + +.comments-head { + margin-bottom: var(--content-gap); + color: var(--primary); + font-size: 24px; + font-weight: 700; + line-height: 1.3; +} + +.comments>br { + display: none; +} + +.comments { + margin-bottom: calc(var(--gap) * 2); +} + +.comment { + margin-bottom: var(--gap); + padding: var(--gap); + color: var(--content); + font-size: 16px; + background: var(--entry); + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.comment p:not(:last-child) { + margin-bottom: 10px; +} + +.comment-details { + margin-top: 10px; + color: var(--secondary); + font-size: 13px; +} + +.comment-details a, +.comment-details b { + color: var(--secondary); + font-weight: 500; +} + +.comment-details a.entrypermalink { + margin-inline-start: 0.3em; +} + +.comments-form ul { + display: flex; + flex-direction: column; + gap: 14px; + list-style: none; +} + +.comments-form label.desc { + display: block; + margin-bottom: 4px; + color: var(--secondary); + font-size: 14px; +} + +.comments-form label.choice { + margin-inline-start: 0.3em; + color: var(--secondary); + font-size: 14px; +} + +.comments-form input.checkbox { + accent-color: var(--primary); +} + +.comments-form input.text, +.comments-form textarea, +#commentAuthenticator input { + padding: 4px 10px; + width: 100%; + color: var(--primary); + background: var(--theme); + border: 2px solid var(--tertiary); + border-radius: var(--radius); +} + +.comments-form input.text:focus, +.comments-form textarea:focus, +#commentAuthenticator input:focus { + border-color: var(--secondary); +} + +.comments-form textarea { + min-height: 160px; + resize: vertical; +} + +#commentAuthenticator input { + width: auto; +} + +.comments-form .info, +#commentAuthenticator { + color: var(--secondary); + font-size: 13px; +} + +.comments-form input.button { + color: var(--theme); + font-size: 13px; + line-height: 36px; + background: var(--primary); + border-radius: calc(36px / 2); + padding: 0 16px; + margin-inline-end: 8px; +} + +.comments-form .status, +.comments-form .error { + display: block; + margin-bottom: var(--content-gap); + padding: 10px 15px; + color: var(--content); + font-size: 14px; + background: var(--code-bg); + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.comments-form .error { + border-color: var(--secondary); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + + +/* zmedia.css */ + +@media screen and (max-width: 768px) { + /* theme-vars */ + :root { + --gap: 14px; + } + + /* post-entry */ + .first-entry { + min-height: 260px; + } + + /* archive */ + .archive-month { + flex-direction: column; + } + + .archive-year { + margin-top: 20px; + } + + /* footer */ + .footer { + padding: calc((var(--footer-height) - var(--gap) - 10px) / 2) var(--gap); + } +} + +/* footer */ +@media screen and (max-width: 900px) { + .list .top-link { + transform: translateY(-5rem); + } +} + +@media (prefers-reduced-motion) { + /* terms; post-entry; search */ + .terms-tags a:active, + .post-entry:active, + .top-link, + .searchResults .focus, + .searchResults li:active { + transform: none; + } +} + diff --git a/app/src/main/webapp/themes/papermod/theme.js b/app/src/main/webapp/themes/papermod/theme.js new file mode 100644 index 000000000..8f6ea23a4 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/theme.js @@ -0,0 +1,83 @@ +/* + PaperMod v8+ + License: MIT https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE + Copyright (c) 2020 nanxiaobei and adityatelange + Copyright (c) 2021-2026 adityatelange + + Ported to Apache Roller from layouts/_partials/head.html and footer.html. + Loaded synchronously in so the theme is applied before first paint. +*/ +(function () { + var html = document.documentElement; + var darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); + + function storedTheme() { + try { + return localStorage.getItem('pref-theme'); + } catch (e) { + return null; + } + } + + function applyTheme() { + var pref = storedTheme(); + if (pref === 'dark' || pref === 'light') { + html.dataset.theme = pref; + } else { + html.dataset.theme = darkQuery.matches ? 'dark' : 'light'; + } + } + + applyTheme(); + + // "auto": keep following the OS setting until the reader picks a theme + darkQuery.addEventListener('change', function () { + if (!storedTheme()) { + applyTheme(); + } + }); + + document.addEventListener('DOMContentLoaded', function () { + var toggle = document.getElementById('theme-toggle'); + if (toggle) { + toggle.addEventListener('click', function () { + var next = html.dataset.theme === 'dark' ? 'light' : 'dark'; + html.dataset.theme = next; + try { + localStorage.setItem('pref-theme', next); + } catch (e) { + // storage unavailable; the choice lasts for this page only + } + }); + } + + var menu = document.getElementById('menu'); + if (menu) { + var scrollPosition = localStorage.getItem('menu-scroll-position'); + if (scrollPosition) { + menu.scrollLeft = parseInt(scrollPosition, 10); + } + menu.addEventListener('scroll', function () { + localStorage.setItem('menu-scroll-position', menu.scrollLeft); + }); + } + + var topLink = document.getElementById('top-link'); + if (topLink) { + window.addEventListener('scroll', function () { + var scrollThreshold = window.innerHeight; + if (document.body.scrollTop > scrollThreshold || document.documentElement.scrollTop > scrollThreshold) { + topLink.classList.remove('hidden'); + } else { + topLink.classList.add('hidden'); + } + }); + topLink.addEventListener('click', function (e) { + e.preventDefault(); + var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' }); + history.replaceState(null, '', ' '); + }); + } + }); +})(); diff --git a/app/src/main/webapp/themes/papermod/theme.xml b/app/src/main/webapp/themes/papermod/theme.xml new file mode 100644 index 000000000..a7703ca95 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/theme.xml @@ -0,0 +1,21 @@ + + + + papermodPaperMod + A port of the PaperMod theme for Hugo: a fast, clean, card-based layout with reading times and automatic light and dark modes. + Aditya Telange (ported for Apache Roller) + + custom.cssPaperMod custom stylescustom.csstext/csscustom.cssvelocity + + + + + + + + + + + + + diff --git a/app/src/main/webapp/themes/papermod/weblog.vm b/app/src/main/webapp/themes/papermod/weblog.vm new file mode 100644 index 000000000..e88481268 --- /dev/null +++ b/app/src/main/webapp/themes/papermod/weblog.vm @@ -0,0 +1,44 @@ + + + + #includeTemplate($model.weblog "standard_head") + #if($model.weblogCategory) + $model.weblogCategory.name | $model.weblog.name + #else + $model.weblog.name#if($utils.isNotEmpty($model.weblog.tagline)) | $model.weblog.tagline#end + #end + + + #includeTemplate($model.weblog "standard_header") +
+ #set($pager = $model.getWeblogEntriesPager()) + ## Page 1 of the latest-entries view is the home page; category and date + ## archives (which have next/previous collection links) are not. + #set($pageParam = $model.getRequestParameter("page")) + #set($isHome = !$model.weblogCategory && !$pager.prevLink && !$pager.prevCollectionLink && !$pager.nextCollectionLink && (!$pageParam || $pageParam == "0")) + #set($firstEntry = false) + #if($model.weblogCategory) + + #elseif($isHome && $utils.isNotEmpty($model.weblog.about)) +
+
+

$model.weblog.name

+
+
+ #set($about = $model.weblog.about.trim()) + #if($about.startsWith("<"))$about#{else}

$about

#end +
+
+ #elseif($isHome) + #set($firstEntry = true) + #end + #includeTemplate($model.weblog "_post_list") +
+ #includeTemplate($model.weblog "standard_footer") + + diff --git a/app/src/main/webapp/themes/terminal/archives.vm b/app/src/main/webapp/themes/terminal/archives.vm new file mode 100644 index 000000000..6fcde0247 --- /dev/null +++ b/app/src/main/webapp/themes/terminal/archives.vm @@ -0,0 +1,60 @@ + + + + #includeTemplate($model.weblog "standard_head") + Archives :: $model.weblog.name + + +
+ #includeTemplate($model.weblog "standard_header") +
+
+

Archives

+ + + #set($archiveEntries = $model.weblog.getRecentWeblogEntries("nil", 100)) + #set($currentYear = "") + #foreach($archiveEntry in $archiveEntries) + #set($entryYear = $utils.formatDate($archiveEntry.pubTime, "yyyy")) + #if($entryYear != $currentYear) + #if($currentYear != "") + + #end + #set($currentYear = $entryYear) +

$entryYear

+
    + #end +
  • + + $archiveEntry.title +
  • + #end + #if($currentYear != "") +
+ #end + + #set($archiveCategories = $model.weblog.weblogCategories) + #if($archiveCategories.size() > 0) +

Categories

+ + #end + + #set($archiveTags = $model.weblog.getPopularTags(-1, 50)) + #if($archiveTags.size() > 0) +

Tags

+ + #end +
+
+ #includeTemplate($model.weblog "standard_footer") +
+ + diff --git a/app/src/main/webapp/themes/terminal/custom.css b/app/src/main/webapp/themes/terminal/custom.css new file mode 100644 index 000000000..3e1ee119c --- /dev/null +++ b/app/src/main/webapp/themes/terminal/custom.css @@ -0,0 +1,3 @@ +/* Add weblog-specific Terminal overrides here. */ +/* To change the color scheme, set --background, --foreground, and --accent on + :root (see https://panr.github.io/terminal-css/ for ready-made schemes). */ diff --git a/app/src/main/webapp/themes/terminal/entry.vm b/app/src/main/webapp/themes/terminal/entry.vm new file mode 100644 index 000000000..ab11f01ab --- /dev/null +++ b/app/src/main/webapp/themes/terminal/entry.vm @@ -0,0 +1,73 @@ + + + + #includeTemplate($model.weblog "standard_head") + $model.weblogEntry.title :: $model.weblog.name + + +
+ #includeTemplate($model.weblog "standard_header") +
+ #set($entry = $model.weblogEntry) +
+

+ $entry.title +

+ + + #if($entry.tags && $entry.tags.size() > 0) + + #end + +
+ $entry.displayContent +
+ + #set($pager = $model.getWeblogEntriesPager()) + #if($pager.prevLink || $pager.nextLink) + + #end + +
+ #showWeblogEntryComments($entry) + #showWeblogEntryCommentForm($entry) +
+
+
+ #includeTemplate($model.weblog "standard_footer") +
+ + diff --git a/app/src/main/webapp/themes/terminal/footer.vm b/app/src/main/webapp/themes/terminal/footer.vm new file mode 100644 index 000000000..20cf744d8 --- /dev/null +++ b/app/src/main/webapp/themes/terminal/footer.vm @@ -0,0 +1,13 @@ + diff --git a/app/src/main/webapp/themes/terminal/head.vm b/app/src/main/webapp/themes/terminal/head.vm new file mode 100644 index 000000000..1a8d6bb8d --- /dev/null +++ b/app/src/main/webapp/themes/terminal/head.vm @@ -0,0 +1,10 @@ + + + + + + + +#showAutodiscoveryLinks($model.weblog) +#showMetaDescription() +#showAnalyticsTrackingCode($model.weblog) diff --git a/app/src/main/webapp/themes/terminal/header.vm b/app/src/main/webapp/themes/terminal/header.vm new file mode 100644 index 000000000..85dcefd66 --- /dev/null +++ b/app/src/main/webapp/themes/terminal/header.vm @@ -0,0 +1,36 @@ +## Terminal's header.html, logo.html, menu.html, and mobile-menu.html. +## The mobile menu uses
instead of Terminal's menu.js. +#set($navPages = []) +#foreach($navPage in $model.weblog.templates) + #if($navPage.navbar && !$navPage.hidden && $navPage.link) + #set($added = $navPages.add($navPage)) + #end +#end +
+
+ + +
+ +
diff --git a/app/src/main/webapp/themes/terminal/post-list.vm b/app/src/main/webapp/themes/terminal/post-list.vm new file mode 100644 index 000000000..a6af5e3de --- /dev/null +++ b/app/src/main/webapp/themes/terminal/post-list.vm @@ -0,0 +1,60 @@ +## Terminal's list.html and pagination.html, shared by the weblog, tag, and +## search templates. Expects $pager to be set by the including template. +#set($listEntries = []) +#foreach($day in $pager.entries.keySet()) + #foreach($entry in $pager.entries.get($day)) + #set($added = $listEntries.add($entry)) + #end +#end +
+ #foreach($entry in $listEntries) +
+

+ $entry.title +

+ + #if($entry.tags && $entry.tags.size() > 0) + + #end +
+ #if($utils.isNotEmpty($entry.summary)) + $entry.transformedSummary + #else +

$utils.truncateNicely($utils.removeHTML($entry.transformedText), 240, 300, "…")

+ #end +
+
+ [Read more →] +
+
+ #end + #if($listEntries.size() == 0) +

No posts here yet.

+ #end + #if($pager.prevLink || $pager.nextLink) + + #end +
diff --git a/app/src/main/webapp/themes/terminal/preview.png b/app/src/main/webapp/themes/terminal/preview.png new file mode 100644 index 000000000..054d2f611 Binary files /dev/null and b/app/src/main/webapp/themes/terminal/preview.png differ diff --git a/app/src/main/webapp/themes/terminal/search.vm b/app/src/main/webapp/themes/terminal/search.vm new file mode 100644 index 000000000..cfe4608df --- /dev/null +++ b/app/src/main/webapp/themes/terminal/search.vm @@ -0,0 +1,34 @@ + + + + #includeTemplate($model.weblog "standard_head") + Search: $model.term :: $model.weblog.name + + +
+ #includeTemplate($model.weblog "standard_header") +
+

Search

+ + #if($utils.isNotEmpty($model.errorMessage)) + + #elseif($model.hits > 0) + #set($firstHit = $model.offset + 1) + #set($lastHit = $model.offset + $model.limit) + #if($lastHit > $model.hits)#set($lastHit = $model.hits)#end + + #set($pager = $model.getWeblogEntriesPager()) + #includeTemplate($model.weblog "_post_list") + #elseif($utils.isNotEmpty($model.term)) + + #end +
+ #includeTemplate($model.weblog "standard_footer") +
+ + diff --git a/app/src/main/webapp/themes/terminal/tags.vm b/app/src/main/webapp/themes/terminal/tags.vm new file mode 100644 index 000000000..2a9eadda5 --- /dev/null +++ b/app/src/main/webapp/themes/terminal/tags.vm @@ -0,0 +1,18 @@ + + + + #includeTemplate($model.weblog "standard_head") + #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end :: $model.weblog.name + + +
+ #includeTemplate($model.weblog "standard_header") +
+

Posts for: #foreach($tagName in $model.tags)#$tagName#if($foreach.hasNext), #end#end

+ #set($pager = $model.getWeblogEntriesPager()) + #includeTemplate($model.weblog "_post_list") +
+ #includeTemplate($model.weblog "standard_footer") +
+ + diff --git a/app/src/main/webapp/themes/terminal/theme.css b/app/src/main/webapp/themes/terminal/theme.css new file mode 100644 index 000000000..52a1adf9a --- /dev/null +++ b/app/src/main/webapp/themes/terminal/theme.css @@ -0,0 +1,1462 @@ +/*! + * Terminal for Apache Roller + * + * Ported from Terminal (https://github.com/panr/hugo-theme-terminal, commit 8ef994c): + * assets/css/main.css, header.css, menu.css, footer.css, post.css, + * pagination.css, buttons.css, terms.css, and code.css, with the default + * scheme from terminal.css. Fira Code is not bundled; the font stack uses it + * when the reader has it installed and falls back to the system monospace font. + * + * The MIT License (MIT) + * + * Copyright (c) 2024 panr + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +/* ---- main.css ---- */ + +:root { + --background: #1a170f; + --foreground: #eceae5; + --accent: #eec35e; + --font-size: 1rem; + --line-height: 1.54em; + --radius: 0; +} + +html { + box-sizing: border-box; +} + +*, +*::before, +*::after { + box-sizing: inherit; + word-break: break-word; +} + +body { + margin: 0; + padding: 0; + font-family: "Fira Code", Monaco, Consolas, "Ubuntu Mono", monospace; + font-size: var(--font-size); + line-height: var(--line-height); + background-color: var(--background); + color: var(--foreground); + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; + -webkit-overflow-scrolling: touch; + text-size-adjust: 100%; +} + +h1 { + font-size: calc(var(--font-size) * 1.45); + letter-spacing: 0; +} + +h2 { + font-size: calc(var(--font-size) * 1.35); + letter-spacing: 0; +} + +h3 { + font-size: calc(var(--font-size) * 1.15); + letter-spacing: 0; +} + +h4, +h5, +h6 { + font-size: calc(var(--font-size) * 1); + letter-spacing: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p, +ul, +ol, +img, +figure, +video, +table { + margin: 25px 0; +} + +a { + color: var(--accent); +} + +button { + position: relative; + font: inherit; + font-weight: bold; + text-decoration: none; + text-align: center; + background: transparent; + color: var(--accent); + padding: 5px 18px; + border: 4px solid var(--accent); + border-radius: var(--radius); + transition: background 0.15s linear; + appearance: none; + cursor: pointer; + outline: none; +} + +button:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); +} + +button:focus-visible, +a:focus-visible { + outline: 1px solid var(--accent); + outline-offset: 2px; +} + +fieldset { + display: inline-block; + border: 2px solid var(--foreground); + border-radius: calc(var(--radius) * 1.6); + padding: 10px; +} + +fieldset *:first-child { + margin-top: 0; +} + +fieldset input, +fieldset select, +fieldset textarea, +fieldset label, +fieldset button { + margin-top: calc(var(--line-height) * 0.5); + width: 100%; +} + +label { + display: inline-block; +} + +label input { + margin-top: 0; +} + +input, +textarea, +select { + background: transparent; + color: var(--foreground); + border: 1px solid var(--foreground); + border-radius: var(--radius); + padding: 10px; + font: inherit; + appearance: none; +} + +input[type="checkbox"] { + width: auto; +} + +input:focus-visible, +input:active, +textarea:focus-visible, +textarea:active, +select:focus-visible, +select:active { + border-color: var(--accent); + outline: 1px solid var(--accent); + outline-offset: 2px; +} + +input:active, +textarea:active, +select:active { + box-shadow: none; +} + +select { + background-image: linear-gradient( + 45deg, + transparent 50%, + var(--foreground) 50% + ), + linear-gradient(135deg, var(--foreground) 50%, transparent 50%); + background-position: calc(100% - 20px), calc(100% - 1em); + background-size: + 5px 5px, + 5px 5px; + background-repeat: no-repeat; + padding-right: 40px; +} + +select option { + background: var(--background); +} + +input[type="checkbox"] { + vertical-align: middle; + padding: 10px; + box-shadow: inset 0 0 0 3px var(--background); +} + +input[type="checkbox"]:checked { + background: var(--accent); +} + +img { + display: block; + max-width: 100%; + border: 8px solid var(--accent); + border-radius: var(--radius); + padding: 8px; + overflow: hidden; +} + +img.left { + margin-right: auto; +} + +img.center { + margin-left: auto; + margin-right: auto; +} + +img.right { + margin-left: auto; +} + +figure { + width: fit-content; +} + +figure.left { + margin-right: auto; +} + +figure.center { + margin-left: auto; + margin-right: auto; +} + +figure.right { + margin-left: auto; +} + +figure img, +figure video { + margin-bottom: 0; +} + +figure figcaption { + background: var(--accent); + color: var(--background); + text-align: center; + font-size: var(--font-size); + font-weight: normal; + margin-top: -8px; + padding: 0 8px; + border-radius: 0 0 var(--radius) var(--radius); +} + +figure figcaption p:first-child { + margin-top: 0; +} + +ul, +ol { + margin-left: 4ch; + padding: 0; +} + +ul ul, +ul ol, +ol ul, +ol ol { + margin-top: 0; +} + +li::marker { + color: var(--accent); +} + +ul li, +ol li { + position: relative; +} + +code, +kbd { + font-family: + "Fira Code", + Monaco, + Consolas, + "Ubuntu Mono", + monospace !important; + font-feature-settings: normal; + background: color-mix(in srgb, var(--foreground) 5%, transparent); + border: 1px solid var(--code-border); + padding: 1px 6px; + margin: 0 2px; + font-size: calc(var(--font-size) * 0.95); +} + +kbd { + border-top: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); + border-left: 1px solid var(--accent); + border-right: 1px solid var(--accent); + border-bottom: 4px solid var(--accent); + border-radius: 4px; +} + +code code { + background: transparent; + padding: 0; + margin: 0; +} + +pre { + tab-size: 4; + background: color-mix(in srgb, var(--foreground) 5%, transparent) !important; + color: var(--foreground); + padding: 20px 10px; + font-size: calc(var(--font-size) * 0.95) !important; + overflow: auto; + border-radius: var(--radius); + border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); +} + +pre code { + background: none !important; + margin: 0; + padding: 0; + border: none; +} + +sup { + line-height: 0; +} + +abbr { + position: relative; + text-decoration-style: wavy; + text-decoration-color: var(--accent); + cursor: help; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.25em; +} + +mark { + background: color-mix(in srgb, var(--accent) 45%, transparent); + color: var(--foreground); +} + +blockquote { + position: relative; + border-top: 1px solid var(--accent); + border-bottom: 1px solid var(--accent); + margin: 0; + padding: 25px; +} + +blockquote::before { + content: ">"; + display: block; + position: absolute; + left: 0; + color: var(--accent); +} + +blockquote p:first-child { + margin-top: 0; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +table { + table-layout: auto; + border-collapse: collapse; +} + +table, +th, +td { + border: 2px solid var(--foreground); + padding: 10px; +} + +th { + border-style: solid; + color: var(--foreground); + text-align: left; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +hr { + width: 100%; + border: none; + background: var(--accent); + height: 2px; +} + +/* One Heading Size */ +.headings--one-size h1, +.headings--one-size h2, +.headings--one-size h3, +.headings--one-size h4, +.headings--one-size h5, +.headings--one-size h6 { + font-size: 1.4rem; + line-height: 1.3; + margin: 20px 0; +} + +.headings--one-size ~ h1:first-child, +.headings--one-size ~ h2:first-child, +.headings--one-size ~ h3:first-child, +.headings--one-size ~ h4:first-child, +.headings--one-size ~ h5:first-child, +.headings--one-size ~ h6:first-child { + margin-top: 20px; +} + +blockquote.twitter-tweet { + position: relative; + background: var(--background); + font: inherit; + color: inherit; + border: 1px solid var(--accent); + padding-top: 60px; +} + +blockquote.twitter-tweet a { + color: var(--accent); + text-decoration: underline; +} + +blockquote.twitter-tweet::before { + content: "> From X:"; + position: absolute; + top: 20px; + left: 20px; + color: var(--accent); + font-weight: bold; +} + +.container { + display: flex; + flex-direction: column; + padding: 40px; + max-width: 864px; + min-height: 100vh; + border-right: 1px solid color-mix(in srgb, var(--accent) 10%, transparent); +} + +.container.full, .container.center { + border: none; + margin: 0 auto; +} + +.container.full { + max-width: 100%; +} + +.content { + display: flex; + flex-direction: column; +} + +.hidden { + display: none; +} + +.__h_video { + position: relative; + overflow: visible !important; + height: auto !important; + padding-bottom: 0 !important; +} + +.__h_video a { + position: relative; + display: block; + width: 100%; +} + +.__h_video a img { + width: 100%; +} + +.__h_video a .play { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 60px; + height: 60px; +} + +iframe[src*="youtube.com"] { + border: 8px solid var(--accent) !important; + padding: 8px !important; +} + +@media (max-width: 684px) { + :root { + --font-size: 0.95rem; + } + + .container { + padding: 20px; + } +} + +@media print { + .container { + display: initial; + } + + .content { + display: initial; + } +} + +/* ---- header.css ---- */ + +.header { + display: flex; + flex-direction: column; + position: relative; +} + +.header__inner { + display: flex; + align-items: center; + justify-content: space-between; +} + +.header__logo { + display: flex; + flex: 1; +} + +.header__logo::after { + content: ""; + background: repeating-linear-gradient(90deg, var(--accent), var(--accent) 2px, transparent 0, transparent 10px); + display: block; + width: 100%; + right: 10px; +} + +.header__logo a { + flex: 0 0 auto; + max-width: 100%; + text-decoration: none; +} + +.logo { + display: flex; + align-items: center; + text-decoration: none; + background: var(--accent); + color: var(--background); + font-weight: bold; + padding: 5px 10px; +} + +@media print { + .header { + display: none; + } +} + +/* ---- menu.css ---- */ + +.navigation-menu { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin: 20px 1px; +} + +.navigation-menu__inner { + display: flex; + flex: 1; + flex-wrap: wrap; + list-style: none; + margin: 0; + padding: 0; +} + +.navigation-menu__inner > li { + flex: 0 0 auto; + margin-bottom: 10px; + white-space: nowrap; +} + +.navigation-menu__inner > li:not(:last-of-type) { + margin-right: 20px; +} + +.navigation-menu .spacer { + flex-grow: 1 !important; +} + +.menu { + display: flex; + flex-direction: column; + position: relative; + list-style: none; + padding: 0; + margin: 0; +} + +.menu__trigger { + margin-right: 0 !important; + color: var(--accent); + user-select: none; + cursor: pointer; +} + +.menu__dropdown { + display: none; + flex-direction: column; + position: absolute; + background: var(--background); + box-shadow: 0 10px var(--background), -10px 10px var(--background), 10px 10px var(--background); + color: var(--accent); + border: 2px solid var(--accent); + margin: 0; + padding: 10px; + top: 10px; + left: 0; + list-style: none; + z-index: 99; +} + +.open .menu__dropdown { + display: flex; +} + +.menu__dropdown > li { + flex: 0 0 auto; +} + +.menu__dropdown > li:not(:last-of-type) { + margin-bottom: 10px; +} + +.menu__dropdown > li a { + display: flex; + padding: 5px; +} + +.menu--mobile .menu__trigger { + color: var(--accent); + border: 2px solid; + margin-left: 10px; + height: 100%; + padding: 3px 8px; + margin-bottom: 0 !important; + position: relative; + cursor: pointer; + display: none; +} + +.menu--mobile li { + flex: 0 0 auto; +} + +.menu--mobile li:not(:last-of-type) { + margin-bottom: 10px; +} + +.menu--language-selector .menu__trigger { + color: var(--accent); + border: 2px solid; + margin-left: 5px; + height: 100%; + padding: 3px 8px; + margin-bottom: 0 !important; + position: relative; + cursor: pointer; +} + +.menu--language-selector .menu__dropdown { + left: auto; + right: 0; +} + +@media (max-width: 684px) { + .navigation-menu { + margin: 0; + } + + .navigation-menu__inner { + flex-direction: column; + align-items: flex-start; + padding: 0; + } + + .navigation-menu__inner li { + margin: 0; + padding: 5px; + } + + .menu--desktop { + display: none; + } + + .menu--mobile .menu__trigger { + display: block; + } + + .menu--mobile .menu__dropdown { + left: auto; + right: 0; + } + + .menu--language-selector .menu__trigger { + display: none; + } +} + +/* ---- footer.css ---- */ + +.footer { + padding: 40px 0; + flex-grow: 0; + opacity: 0.65; +} + +.footer__inner { + display: flex; + align-items: center; + justify-content: space-between; + margin: 0; + max-width: 100%; +} + +.footer a { + color: inherit; +} + +.footer .copyright { + display: flex; + flex-flow: row wrap; + flex: 1; + align-items: center; + justify-content: center; +} + +.footer .copyright--user { + margin: auto; + text-align: center; +} + +.footer .copyright > *:first-child:not(:only-child) { + margin-right: 10px; +} + +.footer .copyright span { + white-space: nowrap; +} + +@media (max-width: 900px) { + .footer__inner { + flex-direction: column; + } +} + +/* ---- post.css ---- */ + +.index-content { + margin: 25px 0; +} + +.framed { + border: 1px solid var(--accent); + padding: 20px; +} + +.framed *:first-child { + margin-top: 0; +} + +.framed *:last-child { + margin-bottom: 0; +} + +.posts { + width: 100%; +} + +.post { + width: 100%; + text-align: left; + padding: 30px 0; +} + +.post:not(:last-of-type) { + border-bottom: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); +} + +.post-meta { + font-size: inherit; + margin-bottom: 10px; + color: color-mix(in srgb, var(--foreground) 65%, transparent); +} + +.post-meta > *:not(:first-child)::before { + content: "::"; + display: inline-block; + margin: 0 8px; +} + +.post-title { + position: relative; + color: var(--accent); + margin-top: 0 !important; + margin-bottom: 15px !important; + padding-bottom: 15px; + border-bottom: 3px dotted var(--accent); + text-decoration: none !important; +} + +.post-title::after { + content: ""; + position: absolute; + bottom: 2px; + display: block; + width: 100%; + border-bottom: 3px dotted var(--accent); +} + +.post-title a { + text-decoration: none; +} + +.post-tags { + display: block; + margin-bottom: 20px; + font-size: inherit; + color: var(--accent); +} + +.table-of-contents { + margin: 40px 0; +} + +.post-content { + margin-top: 25px; +} + +.post-cover { + margin: 25px 0; +} + +.post ul { + list-style: none; +} + +.post ul li:not(:empty)::before { + content: "-"; + position: absolute; + left: -20px; + color: var(--accent); +} + +.post--regulation h1 { + justify-content: center; +} + +.post--regulation h2 { + justify-content: center; + margin-bottom: 10px; +} + +.post--regulation h2 + h2 { + margin-top: -10px; + margin-bottom: 20px; +} + +.hanchor { + position: absolute; + color: var(--accent); + text-decoration: none; + margin-left: 10px; + visibility: hidden; +} + +h1:hover .hanchor, +h2:hover .hanchor, +h3:hover .hanchor, +h4:hover .hanchor, +h5:hover .hanchor, +h6:hover .hanchor { + visibility: visible; +} + +.footnotes { + color: color-mix(in srgb, var(--foreground) 50%, transparent); +} + +.footnotes hr { + background: color-mix(in srgb, var(--foreground) 50%, transparent); +} + +@media (max-width: 684px) { + .post-cover { + padding: 10px; + border-width: 10px; + } +} + +/* ---- pagination.css ---- */ + +.pagination { + margin-top: 50px; +} + +.pagination__title { + display: flex; + text-align: center; + position: relative; + margin: 100px 0 20px; +} + +.pagination__title-h { + text-align: center; + margin: 0 auto; + padding: 5px 10px; + background: var(--background); + color: color-mix(in srgb, var(--foreground) 30%, transparent); + font-size: calc(var(--font-size) * 0.8); + text-transform: uppercase; + text-decoration: none; + letter-spacing: 0.1em; + z-index: 1; +} + +.pagination__title hr { + position: absolute; + left: 0; + right: 0; + width: 100%; + margin-top: 15px; + z-index: 0; +} + +.pagination__buttons { + display: flex; + align-items: center; + justify-content: center; + flex-flow: row wrap; + gap: 10px; +} + +.pagination__buttons a { + display: inline-flex; + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + text-decoration: none; +} + +.button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: inherit; + padding: 0; + appearance: none; +} + +.button a { + display: flex; + justify-content: center; + flex: 1; + padding: 8px 16px; +} + +.button__text { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.next .button__icon { + margin-left: 8px; +} + +.prev .button__icon { + margin-right: 8px; +} + +@media print { + .pagination { + display: none; + } +} + +/* ---- buttons.css ---- */ + +.button-container { + display: table; + margin-left: auto; + margin-right: auto; +} + +button, +.button, +a.button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 18px; + text-decoration: none; + text-align: center; + font: inherit; + font-weight: bold; + appearance: none; + cursor: pointer; + outline: none; +} + +button.outline, +.button.outline, +a.button.outline { + background: transparent; + box-shadow: none; + padding: 8px 18px; +} + +button.outline :hover, +.button.outline :hover, +a.button.outline :hover { + transform: none; + box-shadow: none; +} + +button.link, +.button.link, +a.button.link { + background: none; + font-size: var(--font-size); +} + +button.small, +.button.small, +a.button.small { + font-size: calc(var(--font-size) * 0.8); +} + +button.wide, +.button.wide, +a.button.wide { + min-width: 200px; + padding: 14px 24px; +} + +a.button.inline { + background: none; + color: var(--accent); + padding: initial; + margin: initial; + border: initial; + font-weight: initial; + text-decoration: none; +} + +a.button.inline:active, +a.button.inline:hover { + background: none; +} + +a.read-more, +a.read-more:hover, +a.read-more:active { + display: inline-flex; + border: none; + background: none; + box-shadow: none; + padding: 0; + max-width: 100%; + text-decoration: none; +} + +/* ---- terms.css ---- */ + +.terms h3 { + font-size: initial; +} + +.terms ul { + list-style: none; +} + +.terms ul li a { + color: var(--accent); +} + +.terms ul li:not(:empty)::before { + content: "-"; + position: absolute; + left: -20px; + color: var(--accent); +} + +/* ---- code.css ---- */ + +:root { + --code-border: color-mix(in srgb, var(--foreground) 10%, transparent); +} + +pre:not(.chroma) { + padding: 10px; +} + +pre.chroma { + margin: 0; + padding: 10px 0; + color: var(--foreground); +} + +pre code { + color: var(--foreground); + border: none; +} + +code { + color: var(--accent); + text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} + +.highlight { + position: relative; + margin: 20px 0; + border: 1px solid var(--code-border); +} + +.highlight pre { + border: none; +} + +.code-title { + display: flex; + align-items: center; + justify-content: space-between; + background: color-mix(in srgb, var(--foreground) 5%, transparent); + border-bottom: 1px solid var(--code-border); + color: var(--comment); + text-transform:uppercase; + font-size: calc(var(--font-size) * 0.8); + padding: 6px 10px; + line-height: 1; +} + +.copy-button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 3px 8px; + text-decoration: none; + text-align: center; + font-size: 13px; + font-weight: 500; + border: 1px solid color-mix(in srgb, var(--accent) 15%, transparent); + appearance: none; + cursor: pointer; + outline: none; +} + +.code-title .copy-button { + z-index: 1; + background: color-mix(in srgb, var(--foreground) 5%, var(--background)); +} + +.code-title:hover .copy-button { + display: inline-block; +} + +.code-title .copy-button:hover { + background: color-mix(in srgb, var(--accent) 10%, var(--background)); +} + +.collapsable-code { + position: relative; + width: 100%; + margin: 20px 0; + border: 1px solid var(--accent); + + .highlight { + margin: 0; + } +} + +.collapsable-code__title { + display: inline-flex; + align-items: center; + flex: 1; + color: var(--accent); + padding: 3px 10px; + font-size: calc(var(--font-size) * 0.9); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.collapsable-code__language { + color: var(--accent); + border: 1px solid var(--accent); + border-bottom: none; + text-transform: uppercase; + padding: 3px 10px; +} + +.collapsable-code summary { + color: var(--accent); + padding: 0 10px; + cursor: pointer; +} + +.collapsable-code summary:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent); +} + +.collapsable-code pre { + margin-top: 0; +} + +.collapsable-code pre::first-line { + line-height: 0; +} + +/* ========================================================================== + Apache Roller additions + ========================================================================== */ + +/* Light variant, used when the reader's system prefers a light color scheme. + Same hues as Terminal's default amber scheme, with the accent darkened to + keep link text readable on a light background. */ +@media (prefers-color-scheme: light) { + :root { + --background: #f6f3eb; + --foreground: #1a170f; + --accent: #8a5a00; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Mobile menu:
in place of Terminal's menu.js */ +.menu--mobile { + display: none; +} +.menu--mobile summary { + list-style: none; +} +.menu--mobile summary::-webkit-details-marker { + display: none; +} +.menu--mobile[open] .menu__dropdown { + display: flex; + top: 40px; + width: max-content; + white-space: nowrap; +} +.navigation-menu a[aria-current="page"] { + text-decoration-style: double; +} +@media (max-width: 684px) { + .menu--mobile { + display: flex; + } + .navigation-menu { + display: none; + } +} + +/* Footer: Roller renders more items than Terminal's two spans, so space them + with a gap instead of the first item's margin */ +.footer .copyright { + column-gap: 1ch; +} +.footer .copyright > *:first-child:not(:only-child) { + margin-right: 0; +} + +.post-list-empty { + color: color-mix(in srgb, var(--foreground) 65%, transparent); +} +.post-meta a { + color: inherit; +} + +/* Entry footer: Roller comments (markup from #showWeblogEntryComments and + #showWeblogEntryCommentForm) styled like Terminal's framed blocks */ +.post-comments { + margin-top: 50px; +} +.post-comments br { + display: none; +} +.post-comments ul li::before, +.post-comments ul li:not(:empty)::before { + content: none; +} +.post-comments ul { + margin-left: 0; +} +.comments-head { + margin: 25px 0; + color: var(--accent); + font-weight: bold; +} +.comments-head::before { + content: "## "; +} +.comments:not(:has(.comment)) { + display: none; +} +.comment { + margin-bottom: 20px; + padding: 10px 20px; + border-left: 2px solid var(--accent); +} +.comment-details { + margin: 10px 0 0; + color: color-mix(in srgb, var(--foreground) 65%, transparent); +} +.comment .entrypermalink { + margin-left: 5px; + text-decoration: none; +} +.comments-form { + border: 1px solid var(--accent); + padding: 0 20px 20px; +} +.comments-form li { + margin-bottom: 15px; +} +.comments-form label.desc { + display: block; + margin-bottom: 5px; +} +.comments-form label.choice { + margin-left: 8px; +} +.comments-form input.text, +.comments-form textarea { + width: 100%; +} +.comments-form input[type="checkbox"] { + border-color: var(--accent); +} +.comments-form li.info { + color: color-mix(in srgb, var(--foreground) 65%, transparent); +} +.comments-form .status, +.comments-form .error { + display: block; + margin: 15px 0; +} +.comments-form .error { + color: #ff6b6b; +} +@media (prefers-color-scheme: light) { + .comments-form .error { + color: #b3261e; + } +} +input[type="submit"], +input[type="button"] { + font: inherit; + font-weight: bold; + background: transparent; + color: var(--accent); + padding: 5px 18px; + border: 4px solid var(--accent); + border-radius: var(--radius); + cursor: pointer; +} +input[type="submit"]:hover, +input[type="button"]:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); +} + +/* Search */ +.search-form { + display: flex; + align-items: center; + gap: 10px; + margin: 25px 0; +} +.search-form input { + flex: 1; + min-width: 0; +} +.search-prompt { + color: var(--accent); + font-weight: bold; +} + +/* Archives (Terminal's terms.html list style) */ +.archives h2 { + color: var(--accent); +} +.archive-list { + list-style: none; +} +.archive-list time { + margin: 0 10px 0 0; +} diff --git a/app/src/main/webapp/themes/terminal/theme.xml b/app/src/main/webapp/themes/terminal/theme.xml new file mode 100644 index 000000000..070770ddb --- /dev/null +++ b/app/src/main/webapp/themes/terminal/theme.xml @@ -0,0 +1,20 @@ + + + + + terminalTerminal + A port of Terminal, the retro console theme for Hugo: monospace type, a dark background with amber accents, and a light variant for readers who prefer one. + panr (ported for Apache Roller) + + custom.cssTerminal custom stylescustom.csstext/csscustom.cssvelocity + + + + + + + + + + + diff --git a/app/src/main/webapp/themes/terminal/weblog.vm b/app/src/main/webapp/themes/terminal/weblog.vm new file mode 100644 index 000000000..d81e74050 --- /dev/null +++ b/app/src/main/webapp/themes/terminal/weblog.vm @@ -0,0 +1,33 @@ + + + + #includeTemplate($model.weblog "standard_head") + #if($model.tags && $model.tags.size() > 0) + #foreach($tagName in $model.tags)$tagName#if($foreach.hasNext), #end#end :: $model.weblog.name + #elseif($model.weblogCategory) + $model.weblogCategory.name :: $model.weblog.name + #else + $model.weblog.name + #end + + +
+ #includeTemplate($model.weblog "standard_header") +
+ #set($pager = $model.getWeblogEntriesPager()) + #if($model.tags && $model.tags.size() > 0) +

Posts for: #foreach($tagName in $model.tags)#$tagName#if($foreach.hasNext), #end#end

+ #elseif($model.weblogCategory) +

Posts in: $model.weblogCategory.name

+ #if($utils.isNotEmpty($model.weblogCategory.description)) +
$model.weblogCategory.description
+ #end + #elseif(!$pager.prevLink && $utils.isNotEmpty($model.weblog.about)) +
$model.weblog.about
+ #end + #includeTemplate($model.weblog "_post_list") +
+ #includeTemplate($model.weblog "standard_footer") +
+ + diff --git a/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java b/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java index b4584c44d..e4d3aa2be 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/ThemeManagerTest.java @@ -51,10 +51,15 @@ public void testThemeAssumptions() throws Exception { assertNotNull( themeManager.getTheme("basic") ); assertNotNull( themeManager.getTheme("basic").getStylesheet() ); assertNull( themeManager.getTheme("frontpage").getStylesheet() ); + assertNotNull( themeManager.getTheme("hyde") ); + assertNotNull( themeManager.getTheme("hyde").getStylesheet() ); + assertNotNull( themeManager.getTheme("papermod") ); + assertNotNull( themeManager.getTheme("papermod").getStylesheet() ); + assertNotNull( themeManager.getTheme("terminal") ); + assertNotNull( themeManager.getTheme("terminal").getStylesheet() ); } } - diff --git a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java index 158f70f20..54693bb50 100644 --- a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java +++ b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,6 +30,7 @@ import com.microsoft.playwright.BrowserType; import com.microsoft.playwright.Page; import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.TimeoutError; import com.microsoft.playwright.Tracing; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -343,17 +345,39 @@ protected void insertMediaFileThroughDialog(String weblogHandle) { } } + private static final String ADVANCED_PANEL = "#collapseAdvanced"; + private static final String PUBLISHING_TIME_TOPS = "() => ['hours', 'minutes', 'seconds'].map(name =>" + + " document.querySelector(`select[name='bean.${name}']`).getBoundingClientRect().top)"; + /** * Expands the entry editor's advanced settings and checks the publishing * time's hour, minute and second selects sit on one line rather than as * stacked form rows. */ protected void assertPublishingTimeOnOneLine() { - page.locator("a[href='#collapseAdvanced']").click(); - assertThat(page.locator("#collapseAdvanced")).isVisible(); - double hours = page.locator("select[name='bean.hours']").boundingBox().y; - double minutes = page.locator("select[name='bean.minutes']").boundingBox().y; - double seconds = page.locator("select[name='bean.seconds']").boundingBox().y; + // the selects only settle into place once Bootstrap has finished + // animating the panel open, so wait for the shown event + page.evaluate("sel => document.querySelector(sel).addEventListener('shown.bs.collapse'," + + " () => window.rollerAdvancedShown = true, {once: true})", ADVANCED_PANEL); + page.locator("a[href='" + ADVANCED_PANEL + "']").click(); + page.waitForFunction("() => window.rollerAdvancedShown === true"); + assertThat(page.locator(ADVANCED_PANEL)).isVisible(); + + // poll until the three selects share a line, retrying the way + // Playwright's own assertions do; a timeout falls through to the + // assertion below, which reports where they ended up + try { + page.waitForFunction("() => { const y = (" + PUBLISHING_TIME_TOPS + ")();" + + " return Math.abs(y[0] - y[1]) < 2 && Math.abs(y[1] - y[2]) < 2; }"); + } catch (TimeoutError e) { + // reported by the assertion below + } + // read all three positions in one pass, so a layout shift between + // separate reads can't make them disagree + List tops = (List) page.evaluate(PUBLISHING_TIME_TOPS); + double hours = ((Number) tops.get(0)).doubleValue(); + double minutes = ((Number) tops.get(1)).doubleValue(); + double seconds = ((Number) tops.get(2)).doubleValue(); org.junit.jupiter.api.Assertions.assertTrue( Math.abs(hours - minutes) < 2 && Math.abs(minutes - seconds) < 2, "publishing time selects should share a line, but sit at y=" + hours + ", " + minutes + ", " + seconds);