Add bookmark item search (#15)

- Implement form styling throughout
- Modify header links for narrower views
- Clean up CSS
This commit is contained in:
Daniel J. Summers 2024-05-26 16:56:30 -04:00
parent 9d59bfb1c6
commit 58dd7a4ffb
6 changed files with 116 additions and 58 deletions

View File

@ -138,13 +138,16 @@ class ItemList {
* Create an item list with items matching given search terms * Create an item list with items matching given search terms
* *
* @param string $search The item search terms / query * @param string $search The item search terms / query
* @param bool $isBookmarked Whether to restrict the search to bookmarked items
* @param SQLite3 $db The database connection to use to obtain items * @param SQLite3 $db The database connection to use to obtain items
* @return static An item list match the given search terms * @return static An item list match the given search terms
*/ */
public static function matchingSearch(string $search, SQLite3 $db): static { public static function matchingSearch(string $search, bool $isBookmarked, SQLite3 $db): static {
$list = new static($db, $where = $isBookmarked ? ['item.is_bookmarked = 1'] : [];
self::makeQuery($db, ['item.id IN (SELECT ROWID FROM item_search WHERE content MATCH :search)'], $where[] = 'item.id IN (SELECT ROWID FROM item_search WHERE content MATCH :search)';
[[':search', $search]]), 'Matching', "/search?search=$search"); $list = new static($db, self::makeQuery($db, $where, [[':search', $search]]),
'Matching' . ($isBookmarked ? ' Bookmarked' : ''),
"/search?search=$search&items=" . ($isBookmarked ? 'bookmarked' : 'all'));
$list->showIndicators = true; $list->showIndicators = true;
$list->displayFeed = true; $list->displayFeed = true;
return $list; return $list;

View File

@ -25,28 +25,28 @@ header {
flex-flow: row wrap; flex-flow: row wrap;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
div, nav {
div {
margin-bottom: .25rem; margin-bottom: .25rem;
} }
.title { .title {
font-size: 1.5rem; font-size: 1.5rem;
} }
.version { .version {
font-size: .85rem; font-size: .85rem;
padding-left: .5rem; padding-left: .5rem;
color: rgba(255, 255, 255, .75); color: rgba(255, 255, 255, .75);
} }
a:link, a:visited { a:link, a:visited {
color: white; color: white;
} }
nav {
display: flex;
flex-flow: row wrap;
gap: 0 .4rem;
}
} }
main { main {
padding: 0 .5rem; padding: 0 .5rem;
.refresh, .loading { .refresh, .loading {
font-style: italic; font-style: italic;
font-size: .9rem; font-size: .9rem;
@ -54,14 +54,12 @@ main {
.htmx-request .refresh { .htmx-request .refresh {
display: none; display: none;
} }
.loading { .loading {
display: none; display: none;
} }
.htmx-request .loading { .htmx-request .loading {
display: inline; display: inline;
} }
.user_messages { .user_messages {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
@ -74,11 +72,9 @@ main {
background-color: rgba(255, 255, 255, .75); background-color: rgba(255, 255, 255, .75);
padding: .25rem; padding: .25rem;
} }
.user_messages + h1 { .user_messages + h1 {
margin-top: .25rem; margin-top: .25rem;
} }
.item_published { .item_published {
margin-bottom: 1rem; margin-bottom: 1rem;
line-height: 1.2; line-height: 1.2;
@ -87,35 +83,73 @@ main {
article { article {
max-width: 60rem; max-width: 60rem;
margin: auto; margin: auto;
.item_content { .item_content {
border: solid 1px navy; border: solid 1px navy;
border-radius: .5rem; border-radius: .5rem;
background-color: white; background-color: white;
padding: .5rem; padding: .5rem;
img { img {
max-width: 100%; max-width: 100%;
object-fit: contain; object-fit: contain;
} }
} }
.meta { .meta {
font-size: .9rem; font-size: .9rem;
} }
&.docs {
line-height: 1.4rem;
}
} }
article.docs { form {
line-height: 1.4rem; display: flex;
flex-flow: row wrap;
justify-content: center;
gap: 0 2rem;
label {
font-size: .9rem;
font-weight: bold;
input, select {
display: block;
}
}
.break {
flex-basis: 100%;
height: 1rem;
width: 0;
}
input[type=url],
input[type=text],
input[type=email],
input[type=password],
select {
min-width: 12rem;
max-width: 100%;
font-size: 1rem;
padding: .25rem;
border-radius: .25rem;
background-color: white;
border: solid 2px navy;
}
select {
min-width: unset;
max-width: unset;
}
} }
@media all and (min-width: 60rem) {
input[type=url], form {
input[type=text], input[type=url],
input[type=email], input[type=text],
input[type=password] { input[type=email],
width: 40%; input[type=password] {
font-size: 1rem; min-width: 25rem;
padding: .25rem; }
border-radius: .25rem; }
}
.action_buttons {
margin: 1rem 0;
display: flex;
flex-flow: row nowrap;
justify-content: space-evenly;
} }
button, button,
.action_buttons a:link, .action_buttons a:link,
@ -128,18 +162,11 @@ button,
border-radius: .25rem; border-radius: .25rem;
cursor: pointer; cursor: pointer;
border: none; border: none;
} &:hover {
button:hover, text-decoration: none;
.action_buttons a:hover { cursor: pointer;
text-decoration: none; background: linear-gradient(navy, #000032);
cursor: pointer; }
background: linear-gradient(navy, #000032);
}
.action_buttons {
margin: 1rem 0;
display: flex;
flex-flow: row nowrap;
justify-content: space-evenly;
} }
code { code {
font-size: .9rem; font-size: .9rem;
@ -149,12 +176,10 @@ p.back-link {
} }
.item_heading { .item_heading {
margin-bottom: 0; margin-bottom: 0;
.bookmark { .bookmark {
padding: 0; padding: 0;
border: solid 1px black; border: solid 1px black;
border-radius: .5rem; border-radius: .5rem;
&.add { &.add {
background-color: lightgray; background-color: lightgray;
&:hover { &:hover {
@ -167,7 +192,6 @@ p.back-link {
background: linear-gradient(gray, lightgreen); background: linear-gradient(gray, lightgreen);
} }
} }
img { img {
max-width: 1.5rem; max-width: 1.5rem;
max-height: 1.5rem; max-height: 1.5rem;

View File

@ -61,7 +61,8 @@ page_head($title); ?>
<label> <label>
Feed URL Feed URL
<input type=url name=url required autofocus value="<?=$feed['url']?>"> <input type=url name=url required autofocus value="<?=$feed['url']?>">
</label><br> </label>
<span class=break></span>
<button type=submit>Save</button> <button type=submit>Save</button>
</form> </form>
</article><?php </article><?php

View File

@ -11,13 +11,35 @@ include '../start.php';
$db = Data::getConnection(); $db = Data::getConnection();
Security::verifyUser($db); Security::verifyUser($db);
if (key_exists('search', $_GET)) { $search = $_GET['search'] ?? '';
$list = ItemList::matchingSearch($_GET['search'], $db); $items = $_GET['items'] ?? 'all';
if ($search != '') {
$list = ItemList::matchingSearch($search, $items == 'bookmarked', $db);
} }
page_head('Item Search'); ?> page_head('Item Search'); ?>
<h1>Item Search</h1> <h1>Item Search</h1>
// TODO: search form <?php <article>
if (isset($list)) $list->render(); <form method=GET action=/search>
<label>
Search Criteria
<input type=text name=search required autofocus value="<?=htmlspecialchars($search)?>">
</label>
<label>
Items to Search
<select name=items>
<option value=all <?=$items == 'all' ? ' selected' : ''?>>All</option>
<option value=bookmarked <?=$items == 'bookmarked' ? ' selected' : ''?>>Bookmarked</option>
</select>
</label>
<span class=break></span>
<button type=submit>Search</button>
</form><?php
if (isset($list)) { ?>
<hr><?php
$list->render();
} ?>
</article><?php
page_foot(); page_foot();
$db->close(); $db->close();

View File

@ -26,12 +26,13 @@ page_head('Log On'); ?>
<label> <label>
E-mail Address E-mail Address
<input type=email name=email required autofocus> <input type=email name=email required autofocus>
</label><br><?php </label><?php
} ?> } ?>
<label> <label>
Password Password
<input type=password name=password required<?=$isSingle ? ' autofocus' : ''?>> <input type=password name=password required<?=$isSingle ? ' autofocus' : ''?>>
</label><br> </label>
<span class=break></span>
<button type=submit>Log On</button> <button type=submit>Log On</button>
</form> </form>
</article><?php </article><?php

View File

@ -41,6 +41,11 @@ function add_info(string $message): void {
/** @var bool $is_htmx True if this request was initiated by htmx, false if not */ /** @var bool $is_htmx True if this request was initiated by htmx, false if not */
$is_htmx = key_exists('HTTP_HX_REQUEST', $_SERVER) && !key_exists('HTTP_HX_HISTORY_RESTORE_REQUEST', $_SERVER); $is_htmx = key_exists('HTTP_HX_REQUEST', $_SERVER) && !key_exists('HTTP_HX_HISTORY_RESTORE_REQUEST', $_SERVER);
function nav_link(string $link, bool $isFirst = false) {
$sep = $isFirst ? '' : ' | ';
echo "<span>$sep$link</span>";
}
/** /**
* Render the title bar for the page * Render the title bar for the page
*/ */
@ -52,7 +57,7 @@ function title_bar(): void {
}; ?> }; ?>
<header hx-target=#main hx-push-url=true> <header hx-target=#main hx-push-url=true>
<div><a href=/ class=title>Feed Reader Central</a><span class=version>v<?=$version?></span></div> <div><a href=/ class=title>Feed Reader Central</a><span class=version>v<?=$version?></span></div>
<div><?php <nav><?php
if (key_exists(Key::USER_ID, $_SESSION)) { if (key_exists(Key::USER_ID, $_SESSION)) {
$db = Data::getConnection(); $db = Data::getConnection();
try { try {
@ -65,20 +70,22 @@ function title_bar(): void {
$bookQuery->bindValue(':id', $_SESSION[Key::USER_ID]); $bookQuery->bindValue(':id', $_SESSION[Key::USER_ID]);
$bookResult = $bookQuery->execute(); $bookResult = $bookQuery->execute();
$hasBookmarks = $bookResult && $bookResult->fetchArray(SQLITE3_NUM)[0]; $hasBookmarks = $bookResult && $bookResult->fetchArray(SQLITE3_NUM)[0];
echo hx_get('/feeds', 'Feeds') . ' | '; nav_link(hx_get('/feeds', 'Feeds'), true);
if ($hasBookmarks) echo hx_get('/?bookmarked', 'Bookmarked') . ' | '; if ($hasBookmarks) nav_link(hx_get('/?bookmarked', 'Bookmarked'));
echo hx_get('/search', 'Search') . ' | ' . hx_get('/docs/', 'Docs') nav_link(hx_get('/search', 'Search'));
. ' | <a href=/user/log-off>Log Off</a>'; nav_link(hx_get('/docs/', 'Docs'));
nav_link('<a href=/user/log-off>Log Off</a>');
if ($_SESSION[Key::USER_EMAIL] != Security::SINGLE_USER_EMAIL) { if ($_SESSION[Key::USER_EMAIL] != Security::SINGLE_USER_EMAIL) {
echo " | {$_SESSION[Key::USER_EMAIL]}"; nav_link($_SESSION[Key::USER_EMAIL]);
} }
} finally { } finally {
$db->close(); $db->close();
} }
} else { } else {
echo hx_get('/user/log-on', 'Log On') . ' | ' . hx_get('/docs/', 'Docs'); nav_link(hx_get('/user/log-on', 'Log On'), true);
nav_link(hx_get('/docs/', 'Docs'));
} ?> } ?>
</div> </nav>
</header> </header>
<main id=main hx-target=this hx-push-url=true hx-swap="innerHTML show:window:top"><?php <main id=main hx-target=this hx-push-url=true hx-swap="innerHTML show:window:top"><?php
} }