feat: add book detail view with spine-book-model, httpd/book handler, and title links
- Add spine--format-defs defconst shared between index and book models - Add spine-book-model with status extraction, format selector, notes, recommendation - Add templates/book.mustache (Pico CSS, journal-style layout) - Add httpd/book handler serving the detail page - Link book titles in index view to /book?id=X - Add spine-book-model-test.el with 13 tests
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Spine — {{title}}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.min.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/3.31.0/iconfont/tabler-icons.min.css" />
|
||||
<style>
|
||||
.book { max-width: 560px; margin-inline: auto; }
|
||||
.book hgroup { margin-bottom: .4rem; }
|
||||
.book hgroup h3 { margin-bottom: .1rem; }
|
||||
.book hgroup p { color: var(--pico-muted-color); margin: 0; }
|
||||
.cover { width: 44px; height: 60px; border-radius: var(--pico-border-radius);
|
||||
background: var(--pico-primary-background); color: var(--pico-primary-inverse);
|
||||
display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.pill { padding: .1rem .5rem; border-radius: var(--pico-border-radius); font-size: .75rem; font-weight: 500; }
|
||||
.pill.want { background: #e6f1fb; color: #0c447c; }
|
||||
.pill.reading { background: #faeeda; color: #854f0b; }
|
||||
.pill.read { background: #e1f5ee; color: #085041; }
|
||||
.formats { margin-bottom: 1.1rem; }
|
||||
.formats button { --pico-font-size: .85rem; }
|
||||
.seclabel { font-size: .72rem; letter-spacing: .03em; color: var(--pico-muted-color);
|
||||
text-transform: none; margin-bottom: .6rem; }
|
||||
.thread { border-left: 2px solid var(--pico-muted-border-color); padding-left: .9rem; margin-bottom: .9rem; }
|
||||
.entry { margin-bottom: .8rem; }
|
||||
.entry:last-child { margin-bottom: 0; }
|
||||
.edate { font-size: .75rem; color: var(--pico-muted-color); margin-bottom: .15rem; }
|
||||
.etext { font-size: .9rem; line-height: 1.5; }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
|
||||
background: var(--pico-primary-background); color: var(--pico-primary-inverse);
|
||||
display: flex; align-items: center; justify-content: center; font-size: .75rem; }
|
||||
.compose, .recform { margin-bottom: 0; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.pill.want { background: #14304a; color: #b5d4f4; }
|
||||
.pill.reading { background: #3a2c12; color: #fac775; }
|
||||
.pill.read { background: #103a30; color: #9fe1cb; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<article class="book">
|
||||
<p><a href="{{back_url}}" style="font-size:.85rem;">← Back to shelf</a></p>
|
||||
|
||||
<div style="display: flex; gap: 14px; align-items: flex-start; margin-bottom: 1rem;">
|
||||
<div class="cover"><i class="ti {{cover_icon}}" style="font-size: 22px;" aria-hidden="true"></i></div>
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<hgroup>
|
||||
<h3>{{title}}</h3>
|
||||
<p>{{author}}</p>
|
||||
</hgroup>
|
||||
{{#status_class}}
|
||||
<span class="pill {{status_class}}">{{status_label}}</span>
|
||||
{{/status_class}}
|
||||
{{#meta}}
|
||||
<span class="muted" style="color: var(--pico-muted-color); font-size: .85rem; margin-left: .5rem;">{{meta}}</span>
|
||||
{{/meta}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="formats" role="group">
|
||||
{{#formats}}
|
||||
<button class="{{^active}}secondary outline{{/active}}"><i class="ti {{icon}}" style="font-size: 15px; vertical-align: -2px; margin-right: 5px;" aria-hidden="true"></i>{{label}}</button>
|
||||
{{/formats}}
|
||||
</div>
|
||||
|
||||
{{#notes}}
|
||||
<p class="seclabel">Reading log</p>
|
||||
<div class="thread">
|
||||
{{#notes}}
|
||||
<div class="entry">
|
||||
<div class="edate">{{date}}</div>
|
||||
<div class="etext">{{text}}</div>
|
||||
</div>
|
||||
{{/notes}}
|
||||
</div>
|
||||
{{/notes}}
|
||||
|
||||
<div class="compose" role="group">
|
||||
<input type="text" placeholder="Add a note…" />
|
||||
<button><i class="ti ti-plus" style="font-size: 15px; vertical-align: -2px; margin-right: 4px;" aria-hidden="true"></i>Log note</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{{#recommendation}}
|
||||
<p class="seclabel">On your radar</p>
|
||||
<div style="display: flex; align-items: flex-start; gap: 10px; margin-bottom: 1rem;">
|
||||
<div class="avatar">{{initials}}</div>
|
||||
<div style="font-size: .9rem; line-height: 1.5;"><strong>{{by}}</strong><br />"{{note}}"</div>
|
||||
</div>
|
||||
{{/recommendation}}
|
||||
<div class="recform" role="group">
|
||||
<input type="text" placeholder="Recommended by…" style="flex: 0 0 34%;" />
|
||||
<input type="text" placeholder="Why it's on your radar…" />
|
||||
<button aria-label="Save recommendation"><i class="ti ti-check" style="font-size: 16px;" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -78,7 +78,8 @@
|
||||
{{#books}}
|
||||
<div class="row{{#selected}} selected{{/selected}}">
|
||||
<i class="ti {{format_icon}} glyph" aria-hidden="true"></i>
|
||||
<span class="title">{{title}} <span class="muted">· {{author}}</span></span>
|
||||
<a href="{{book_url}}" class="title" style="text-decoration:none;color:inherit">{{title}}</a>
|
||||
<span class="muted">· {{author}}</span>
|
||||
{{#tags_inline}}<span class="tags">{{tags_inline}}</span>{{/tags_inline}}
|
||||
{{#rec_via}}<span class="trail">via {{rec_via}}</span>{{/rec_via}}
|
||||
{{#rating_display}}<span class="trail">{{rating_display}}</span>{{/rating_display}}
|
||||
|
||||
Reference in New Issue
Block a user