mirror of
https://git.nolog.cz/NoLog.cz/headline.git
synced 2025-01-31 11:53:35 +01:00
Merge branch 'filter-by-feed-dialog'
This commit is contained in:
commit
ec03dace99
8 changed files with 288 additions and 119 deletions
95
view/app.py
95
view/app.py
|
@ -8,7 +8,7 @@ import confuse
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
DATABASE = "../data/diffs.db"
|
DATABASE = "file:../data/diffs.db?mode=ro"
|
||||||
CONFIG_FILE = "../data/config.yaml"
|
CONFIG_FILE = "../data/config.yaml"
|
||||||
|
|
||||||
config = confuse.Configuration("headline", __name__)
|
config = confuse.Configuration("headline", __name__)
|
||||||
|
@ -24,7 +24,7 @@ assets.url_expire = False
|
||||||
def get_db():
|
def get_db():
|
||||||
db = getattr(g, "_database", None)
|
db = getattr(g, "_database", None)
|
||||||
if db is None:
|
if db is None:
|
||||||
db = g._database = sqlite3.connect(DATABASE)
|
db = g._database = sqlite3.connect(DATABASE, uri=True)
|
||||||
db.row_factory = sqlite3.Row
|
db.row_factory = sqlite3.Row
|
||||||
return db
|
return db
|
||||||
|
|
||||||
|
@ -49,39 +49,86 @@ def websearch_to_fts_query(search: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_feeds():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"rss_source": str(conf["rss_source"]),
|
||||||
|
"unique_tag": str(conf["unique_tag"]),
|
||||||
|
"feed_name": str(conf["name"]),
|
||||||
|
}
|
||||||
|
for conf in config["feeds"]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
db = get_db().cursor()
|
db = get_db().cursor()
|
||||||
|
|
||||||
search = request.args.get("search", type=str, default="")
|
search = request.args.get("search", type=str, default="")
|
||||||
query = websearch_to_fts_query(search) if search else None
|
query = websearch_to_fts_query(search) if search else None
|
||||||
|
selected_feeds = request.args.getlist("feeds[]")
|
||||||
|
|
||||||
db.execute(
|
sql_select = "SELECT * FROM diffs "
|
||||||
f"SELECT count(*) FROM diffs{'_fts(?)' if query else ''}",
|
sql_count = "SELECT count(*) FROM diffs "
|
||||||
(query,) if query else (),
|
|
||||||
)
|
|
||||||
|
|
||||||
diff_count = db.fetchall()[0][0]
|
if query:
|
||||||
|
sql_part_query = f"JOIN (SELECT rowid FROM diffs_fts({query})) filter ON filter.rowid = diffs.diff_id "
|
||||||
|
sql_select = sql_select + sql_part_query
|
||||||
|
sql_count = sql_count + sql_part_query
|
||||||
|
|
||||||
|
if selected_feeds:
|
||||||
|
feeds = str(selected_feeds).strip("[]")
|
||||||
|
sql_part_feeds = f"WHERE feed_name in ({feeds}) "
|
||||||
|
sql_select = sql_select + sql_part_feeds
|
||||||
|
sql_count = sql_count + sql_part_feeds
|
||||||
|
|
||||||
# flask-paginate
|
# flask-paginate
|
||||||
page = request.args.get(get_page_parameter(), type=int, default=1)
|
page = request.args.get(get_page_parameter(), type=int, default=1)
|
||||||
|
db.execute(sql_count)
|
||||||
|
diff_count = db.fetchall()[0][0]
|
||||||
|
|
||||||
pagination = Pagination(
|
pagination = Pagination(
|
||||||
page=page, total=diff_count, record_name="diffs", css_framework="bootstrap5"
|
page=page, total=diff_count, record_name="diffs", css_framework="bootstrap5"
|
||||||
)
|
)
|
||||||
|
|
||||||
page_skip = pagination.skip
|
page_skip = pagination.skip
|
||||||
per_page = pagination.per_page
|
per_page = pagination.per_page
|
||||||
if query:
|
|
||||||
db.execute(
|
# Create and execute final query after getting page info
|
||||||
"SELECT * FROM diffs JOIN (SELECT rowid FROM diffs_fts(?)) filter ON filter.rowid = diffs.diff_id ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
sql_part_pagination = f"ORDER BY diff_id DESC LIMIT {per_page} OFFSET {page_skip} "
|
||||||
(query, per_page, page_skip),
|
sql_select = sql_select + sql_part_pagination
|
||||||
)
|
print(sql_select)
|
||||||
else:
|
db.execute(sql_select)
|
||||||
db.execute(
|
|
||||||
"SELECT * FROM diffs ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
# This would be a cleaner way to do it, but I have no clue how to make it work. Giving multiple feeds to the query is just seemingly impossible in sqlite.
|
||||||
(per_page, page_skip),
|
# What about switching to Elasticsearch? :)
|
||||||
)
|
|
||||||
|
# if selected_feeds and query:
|
||||||
|
# feeds = str(selected_feeds).strip("[]")
|
||||||
|
# db.execute(
|
||||||
|
# "SELECT * FROM diffs JOIN (SELECT rowid FROM diffs_fts(?)) filter ON filter.rowid = diffs.diff_id WHERE feed_name IN (?) ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
||||||
|
# (query, feeds, per_page, page_skip),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# elif query:
|
||||||
|
# db.execute(
|
||||||
|
# "SELECT * FROM diffs JOIN (SELECT rowid FROM diffs_fts(?)) filter ON filter.rowid = diffs.diff_id ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
||||||
|
# (query, per_page, page_skip),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# elif selected_feeds:
|
||||||
|
# feeds = str(selected_feeds).strip("[]").replace("'", '"')
|
||||||
|
# print(feeds)
|
||||||
|
# db.execute(
|
||||||
|
# f"SELECT * FROM diffs WHERE feed_name IN ({','.join(['?']*len(selected_feeds))}) ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
||||||
|
# (selected_feeds, per_page, page_skip),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# db.execute(
|
||||||
|
# "SELECT * FROM diffs ORDER BY diff_id DESC LIMIT ? OFFSET ?",
|
||||||
|
# (per_page, page_skip),
|
||||||
|
# )
|
||||||
|
|
||||||
diffs = db.fetchall()
|
diffs = db.fetchall()
|
||||||
|
|
||||||
html = render_template(
|
html = render_template(
|
||||||
|
@ -91,6 +138,8 @@ def index():
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
diff_count=diff_count,
|
diff_count=diff_count,
|
||||||
search=search,
|
search=search,
|
||||||
|
feeds=get_feeds(),
|
||||||
|
selected_feeds=selected_feeds,
|
||||||
)
|
)
|
||||||
|
|
||||||
res = make_response(html)
|
res = make_response(html)
|
||||||
|
@ -125,15 +174,7 @@ def about():
|
||||||
|
|
||||||
@app.route("/feeds")
|
@app.route("/feeds")
|
||||||
def feed_list():
|
def feed_list():
|
||||||
feeds = []
|
return render_template("feeds.html", feeds=get_feeds())
|
||||||
for conf in config["feeds"]:
|
|
||||||
feed = {
|
|
||||||
"rss_source": str(conf["rss_source"]),
|
|
||||||
"unique_tag": str(conf["unique_tag"]),
|
|
||||||
"feed_name": str(conf["name"]),
|
|
||||||
}
|
|
||||||
feeds.append(feed)
|
|
||||||
return render_template("feeds.html", feeds=feeds)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/robots.txt")
|
@app.route("/robots.txt")
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
border-color: var(--color-border);
|
border-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
:not(dialog) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,18 +132,36 @@
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button,
|
||||||
|
.button-outline {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
transition: background-color 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
transition: background-color 120ms;
|
|
||||||
color: #4b5563;
|
color: #4b5563;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button:not(:disabled):hover {
|
||||||
|
background: hsl(0 0% 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-outline {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: white;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-outline:not(:disabled):hover {
|
||||||
|
background: hsl(0 0% 95%);
|
||||||
|
}
|
||||||
|
|
||||||
.button-md {
|
.button-md {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
|
@ -151,14 +169,10 @@
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:not(:disabled) {
|
button:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:not(:disabled):hover {
|
|
||||||
background: hsl(0 0% 90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pagination */
|
/* Pagination */
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
|
@ -234,19 +248,37 @@
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[hx-swap-oob] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--un-shadow-inset) 0 1px 3px 0 rgba(0,0,0,0.1),var(--un-shadow-inset) 0 1px 2px -1px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog::backdrop {
|
||||||
|
background-color: hsl(0 0% 90% / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
/* layer: shortcuts */
|
/* layer: shortcuts */
|
||||||
.container{padding-left:1rem;padding-right:1rem;margin-left:auto;margin-right:auto;max-width:1200px;}
|
.container{padding-left:1rem;padding-right:1rem;margin-left:auto;margin-right:auto;max-width:1200px;}
|
||||||
.action-link{font-size:0.875rem;line-height:1.25rem;font-weight:500;--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));display:inline-flex;gap:0.375rem;align-items:center;}
|
.action-link{font-size:0.875rem;line-height:1.25rem;font-weight:500;--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));display:inline-flex;gap:0.375rem;align-items:center;}
|
||||||
.text-caption{font-size:0.875rem;line-height:1.25rem;font-weight:500;--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));}
|
.text-caption{font-size:0.875rem;line-height:1.25rem;font-weight:500;--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));}
|
||||||
|
.text-muted{--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));}
|
||||||
.action-link:hover{--un-text-opacity:1;color:rgba(29,78,216,var(--un-text-opacity));}
|
.action-link:hover{--un-text-opacity:1;color:rgba(29,78,216,var(--un-text-opacity));}
|
||||||
/* layer: default */
|
/* layer: default */
|
||||||
.p-0{padding:0;}
|
.p-0{padding:0;}
|
||||||
|
.px-2{padding-left:0.5rem;padding-right:0.5rem;}
|
||||||
.px-4{padding-left:1rem;padding-right:1rem;}
|
.px-4{padding-left:1rem;padding-right:1rem;}
|
||||||
.px-5{padding-left:1.25rem;padding-right:1.25rem;}
|
.px-5{padding-left:1.25rem;padding-right:1.25rem;}
|
||||||
|
.py-1\.5{padding-top:0.375rem;padding-bottom:0.375rem;}
|
||||||
.py-3{padding-top:0.75rem;padding-bottom:0.75rem;}
|
.py-3{padding-top:0.75rem;padding-bottom:0.75rem;}
|
||||||
.py-4{padding-top:1rem;padding-bottom:1rem;}
|
.py-4{padding-top:1rem;padding-bottom:1rem;}
|
||||||
.py-6{padding-top:1.5rem;padding-bottom:1.5rem;}
|
.py-6{padding-top:1.5rem;padding-bottom:1.5rem;}
|
||||||
.pr-2{padding-right:0.5rem;}
|
.pr-2{padding-right:0.5rem;}
|
||||||
|
.mx-2{margin-left:0.5rem;margin-right:0.5rem;}
|
||||||
.my-6{margin-top:1.5rem;margin-bottom:1.5rem;}
|
.my-6{margin-top:1.5rem;margin-bottom:1.5rem;}
|
||||||
.mb-2{margin-bottom:0.5rem;}
|
.mb-2{margin-bottom:0.5rem;}
|
||||||
.mb-3{margin-bottom:0.75rem;}
|
.mb-3{margin-bottom:0.75rem;}
|
||||||
|
@ -254,18 +286,24 @@
|
||||||
.mb-6{margin-bottom:1.5rem;}
|
.mb-6{margin-bottom:1.5rem;}
|
||||||
.mb-8{margin-bottom:2rem;}
|
.mb-8{margin-bottom:2rem;}
|
||||||
.mb-auto{margin-bottom:auto;}
|
.mb-auto{margin-bottom:auto;}
|
||||||
|
.ml-2{margin-left:0.5rem;}
|
||||||
|
.mr-2{margin-right:0.5rem;}
|
||||||
.mt-12{margin-top:3rem;}
|
.mt-12{margin-top:3rem;}
|
||||||
|
.mt-3{margin-top:0.75rem;}
|
||||||
.block{display:block;}
|
.block{display:block;}
|
||||||
.display-none,
|
.display-none,
|
||||||
.hidden{display:none;}
|
.hidden{display:none;}
|
||||||
.bg-white{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}
|
.bg-white{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}
|
||||||
|
.hover\:bg-gray-100:hover{--un-bg-opacity:1;background-color:rgba(243,244,246,var(--un-bg-opacity));}
|
||||||
.fill-current{fill:currentColor;}
|
.fill-current{fill:currentColor;}
|
||||||
.border-b{border-bottom-width:1px;}
|
.border-b{border-bottom-width:1px;}
|
||||||
|
.rounded{border-radius:0.25rem;}
|
||||||
.text-2xl{font-size:1.5rem;line-height:2rem;}
|
.text-2xl{font-size:1.5rem;line-height:2rem;}
|
||||||
.text-4xl{font-size:2.25rem;line-height:2.5rem;}
|
.text-4xl{font-size:2.25rem;line-height:2.5rem;}
|
||||||
.text-lg{font-size:1.125rem;line-height:1.75rem;}
|
.text-lg{font-size:1.125rem;line-height:1.75rem;}
|
||||||
.text-sm{font-size:0.875rem;line-height:1.25rem;}
|
.text-sm{font-size:0.875rem;line-height:1.25rem;}
|
||||||
.text-xl{font-size:1.25rem;line-height:1.75rem;}
|
.text-xl{font-size:1.25rem;line-height:1.75rem;}
|
||||||
|
.text-xs{font-size:0.75rem;line-height:1rem;}
|
||||||
.font-bold{font-weight:700;}
|
.font-bold{font-weight:700;}
|
||||||
.font-medium{font-weight:500;}
|
.font-medium{font-weight:500;}
|
||||||
.text-black{--un-text-opacity:1;color:rgba(0,0,0,var(--un-text-opacity));}
|
.text-black{--un-text-opacity:1;color:rgba(0,0,0,var(--un-text-opacity));}
|
||||||
|
@ -278,22 +316,25 @@
|
||||||
.shrink-0{flex-shrink:0;}
|
.shrink-0{flex-shrink:0;}
|
||||||
.flex-wrap{flex-wrap:wrap;}
|
.flex-wrap{flex-wrap:wrap;}
|
||||||
.gap-2{gap:0.5rem;}
|
.gap-2{gap:0.5rem;}
|
||||||
|
.gap-3{gap:0.75rem;}
|
||||||
.gap-x-2{column-gap:0.5rem;}
|
.gap-x-2{column-gap:0.5rem;}
|
||||||
.gap-x-4{column-gap:1rem;}
|
.gap-x-4{column-gap:1rem;}
|
||||||
.gap-x-6{column-gap:1.5rem;}
|
|
||||||
.gap-x-8{column-gap:2rem;}
|
.gap-x-8{column-gap:2rem;}
|
||||||
.gap-y-2{row-gap:0.5rem;}
|
.gap-y-2{row-gap:0.5rem;}
|
||||||
.sticky{position:sticky;}
|
.sticky{position:sticky;}
|
||||||
.static{position:static;}
|
.static{position:static;}
|
||||||
.max-w-20rem{max-width:20rem;}
|
.max-w-20rem{max-width:20rem;}
|
||||||
.max-w-800px{max-width:800px;}
|
.max-w-800px{max-width:800px;}
|
||||||
|
.w-3{width:0.75rem;}
|
||||||
.w-4{width:1rem;}
|
.w-4{width:1rem;}
|
||||||
.w-full{width:100%;}
|
.w-full{width:100%;}
|
||||||
.overflow-x-auto{overflow-x:auto;}
|
.overflow-x-auto{overflow-x:auto;}
|
||||||
|
.justify-between{justify-content:space-between;}
|
||||||
.items-center{align-items:center;}
|
.items-center{align-items:center;}
|
||||||
.top-0{top:0;}
|
.top-0{top:0;}
|
||||||
.z-10{z-index:10;}
|
.z-10{z-index:10;}
|
||||||
.transition-color{transition-property:color;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
|
.transition-color{transition-property:color;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
|
||||||
|
.columns-2{columns:2;}
|
||||||
@media (min-width: 768px){
|
@media (min-width: 768px){
|
||||||
.md\:hidden{display:none;}
|
.md\:hidden{display:none;}
|
||||||
.md\:display-table{display:table;}
|
.md\:display-table{display:table;}
|
||||||
|
|
|
@ -41,3 +41,4 @@
|
||||||
{% include "parts/footer.html" %}
|
{% include "parts/footer.html" %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
{% block top %}{% endblock %}
|
||||||
|
|
|
@ -4,84 +4,18 @@
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="bg-white sticky top-0 py-3 border-b z-10 mb-6">
|
<div class="bg-white sticky top-0 py-3 border-b z-10 mb-6">
|
||||||
<section class="container flex items-center flex-wrap shrink-0 gap-x-6 gap-y-2">
|
{% include "parts/filters.html" %}
|
||||||
<form method="get" class="flex gap-x-2">
|
|
||||||
<input
|
|
||||||
class="text-input w-full max-w-20rem"
|
|
||||||
type="text"
|
|
||||||
id="search"
|
|
||||||
name="search"
|
|
||||||
value="{{ search|e }}"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="button button-md"
|
|
||||||
type="submit"
|
|
||||||
value="Hledat"
|
|
||||||
>
|
|
||||||
Hledat
|
|
||||||
<svg class="w-4 fill-current" viewBox="0 0 24 24">
|
|
||||||
<path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" x-model="expandDiffs" x-bind:checked="expandDiffs" />
|
|
||||||
<p>Expand diffs</p>
|
|
||||||
</label>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="diff-list">
|
||||||
<section class="container mb-8">
|
<section class="container mb-8">
|
||||||
{% for diff in diffs %}
|
{% include "parts/diff_list.html" %}
|
||||||
<article class="card px-5 py-4 mb-4">
|
|
||||||
<p class="flex gap-x-4 gap-y-2 mb-3 flex-wrap shrink-0">
|
|
||||||
<span class="text-sm font-medium">{{ diff.feed_name }}</span>
|
|
||||||
<time class="text-sm font-medium">{{ diff.diff_time }}</time>
|
|
||||||
<a class="action-link" href="{{ diff.article_url }}">
|
|
||||||
<svg class="w-4 fill-current" viewBox="0 0 24 24"><path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z"></path></svg>
|
|
||||||
Display current article
|
|
||||||
</a>
|
|
||||||
<a class="action-link" href="/article/{{ diff.article_id }}">
|
|
||||||
<svg class="w-4 fill-current" viewBox="0 0 24 24"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM13 12H17V14H11V7H13V12Z"></path></svg>
|
|
||||||
Show change history
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="text-lg" x-bind:class="{ hidden: expandDiffs }">{{ diff.diff_html|safe }}</div>
|
|
||||||
<div x-bind:class="{ hidden: !expandDiffs }">
|
|
||||||
<div class="md:hidden">
|
|
||||||
<div class="mb-2">
|
|
||||||
<p class="text-caption">Before</p>
|
|
||||||
<p class="diff-before text-lg">{{ diff.diff_html|safe }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-caption">After</p>
|
|
||||||
<p class="diff-after text-lg">{{ diff.diff_html|safe }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="display-none md:display-table">
|
|
||||||
<tr>
|
|
||||||
<th class="text-caption pr-2">Before</th>
|
|
||||||
<td class="diff-before text-lg">{{ diff.diff_html|safe }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th class="text-caption pr-2">After</th>
|
|
||||||
<td class="diff-after text-lg">{{ diff.diff_html|safe }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{% endfor %}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="container mb-3">
|
<div class="container mb-3">{{ pagination.links }}</div>
|
||||||
{{ pagination.links }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="container text-sm text-gray-500">
|
<div class="container text-sm text-gray-500">{{ pagination.info }}</div>
|
||||||
{{ pagination.info }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
51
view/templates/parts/diff_list.html
Normal file
51
view/templates/parts/diff_list.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% for diff in diffs %}
|
||||||
|
<article class="card px-5 py-4 mb-4">
|
||||||
|
<p class="flex gap-x-4 gap-y-2 mb-3 flex-wrap shrink-0">
|
||||||
|
<span class="text-sm font-medium">{{ diff.feed_name }}</span>
|
||||||
|
<time class="text-sm font-medium">{{ diff.diff_time }}</time>
|
||||||
|
<a class="action-link" href="{{ diff.article_url }}">
|
||||||
|
<svg class="w-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Display current article
|
||||||
|
</a>
|
||||||
|
<a class="action-link" href="/article/{{ diff.article_id }}">
|
||||||
|
<svg class="w-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM13 12H17V14H11V7H13V12Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Show change history
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="text-lg" x-bind:class="{ hidden: expandDiffs }">
|
||||||
|
{{ diff.diff_html|safe }}
|
||||||
|
</div>
|
||||||
|
<div x-bind:class="{ hidden: !expandDiffs }">
|
||||||
|
<div class="md:hidden">
|
||||||
|
<div class="mb-2">
|
||||||
|
<p class="text-caption">Before</p>
|
||||||
|
<p class="diff-before text-lg">{{ diff.diff_html|safe }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-caption">After</p>
|
||||||
|
<p class="diff-after text-lg">{{ diff.diff_html|safe }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="display-none md:display-table">
|
||||||
|
<tr>
|
||||||
|
<th class="text-caption pr-2">Before</th>
|
||||||
|
<td class="diff-before text-lg">{{ diff.diff_html|safe }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-caption pr-2">After</th>
|
||||||
|
<td class="diff-after text-lg">{{ diff.diff_html|safe }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
24
view/templates/parts/feeds_filter_dialog.html
Normal file
24
view/templates/parts/feeds_filter_dialog.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<dialog hx-boost="false" class="dialog" x-ref="feedsDialog">
|
||||||
|
<header class="flex justify-between mb-6 items-center">
|
||||||
|
<p>Filter by feed</p>
|
||||||
|
<button formmethod="dialog" class="button-outline button-md" @click="$refs.feedsDialog.close()">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="columns-2 mb-6">
|
||||||
|
{% for feed in feeds %}
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-3 px-2 py-1.5 hover:bg-gray-100 rounded"
|
||||||
|
>
|
||||||
|
<input type="checkbox" name="feeds[]" value="{{ feed['feed_name'] }}"
|
||||||
|
{{ 'checked' if feed['feed_name'] in selected_feeds }} > {{
|
||||||
|
feed['feed_name'] }}
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="button button-md">
|
||||||
|
Filter diffs
|
||||||
|
</button>
|
||||||
|
</dialog>
|
48
view/templates/parts/filters.html
Normal file
48
view/templates/parts/filters.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<section class="container">
|
||||||
|
<div class="flex items-center flex-wrap shrink-0 gap-x-2 gap-y-2">
|
||||||
|
<form method="get" action="/" class="flex gap-x-2">
|
||||||
|
<input
|
||||||
|
class="text-input w-full max-w-20rem"
|
||||||
|
type="search"
|
||||||
|
id="search"
|
||||||
|
name="search"
|
||||||
|
value="{{ search|e }}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="button button-md" type="submit" value="Hledat">
|
||||||
|
Search
|
||||||
|
<svg class="w-4 fill-current" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="button-outline button-md" @click="$refs.feedsDialog.showModal()">
|
||||||
|
<svg class="w-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 14L4 5V3H20V5L14 14V20L10 22V14Z"></path></svg>
|
||||||
|
Feeds
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% include "parts/feeds_filter_dialog.html" %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<label class="checkbox mx-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-model="expandDiffs"
|
||||||
|
x-bind:checked="expandDiffs"
|
||||||
|
/>
|
||||||
|
<p>Expand diffs</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if selected_feeds|length > 0 %}
|
||||||
|
<div class="text-xs font-medium text-muted flex items-center mt-3">
|
||||||
|
<svg class="w-3 fill-current mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 14L4 5V3H20V5L14 14V20L10 22V14Z"></path></svg>
|
||||||
|
<div>
|
||||||
|
Feeds: <span>{{ ", ".join(selected_feeds) }}</span>
|
||||||
|
<a href="/" class="ml-2">✕ Clear</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
|
@ -31,7 +31,7 @@ const globalCss = (theme: Required<Theme>) => css`
|
||||||
border-color: var(--color-border);
|
border-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
:not(dialog) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,18 +133,36 @@ const globalCss = (theme: Required<Theme>) => css`
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button,
|
||||||
|
.button-outline {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
border-radius: ${theme.borderRadius.sm};
|
border-radius: ${theme.borderRadius.sm};
|
||||||
|
transition: background-color 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
background: ${theme.colors.gray[100]};
|
background: ${theme.colors.gray[100]};
|
||||||
transition: background-color 120ms;
|
|
||||||
color: ${theme.colors.gray[600]};
|
color: ${theme.colors.gray[600]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button:not(:disabled):hover {
|
||||||
|
background: hsl(0 0% 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-outline {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: white;
|
||||||
|
color: ${theme.colors.gray[600]};
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-outline:not(:disabled):hover {
|
||||||
|
background: hsl(0 0% 95%);
|
||||||
|
}
|
||||||
|
|
||||||
.button-md {
|
.button-md {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
|
@ -152,14 +170,10 @@ const globalCss = (theme: Required<Theme>) => css`
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:not(:disabled) {
|
button:not(:disabled) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:not(:disabled):hover {
|
|
||||||
background: hsl(0 0% 90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pagination */
|
/* Pagination */
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
|
@ -234,6 +248,20 @@ const globalCss = (theme: Required<Theme>) => css`
|
||||||
.table-styled thead tr {
|
.table-styled thead tr {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[hx-swap-oob] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
border-radius: ${theme.borderRadius["md"]};
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: ${theme.boxShadow.DEFAULT};
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog::backdrop {
|
||||||
|
background-color: hsl(0 0% 90% / 80%);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,6 +289,7 @@ export default defineConfig({
|
||||||
md: "0.5rem",
|
md: "0.5rem",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
rules: [[/^columns-(\d+)$/, ([_, num]) => ({ columns: num })]],
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
container: "max-w-1200px px-4 mx-auto",
|
container: "max-w-1200px px-4 mx-auto",
|
||||||
"action-link":
|
"action-link":
|
||||||
|
|
Loading…
Reference in a new issue