Add bookmark item search (#15)
- Implement form styling throughout - Modify header links for narrower views - Clean up CSS
This commit is contained in:
parent
9d59bfb1c6
commit
58dd7a4ffb
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user