Rewrite frontend with utility classes

This commit is contained in:
Ondřej Nývlt 2023-08-20 16:22:59 +02:00
parent 8282236c81
commit 9a5cbef311
13 changed files with 2612 additions and 627 deletions

23
view/README.md Normal file
View file

@ -0,0 +1,23 @@
# Headline Frontend
We use [UnoCSS](https://unocss.dev/) to generate utility classes
## Installation
First, install dependencies:
```
npm i
```
For development, run:
```
npm run dev
```
For production build, run:
```
npm run build
```

1935
view/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

11
view/package.json Normal file
View file

@ -0,0 +1,11 @@
{
"scripts": {
"dev": "unocss \"templates/**/*.html\" --watch -o static/main.css",
"build": "unocss \"templates/**/*.html\" -o static/main.css"
},
"devDependencies": {
"@unocss/preset-mini": "^0.55.1",
"@unocss/transformer-variant-group": "^0.55.2",
"unocss": "^0.55.1"
}
}

View file

@ -1,11 +1,4 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2)">
<rect width="256" height="256" rx="40" fill="black"/> <rect width="256" height="256" rx="40" fill="black"/>
<path d="M50 208V48H80V115.438L176 115.438V48L206 48V208H176V140.281H80V208H50Z" fill="white"/> <path d="M50 208V48H80V115.438L176 115.438V48L206 48V208H176V140.281H80V208H50Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1_2">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 257 B

View file

@ -1,535 +1,297 @@
/* Global */ /* layer: preflights */
*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);}
:root {
--border-color: hsl(0 0% 80% / 60%); /* This file is dynamically generated. Do not edit this file. */
--accent-color: hsl(225 90% 50%);
--accent-color-pressed: hsl(225 90% 35%); :root {
--color-muted: hsl(0 0% 50%); --color-border: hsl(0 0% 80% / 60%);
--radius-s: 0.25em; }
--radius-m: 0.5em;
--font-size-m: 1rem; html {
--font-size-s: 0.85rem; box-sizing: border-box;
--font-size-xs: 0.75rem; font-family: system-ui, sans-serif;
--font-size-l: 1.25rem; }
--box-shadow: 0 2px 0 hsl(0 0% 50% / 20%);
} body {
min-height: 100vh;
html { display: flex;
box-sizing: border-box; flex-direction: column;
font-family: system-ui, sans-serif; align-items: stretch;
} line-height: 1.5;
background-color: #f9fafb;
body { }
min-height: 100vh;
display: flex; *,
flex-direction: column; *::before,
align-items: stretch; *::after {
line-height: 1.5; box-sizing: inherit;
background-color: hsl(0 0% 98%); border-width: 0;
} border-style: solid;
border-color: var(--color-border);
*, }
*::before,
*::after { * {
box-sizing: inherit; margin: 0;
} }
* { body {
margin: 0; line-height: 1.5;
} }
body { img,
line-height: 1.5; picture,
} video,
canvas,
img, svg {
picture, display: block;
video, max-width: 100%;
canvas, }
svg {
display: block; input,
max-width: 100%; button,
} textarea,
select {
input, font: inherit;
button, }
textarea,
select { p,
font: inherit; h1,
} h2,
h3,
p, h4,
h1, h5,
h2, h6 {
h3, overflow-wrap: break-word;
h4, font-size: inherit;
h5, font-weight: inherit;
h6 { }
overflow-wrap: break-word;
font-size: inherit; table,
font-weight: inherit; th,
} tr,
td {
table, text-align: inherit;
th, border-collapse: collapse;
tr, }
td {
text-align: inherit; a {
border-collapse: collapse; color: #2563eb;
} font-weight: 500;
text-decoration: none;
a { transition: color 120ms;
color: var(--accent-color); }
font-weight: 500;
text-decoration: none; a:hover {
transition: color 120ms; color: #1e40af;
} }
a:hover { ins {
color: var(--accent-color-pressed); background-color: hsl(120 100% 95%);
} text-decoration-color: hsl(120 50% 75% / 50%);
}
ins {
background-color: hsl(120 100% 95%); del {
text-decoration-color: hsl(120 50% 75% / 50%); background-color: hsl(0 100% 95%);
} text-decoration-color: hsl(0 50% 40% / 50%);
}
del {
background-color: hsl(0 100% 95%); .diff-before ins {
text-decoration-color: hsl(0 50% 40% / 50%); display: none;
} }
.diff-before ins { .diff-after del {
display: none; display: none;
} }
.diff-after del { /* Forms */
display: none;
} .text-input {
border-radius: 0.125rem;
code { border: 1px solid var(--color-border);
font-size: inherit; padding: 0.375rem 0.75rem;
} transition: border-color 120ms, box-shadow 120ms;
}
summary {
cursor: pointer; .text-input:focus {
list-style: none; border-color: #3b82f6;
display: flex; box-shadow: 0 0 0 2px #3b82f6;
align-items: flex-start; outline: none;
gap: 0.5em; }
}
.checkbox {
summary::-webkit-details-marker { font-size: 0.875rem,1.25rem;
display: none; display: flex;
} align-items: center;
color: #6b7280;
summary::before { font-weight: 500;
content: url('data:image/svg+xml,<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13.1714 12.0007L8.22168 7.05093L9.63589 5.63672L15.9999 12.0007L9.63589 18.3646L8.22168 16.9504L13.1714 12.0007Z"></path></svg>'); cursor: pointer;
display: grid; }
place-content: center;
transition: transform 120ms; .checkbox input {
margin-left: -0.25rem; margin-right: 0.5rem;
margin-top: 0.25rem; }
}
.button {
details[open] summary::before { display: inline-flex;
transform: rotate(90deg); align-items: center;
} flex-shrink: 0;
gap: 0.5em;
/* Layout */ border-radius: 0.125rem;
border: 1px solid var(--color-border);
.header { background: #f3f4f6;
padding-top: 1rem; transition: background-color 120ms;
padding-bottom: 1rem; color: #4b5563;
background-color: white; }
}
.button-md {
.header:not(.header-extended) { font-size: 0.9em;
border-bottom: 1px solid var(--border-color); line-height: 1.5rem;
margin-bottom: 2rem; font-weight: 500;
} padding: 0.375rem 0.75rem;
}
.header .container {
display: flex; .button:not(:disabled) {
align-items: center; cursor: pointer;
gap: 2rem; }
}
.button:not(:disabled):hover {
.header nav { background: hsl(0 0% 90%);
display: flex; }
align-items: center;
gap: 1rem; /* Pagination */
}
.pagination {
.header-link-home { list-style-type: none;
color: inherit; padding: 0;
text-decoration: none; display: flex;
} }
.header h1 { .page-link {
font-size: 1.5rem; display: block;
font-weight: 700; padding: 0.5em 1em;
} text-decoration: none;
color: inherit;
.header h1 .del { }
text-decoration: line-through;
text-decoration-thickness: 2px; .page-item {
} border: 1px solid var(--color-border);
background: #f3f4f6;
.header h1 .ins { transition: background-color 120ms;
text-decoration: underline; color: #4b5563;
text-decoration-thickness: 1px; font-weight: 500;
text-decoration-style: wavy; font-size: 0.85em;
} line-height: 1.5rem;
}
.main {
margin-bottom: auto; .page-item a {
} color: inherit;
}
.footer {
margin-top: 4rem; .page-item:not(.active):hover {
padding: 1.5rem 0; background: #e5e7eb;
color: hsl(0 0% 60%); }
font-size: var(--font-size-s);
font-weight: 500; .page-item:first-of-type {
} border-radius: 0.125rem 0 0 0.125rem;
}
.footer a {
color: inherit; .page-item:last-of-type {
} border-radius: 0 0.125rem 0.125rem 0;
}
.footer a:hover {
color: hsl(0 0% 40%); .page-item:not(:last-of-type) {
} border-right-width: 0px;
}
.footer-container {
display: flex; .page-item.active {
align-items: center; background-color: #2563eb;
flex-shrink: 0; color: white;
flex-wrap: wrap; border-color: transparent;
gap: 0.5rem; }
}
.prose p:not(:last-of-type) {
.footer-colophon { margin-bottom: 1rem;
display: flex; }
align-items: center;
gap: 0.5rem; /* Misc components */
}
.card {
.footer-colophon svg { border: 1px solid var(--color-border);
display: block; border-radius: 0.375rem;
fill: currentColor; background-color: white;
transition: fill 120ms; box-shadow: 0 2px 0 #e5e7eb;
} overflow: hidden;
}
.footer-item:not(:last-child)::after {
content: " · "; .table-styled td,
opacity: 0.5; .table-styled th {
margin: 0 0.25rem; padding: 0.5rem 0.5rem;
} }
/* Utils & components */ .table-styled tr:not(:last-child),
.table-styled thead tr {
.container { border-bottom-width: 1px;
max-width: 1200px; }
margin: 0 auto;
padding: 0 1rem; /* layer: shortcuts */
} .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;}
.inline-icon { .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));}
display: inline-block; .text-muted{--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));}
fill: currentColor; .action-link:hover{--un-text-opacity:1;color:rgba(29,78,216,var(--un-text-opacity));}
width: 1.25em; /* layer: default */
} .p-0{padding:0;}
.px-4{padding-left:1rem;padding-right:1rem;}
.text-input { .px-5{padding-left:1.25rem;padding-right:1.25rem;}
border-radius: var(--radius-s); .py-3{padding-top:0.75rem;padding-bottom:0.75rem;}
border: 1px solid var(--border-color); .py-4{padding-top:1rem;padding-bottom:1rem;}
padding: 0.375rem 0.75rem; .py-6{padding-top:1.5rem;padding-bottom:1.5rem;}
font-size: var(--font-size-m); .pr-2{padding-right:0.5rem;}
transition: border-color 120ms, box-shadow 120ms; .my-6{margin-top:1.5rem;margin-bottom:1.5rem;}
} .mb-2{margin-bottom:0.5rem;}
.mb-3{margin-bottom:0.75rem;}
.text-input:focus { .mb-4{margin-bottom:1rem;}
border-color: var(--accent-color); .mb-6{margin-bottom:1.5rem;}
box-shadow: 0 0 0 2px hsl(225 90% 40% / 50%); .mb-8{margin-bottom:2rem;}
outline: none; .mb-auto{margin-bottom:auto;}
} .mt-12{margin-top:3rem;}
.block{display:block;}
.checkbox { .display-none,
font-size: var(--font-size-s); .hidden{display:none;}
display: flex; .bg-white{--un-bg-opacity:1;background-color:rgba(255,255,255,var(--un-bg-opacity));}
align-items: center; .fill-current{fill:currentColor;}
color: var(--color-muted); .border-b{border-bottom-width:1px;}
font-weight: 500; .text-2xl{font-size:1.5rem;line-height:2rem;}
cursor: pointer; .text-lg{font-size:1.125rem;line-height:1.75rem;}
} .text-sm{font-size:0.875rem;line-height:1.25rem;}
.font-bold{font-weight:700;}
.checkbox input { .font-medium{font-weight:500;}
margin-right: 0.5rem; .text-gray-200{--un-text-opacity:1;color:rgba(229,231,235,var(--un-text-opacity));}
} .text-gray-400{--un-text-opacity:1;color:rgba(156,163,175,var(--un-text-opacity));}
.text-gray-500{--un-text-opacity:1;color:rgba(107,114,128,var(--un-text-opacity));}
.button { .hover\:text-gray-600:hover{--un-text-opacity:1;color:rgba(75,85,99,var(--un-text-opacity));}
display: inline-flex; .text-inherit{color:inherit;}
align-items: center; .flex{display:flex;}
flex-shrink: 0; .shrink-0{flex-shrink:0;}
gap: 0.5em; .flex-wrap{flex-wrap:wrap;}
border-radius: var(--radius-s); .gap-2{gap:0.5rem;}
border: 1px solid var(--border-color); .gap-x-2{column-gap:0.5rem;}
padding: 0.375rem 0.75rem; .gap-x-4{column-gap:1rem;}
background: hsl(0 0% 95%); .gap-x-6{column-gap:1.5rem;}
font-size: var(--font-size-m); .gap-x-8{column-gap:2rem;}
transition: background-color 120ms; .gap-y-2{row-gap:0.5rem;}
color: hsl(0 0% 40%); .sticky{position:sticky;}
font-weight: 500; .static{position:static;}
font-size: 0.9em; .max-w-20rem{max-width:20rem;}
line-height: 1.5rem; .max-w-800px{max-width:800px;}
} .w-4{width:1rem;}
.w-full{width:100%;}
.button:not(:disabled) { .overflow-x-auto{overflow-x:auto;}
cursor: pointer; .items-center{align-items:center;}
} .top-0{top:0;}
.z-10{z-index:10;}
.button:hover { .transition-color{transition-property:color;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
background: hsl(0 0% 90%); @media (min-width: 768px){
} .md\:hidden{display:none;}
.md\:display-table{display:table;}
.card { }
border: 1px solid var(--border-color);
border-radius: var(--radius-m);
background-color: white;
box-shadow: 0 2px 0 hsl(0 0% 50% / 20%);
overflow: hidden;
}
.pagination-container {
overflow-x: auto;
}
.pagination {
list-style-type: none;
padding: 0;
display: flex;
margin-bottom: 1rem;
}
.page-link {
display: block;
padding: 0.5em 1em;
text-decoration: none;
color: inherit;
}
.page-item {
border: 1px solid var(--border-color);
background: hsl(0 0% 95%);
transition: background-color 120ms;
color: hsl(0 0% 40%);
font-weight: 500;
font-size: 0.85em;
line-height: 1.5rem;
}
.page-item:not(.active):hover {
background: hsl(0 0% 90%);
}
.page-item:first-of-type {
border-radius: var(--radius-s) 0 0 var(--radius-s);
}
.page-item:last-of-type {
border-radius: 0 var(--radius-s) var(--radius-s) 0;
}
.page-item:not(:last-of-type) {
border-right-width: 0px;
}
.page-item.active {
background-color: var(--accent-color);
color: white;
border-color: transparent;
}
.pagination-page-info {
color: var(--color-muted);
}
.prose p:not(:last-of-type) {
margin-bottom: 1rem;
}
/* Typography utils */
.text-caption {
font-size: var(--font-size-s);
color: var(--color-muted);
font-weight: 500;
}
.table-head {
font-size: var(--font-size-s);
font-weight: 500;
color: var(--color-muted);
vertical-align: top;
position: relative;
top: 0.3em;
padding-right: 0.75rem;
}
/* Index */
.home-filters {
margin-bottom: 2rem;
position: sticky;
top: 0;
background-color: white;
padding: 0.75rem 0;
border-bottom: 1px solid var(--border-color);
z-index: 10;
}
.home-filters .container {
display: flex;
align-items: center;
flex-wrap: wrap;
flex-shrink: 0;
column-gap: 1.5rem;
row-gap: 0.5rem;
}
.home-filters form {
display: flex;
gap: 0.5rem;
}
.home-filters [name="search"] {
width: 100%;
max-width: 20rem;
}
.home-diff-list {
margin-bottom: 2rem;
}
.changeset-card {
padding: 1rem 1.5rem;
margin-bottom: 1rem;
}
.changeset-card-actions {
display: flex;
column-gap: 1rem;
row-gap: 0.5rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
}
.changeset-card-feed-name,
.changeset-card-time,
.changeset-card-action {
font-weight: 500;
font-size: var(--font-size-s);
flex-shrink: 0;
}
.changeset-card-action {
display: inline-flex;
align-items: center;
gap: 0.5em;
font-weight: 500;
color: var(--color-muted);
text-decoration: none;
transition: color 200ms;
}
.changeset-card-title {
font-size: var(--font-size-l);
}
.changeset-card-diff-s > :not(:last-child) {
margin-bottom: 0.5rem;
}
.changeset-card-diff-m {
display: none;
}
@media screen and (min-width: 800px) {
.changeset-card-diff-s {
display: none;
}
.changeset-card-diff-m {
display: block;
}
}
/* Article detail */
.article-detail h1 {
margin-bottom: 1rem;
font-size: var(--font-size-l);
}
.page-heading {
font-size: var(--font-size-l);
margin-bottom: 2rem;
}
.article-detail-diffs-s .title {
margin-bottom: 1rem;
}
.article-detail-diffs-s .diff {
padding: 0.75rem 1rem;
}
.article-detail-diffs-s .diff-before {
margin-bottom: 0.5rem;
}
.article-detail-diffs-s .diff:not(:last-of-type) {
border-bottom: 1px solid var(--border-color);
}
.article-detail-diffs-l {
display: none;
}
@media screen and (min-width: 800px) {
.article-detail-diffs-s {
display: none;
}
.article-detail-diffs-l {
display: table;
}
}
.article-detail-diffs-l th,
.article-detail-diffs-l td {
padding: 0.5rem 0.75rem;
}
.article-detail-diffs-l td:last-child {
width: 100%;
}
.article-detail-diffs-l th {
font-size: var(--font-size-xs);
color: var(--color-muted);
font-weight: 500;
}
.article-detail-diffs-l tr:not(:last-of-type) {
border-bottom: 1px solid var(--border-color);
}
/* About */
.page-about-prose {
font-size: var(--font-size-l);
max-width: 800px;
}

View file

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block body %} {% block body %}
<div class="container"> <div class="container my-6">
<div class="prose page-about-prose"> <div class="prose max-w-800px text-lg">
<p> <p>
Headline is monitoring rss feeds of czech news websites for changes in Headline is monitoring RSS feeds of czech news websites for changes in
article headlines. Just because it might be interesting. You can read more about it on <a href="https://www.zive.cz/clanky/mrknete-novinarum-pod-ruce-tenhle-web-vam-ukaze-jak-se-titulek-clanku-zmeni-treba-petkrat-za-hodinu/sc-3-a-223150/default.aspx">zive.cz</a> or <a href="https://a2larm.cz/2022/10/co-je-psano-neni-dano-online-media-meni-nazvy-clanku-i-nekolikrat-za-den/">a2larm.cz</a>. article headlines. Just because it might be interesting. You can read more about it on <a href="https://www.zive.cz/clanky/mrknete-novinarum-pod-ruce-tenhle-web-vam-ukaze-jak-se-titulek-clanku-zmeni-treba-petkrat-za-hodinu/sc-3-a-223150/default.aspx">zive.cz</a> or <a href="https://a2larm.cz/2022/10/co-je-psano-neni-dano-online-media-meni-nazvy-clanku-i-nekolikrat-za-den/">a2larm.cz</a>.
</p> </p>

View file

@ -1,41 +1,42 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block body %} {% block body %}
<div class="container article-detail"> <div class="container my-6">
<h1> <h1 class="text-lg mb-6">
Diffs for the article at Diffs for the article at
<a href="{{ article_url }}">{{ article_url|truncate(50) }}</a> <a href="{{ article_url }}">{{ article_url|truncate(50) }}</a>
</h1> </h1>
<div class="card"> <div class="card">
<div class="article-detail-diffs-s"> <ul class="md:hidden list-none p-0">
{% for diff in diffs %} {% for diff in diffs %}
<div class="diff"> <div class="px-4 py-3 border-b">
<p class="text-caption title">{{ diff.diff_time }}</p> <p class="text-caption mb-2">{{ diff.diff_time }}</p>
<div> <div class="mb-2">
<p class="text-caption">Before</p> <p class="text-caption mb-2">Before</p>
<p class="diff-before">{{ diff.diff_html|safe }}</p> <p class="diff-before">{{ diff.diff_html|safe }}</p>
</div> </div>
<div> <div>
<p class="text-caption">After</p> <p class="text-caption mb-2">After</p>
<p class="diff-after">{{ diff.diff_html|safe }}</p> <p class="diff-after">{{ diff.diff_html|safe }}</p>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </ul>
<table class="article-detail-diffs-l"> <table class="hidden md:display-table table-styled">
{% for diff in diffs %} {% for diff in diffs %}
<tr> <tr>
<th>{{ diff.diff_time }}</th> <th class="text-caption">{{ diff.diff_time }}</th>
<td> <td class="p-0 w-full">
<table> <table class="table-styled">
<tr> <tr>
<th class="text-caption table-head">Before</th> <th class="text-caption">Before</th>
<td class="diff-before">{{ diff.diff_html|safe }}</td> <td class="diff-before w-full">{{ diff.diff_html|safe }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-caption table-head">After</th> <th class="text-caption">After</th>
<td class="diff-after">{{ diff.diff_html|safe }}</td> <td class="diff-after w-full">{{ diff.diff_html|safe }}</td>
</tr> </tr>
</table> </table>
</td> </td>

View file

@ -25,20 +25,20 @@
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body hx-boost="true"> <body hx-boost="true">
<header class="header {% block header_class %}{% endblock %}"> <header class="py-4 bg-white {% block header_class %}border-b{% endblock %}">
<div class="container"> <div class="container flex flex-wrap items-center gap-x-8 gap-y-2">
<a class="header-link-home" href="/"> <a href="/" class="text-inherit">
<h1> <h1 class="logo text-2xl font-bold">
<span class="del"> Head</span><span class="ins">line</span> <span class="del">Head</span><span class="ins">line</span>
</h1> </h1>
</a> </a>
<nav> <nav class="flex flex-wrap items-center gap-x-4 gap-y-2">
<a href="/about">About</a> <a href="/about">About</a>
<a href="/feeds">Feeds</a> <a href="/feeds">Feeds</a>
</nav> </nav>
</div> </div>
</header> </header>
<main class="main"> <main class="mb-auto">
{% block body %}{% endblock %} {% block body %}{% endblock %}
</main> </main>
{% include "parts/footer.html" %} {% include "parts/footer.html" %}

View file

@ -1,14 +1,14 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block body %} {% block body %}
<div class="container"> <div class="container my-6">
<div class="table-responsive"> <div class="">
<table class="table table-hover"> <table class="table-styled w-full">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th class="text-caption">Name</th>
<th>RSS/Atom URL</th> <th class="text-caption">RSS/Atom URL</th>
<th>Unique tag</th> <th class="text-caption">Unique tag</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -17,9 +17,8 @@
<td>{{ feed.feed_name }}</td> <td>{{ feed.feed_name }}</td>
<td>{{ feed.rss_source | urlize(target="_blank") }}</td> <td>{{ feed.rss_source | urlize(target="_blank") }}</td>
<td>{{ feed.unique_tag }}</td> <td>{{ feed.unique_tag }}</td>
{% endfor %}
</tr> </tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -1,25 +1,25 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block header_class %}header-extended{% endblock header_class %} {% block header_class %}{% endblock header_class %}
{% block body %} {% block body %}
<div class="home-filters"> <div class="bg-white sticky top-0 py-3 border-b z-10 mb-6">
<div class="container"> <div class="container flex items-center flex-wrap shrink-0 gap-x-6 gap-y-2">
<form method="get"> <form method="get" class="flex gap-x-2">
<input <input
class="text-input" class="text-input w-full max-w-20rem"
type="text" type="text"
id="search" id="search"
name="search" name="search"
value="{{ search|e }}" value="{{ search|e }}"
/> />
<button <button
class="button" class="button button-md"
type="submit" type="submit"
value="Hledat" value="Hledat"
> >
Hledat Hledat
<svg class="inline-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <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> <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> </svg>
</button> </button>
@ -42,55 +42,57 @@
</div> </div>
</div> </div>
<div class="container home-diff-list" id="diff-list"> <div class="container mb-8" id="diff-list">
{% for diff in diffs %} {% for diff in diffs %}
<article class="card changeset-card"> <article class="card px-5 py-4 mb-4">
<p class="changeset-card-actions"> <p class="flex gap-x-4 gap-y-2 mb-3 flex-wrap shrink-0">
<span class="changeset-card-feed-name">{{ diff.feed_name }}</span> <span class="text-sm font-medium">{{ diff.feed_name }}</span>
<time class="changeset-card-time">{{ diff.diff_time }}</time> <time class="text-sm font-medium">{{ diff.diff_time }}</time>
<a class="changeset-card-action" href="{{ diff.article_url }}"> <a class="action-link" href="{{ diff.article_url }}">
<svg class="inline-icon" xmlns="http://www.w3.org/2000/svg" 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> <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 Display current article
</a> </a>
<a class="changeset-card-action" href="/article/{{ diff.article_id }}"> <a class="action-link" href="/article/{{ diff.article_id }}">
<svg class="inline-icon" xmlns="http://www.w3.org/2000/svg" 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> <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 Show change history
</a> </a>
</p> </p>
{% if expand_diffs %} {% if expand_diffs %}
<div class="changeset-card-diff-s"> <div class="md:hidden">
<div> <div class="mb-2">
<div class="text-caption">Before</div> <div class="text-sm text-muted">Before</div>
<div class="diff-before changeset-card-title">{{ diff.diff_html|safe }}</div> <div class="diff-before text-lg">{{ diff.diff_html|safe }}</div>
</div> </div>
<div> <div>
<div class="text-caption">After</div> <div class="text-sm text-muted">After</div>
<div class="diff-after changeset-card-title">{{ diff.diff_html|safe }}</div> <div class="diff-after text-lg">{{ diff.diff_html|safe }}</div>
</div> </div>
</div> </div>
<table class="changeset-card-diff-m"> <table class="display-none md:display-table">
<tr> <tr>
<th class="text-caption table-head">Before</th> <th class="text-caption pr-2">Before</th>
<td class="diff-before changeset-card-title">{{ diff.diff_html|safe }}</td> <td class="diff-before text-lg">{{ diff.diff_html|safe }}</td>
</tr> </tr>
<tr> <tr>
<th class="text-caption table-head">After</th> <th class="text-caption pr-2">After</th>
<td class="diff-after changeset-card-title">{{ diff.diff_html|safe }}</td> <td class="diff-after text-lg">{{ diff.diff_html|safe }}</td>
</tr> </tr>
</table> </table>
{% else %} {% else %}
<div class="changeset-card-title">{{ diff.diff_html|safe }}</div> <div class="text-lg">{{ diff.diff_html|safe }}</div>
{% endif %} {% endif %}
</article> </article>
{% endfor %} {% endfor %}
</div> </div>
<div class="container pagination-container"> <div class="overflow-x-auto">
{{ pagination.links }} <div class="container mb-3">
{{ pagination.links }}
</div>
</div> </div>
<div class="container"> <div class="container text-sm text-gray-500">
{{ pagination.info }} {{ pagination.info }}
</div> </div>
{% endblock body %} {% endblock body %}

View file

@ -1,9 +1,9 @@
<footer class="footer"> <footer class="mt-12 py-6 text-gray-200 text-sm font-medium">
<div class="container footer-container"> <div class="container flex flex-wrap items-center shrink-0 gap-x-2 gap-y-2">
<span class="footer-item footer-colophon"> <span class="flex items-center gap-2 text-gray-400">
<span>created by</span> <span>created by</span>
<a href="https://nolog.cz"> <a href="https://nolog.cz" class="text-gray-400 hover:text-gray-600">
<svg class="footer-nologo" width="120" viewBox="0 0 400 60" fill-rule="evenodd"> <svg class="block fill-current transition-color" width="120" viewBox="0 0 400 60" fill-rule="evenodd">
<title>NoLog</title> <title>NoLog</title>
<path d="M60,0l-60,0l0,60l20,0l0,-41l20,0l-0,41l20,0l-0,-60Z"></path> <path d="M60,0l-60,0l0,60l20,0l0,-41l20,0l-0,41l20,0l-0,-60Z"></path>
<path d="M140,60l50,0l-0,-19l-30,0l0,-41l-20,0l0,60Z"></path> <path d="M140,60l50,0l-0,-19l-30,0l0,-41l-20,0l0,60Z"></path>
@ -14,6 +14,7 @@
</svg> </svg>
</a> </a>
</span> </span>
<a class="footer-item" href="https://git.nolog.cz/mdivecky/headline">Source code</a> <span> · </span>
<a class="text-gray-400 hover:text-gray-600" href="https://git.nolog.cz/mdivecky/headline">Source code</a>
</div> </div>
</footer> </footer>

View file

@ -1,13 +0,0 @@
<header class="header">
<div class="container">
<a href="/">
<h1>
<span class="del"> Head</span><span class="ins">line</span>
</h1>
</a>
<nav>
<a href="/about">About</a>
<a href="/feeds">Feeds</a>
</nav>
</div>
</header>

271
view/uno.config.ts Normal file
View file

@ -0,0 +1,271 @@
import { defineConfig } from "unocss";
import presetMini, { theme, Theme } from "@unocss/preset-mini";
const globalCss = (theme: Required<Theme>) => css`
/* This file is dynamically generated. Do not edit this file. */
:root {
--color-border: hsl(0 0% 80% / 60%);
}
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: stretch;
line-height: 1.5;
background-color: ${theme.colors.gray[50]};
}
*,
*::before,
*::after {
box-sizing: inherit;
border-width: 0;
border-style: solid;
border-color: var(--color-border);
}
* {
margin: 0;
}
body {
line-height: 1.5;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
font-size: inherit;
font-weight: inherit;
}
table,
th,
tr,
td {
text-align: inherit;
border-collapse: collapse;
}
a {
color: ${theme.colors.accent[600]};
font-weight: 500;
text-decoration: none;
transition: color 120ms;
}
a:hover {
color: ${theme.colors.accent[800]};
}
ins {
background-color: hsl(120 100% 95%);
text-decoration-color: hsl(120 50% 75% / 50%);
}
del {
background-color: hsl(0 100% 95%);
text-decoration-color: hsl(0 50% 40% / 50%);
}
.diff-before ins {
display: none;
}
.diff-after del {
display: none;
}
/* Forms */
.text-input {
border-radius: ${theme.borderRadius.sm};
border: 1px solid var(--color-border);
padding: 0.375rem 0.75rem;
transition: border-color 120ms, box-shadow 120ms;
}
.text-input:focus {
border-color: ${theme.colors.accent[500]};
box-shadow: 0 0 0 2px ${theme.colors.accent[500]};
outline: none;
}
.checkbox {
font-size: ${theme.fontSize.sm};
display: flex;
align-items: center;
color: ${theme.colors.gray[500]};
font-weight: 500;
cursor: pointer;
}
.checkbox input {
margin-right: 0.5rem;
}
.button {
display: inline-flex;
align-items: center;
flex-shrink: 0;
gap: 0.5em;
border-radius: ${theme.borderRadius.sm};
border: 1px solid var(--color-border);
background: ${theme.colors.gray[100]};
transition: background-color 120ms;
color: ${theme.colors.gray[600]};
}
.button-md {
font-size: 0.9em;
line-height: 1.5rem;
font-weight: 500;
padding: 0.375rem 0.75rem;
}
.button:not(:disabled) {
cursor: pointer;
}
.button:not(:disabled):hover {
background: hsl(0 0% 90%);
}
/* Pagination */
.pagination {
list-style-type: none;
padding: 0;
display: flex;
}
.page-link {
display: block;
padding: 0.5em 1em;
text-decoration: none;
color: inherit;
}
.page-item {
border: 1px solid var(--color-border);
background: ${theme.colors.gray[100]};
transition: background-color 120ms;
color: ${theme.colors.gray[600]};
font-weight: 500;
font-size: 0.85em;
line-height: 1.5rem;
}
.page-item a {
color: inherit;
}
.page-item:not(.active):hover {
background: ${theme.colors.gray[200]};
}
.page-item:first-of-type {
border-radius: ${theme.borderRadius.sm} 0 0 ${theme.borderRadius.sm};
}
.page-item:last-of-type {
border-radius: 0 ${theme.borderRadius.sm} ${theme.borderRadius.sm} 0;
}
.page-item:not(:last-of-type) {
border-right-width: 0px;
}
.page-item.active {
background-color: ${theme.colors.accent[600]};
color: white;
border-color: transparent;
}
.prose p:not(:last-of-type) {
margin-bottom: 1rem;
}
/* Misc components */
.card {
border: 1px solid var(--color-border);
border-radius: ${theme.borderRadius.md};
background-color: white;
box-shadow: 0 2px 0 ${theme.colors.gray[200]};
overflow: hidden;
}
.table-styled td,
.table-styled th {
padding: 0.5rem 0.5rem;
}
.table-styled tr:not(:last-child),
.table-styled thead tr {
border-bottom-width: 1px;
}
`;
/**
* No-op tag function for CSS tagged templates, which works only as a hint for IDE for CSS syntax highlighting.
*/
function css(strings: TemplateStringsArray, ...exprs: Array<unknown>): string {
let result = strings[0];
for (let i = 1, l = strings.length; i < l; i++) {
result += exprs[i - 1];
result += strings[i];
}
return result;
}
export default defineConfig({
presets: [presetMini()],
theme: {
colors: {
accent: theme.colors.blue,
},
},
shortcuts: {
container: "max-w-1200px px-4 mx-auto",
"action-link":
"text-(sm gray-500) font-medium inline-flex items-center gap-1.5 hover:text-accent-700",
"text-muted": "text-gray-500",
"text-caption": "text-(sm gray-500) font-medium",
},
preflights: [
{
getCSS: (ctx) => globalCss(ctx.theme as Required<Theme>),
},
],
});