From a31c218784687bb67d773b647d38d8685bf0564d Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Sat, 26 Sep 2026 16:20:50 +0100 Subject: [PATCH] Hide draft articles from the front page and mark drafts when viewed directly Add an Article::published() scope and use it on the front page (which previously showed drafts), the /blog listing and the feeds. Draft articles are still reachable at their URL, but now show a draft notice, a "[Draft]" title prefix and a noindex robots meta tag. Co-Authored-By: Claude Opus 5.5 --- app/Http/Controllers/ArticlesController.php | 2 +- app/Http/Controllers/FeedsController.php | 4 +-- app/Http/Controllers/FrontPageController.php | 2 +- app/Models/Article.php | 8 +++++ public/assets/css/app.css | 2 +- public/assets/css/app.css.br | Bin 1792 -> 1828 bytes public/assets/css/app.css.map | 2 +- public/assets/css/app.css.zst | Bin 2117 -> 2158 bytes resources/css/layout.css | 13 ++++++++ resources/views/articles/show.blade.php | 13 +++++++- resources/views/master.blade.php | 1 + tests/Feature/ArticlesTest.php | 33 +++++++++++++++++++ tests/Feature/FrontPageTest.php | 11 +++++++ 13 files changed, 84 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/ArticlesController.php b/app/Http/Controllers/ArticlesController.php index ff28fd50..b2da783f 100644 --- a/app/Http/Controllers/ArticlesController.php +++ b/app/Http/Controllers/ArticlesController.php @@ -17,7 +17,7 @@ class ArticlesController extends Controller */ public function index(?int $year = null, ?int $month = null): View { - $articles = Article::where('published', '1') + $articles = Article::published() ->date($year, $month) ->orderBy('updated_at', 'desc') ->simplePaginate(5); diff --git a/app/Http/Controllers/FeedsController.php b/app/Http/Controllers/FeedsController.php index 9aeb4abb..0e312882 100644 --- a/app/Http/Controllers/FeedsController.php +++ b/app/Http/Controllers/FeedsController.php @@ -15,7 +15,7 @@ class FeedsController extends Controller */ public function blogJson(): JsonResponse { - $articles = Article::where('published', '1')->latest('updated_at')->take(20)->get(); + $articles = Article::published()->latest('updated_at')->take(20)->get(); $data = [ 'version' => 'https://jsonfeed.org/version/1.1', 'title' => 'The JSON Feed for '.config('user.display_name').'’s blog', @@ -89,7 +89,7 @@ class FeedsController extends Controller */ public function blogJf2(): JsonResponse { - $articles = Article::where('published', '1')->latest('updated_at')->take(20)->get(); + $articles = Article::published()->latest('updated_at')->take(20)->get(); $items = []; foreach ($articles as $article) { $items[] = [ diff --git a/app/Http/Controllers/FrontPageController.php b/app/Http/Controllers/FrontPageController.php index 19537663..47833f45 100644 --- a/app/Http/Controllers/FrontPageController.php +++ b/app/Http/Controllers/FrontPageController.php @@ -26,7 +26,7 @@ class FrontPageController extends Controller ->withCount(['webmentions AS reposts' => function ($query) { $query->where('type', 'repost-of'); }])->get(); - $articles = Article::latest()->get(); + $articles = Article::published()->latest()->get(); $bookmarks = Bookmark::latest()->with('tags')->get(); $likes = Like::latest()->get(); diff --git a/app/Models/Article.php b/app/Models/Article.php index 330ff03e..6fccfc9e 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -101,6 +101,14 @@ class Article extends Model ); } + /** + * Scope a query to only include published (non-draft) articles. + */ + public function scopePublished(Builder $query): Builder + { + return $query->where('published', 1); + } + /** * Scope a query to only include articles from a particular year/month. */ diff --git a/public/assets/css/app.css b/public/assets/css/app.css index e255209f..cdb6e3d3 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1,2 +1,2 @@ -@layer reset{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin-block-end:0}ul[role=list],ol[role=list]{list-style:none}body{line-height:1.5}h1,h2,h3,h4,button,input,label{line-height:1.1}h1,h2,h3,h4{text-wrap:balance}a:not([class]){text-decoration-skip-ink:auto;color:currentColor}img,picture{max-width:100%;height:auto;display:block}input,button,textarea,select{font-family:inherit;font-size:inherit}textarea:not([rows]){min-height:10em}:target{scroll-margin-block:5ex}@media (prefers-reduced-motion){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}}}@layer base{:root{color-scheme:light dark;--primary-hue:307;--clr-text:light-dark(oklch(5% .1 var(--primary-hue)),oklch(100% .1 var(--primary-hue)));--clr-background:light-dark(oklch(100% .1 var(--primary-hue)),oklch(15% .15 var(--primary-hue)));--clr-border:light-dark(oklch(90% .2 var(--primary-hue)),oklch(25% .1 var(--primary-hue)));--clr-snow-fall:light-dark(oklch(25% .15 var(--primary-hue)),oklch(85% .2 var(--primary-hue)))}body{background-color:var(--clr-background);color:var(--clr-text)}:root{--border-radius:8px}html{font-size:125%}body{grid-template-rows:min-content 1fr min-content;grid-template-columns:1fr min(80ch,80vw) 1fr;padding-inline:4vw;display:grid;&>header{grid-column:1/-1}&>main{grid-column:1/-1;grid-template-columns:subgrid;display:grid;&>*{grid-column:2/3}&>.full-bleed{grid-column:1/-1}}&>footer{grid-column:1/-1;margin-block-start:2ex;& search form{flex-direction:row;align-items:center;gap:1ex;display:flex;& input{min-width:0}}}}.h-feed{flex-direction:column;gap:2ex;display:flex}}@layer components{.h-entry{& pre{padding:1rem}}body>header{grid-template-rows:auto auto;grid-template-columns:auto 1fr auto;align-items:baseline;gap:1rem;display:grid;& a{text-decoration:none}& h1{text-wrap:nowrap;margin:0}& nav{flex-direction:row;gap:.5rem;display:flex;@media screen and (width<=1100px){flex-wrap:wrap;grid-area:2/1/auto/span 3}}}.note{border:1px solid var(--clr-border);border-radius:var(--border-radius);flex-direction:column;padding:1ex 2ex;display:flex;& .e-content{&>p:first-child{margin-block-start:0}& .u-photo{width:100%;height:auto}}& .syndication-links{flex-direction:row;gap:1ex;display:flex;& svg{border-radius:4px;width:auto;height:1lh}}}.reply-to{border:1px solid var(--clr-border);border-top-left-radius:var(--border-radius);border-top-right-radius:var(--border-radius);border-bottom:none;margin-inline:2ex;padding-inline:1ex;font-size:85%}.pagination{flex-direction:row;justify-content:center;align-items:center;display:flex;& div{flex-direction:row;gap:1ex;display:flex}}#theme-selector{background-color:color-mix(in oklch, var(--clr-border) 40%, transparent);border-radius:999px;justify-self:start;align-items:center;gap:.25rem;padding:.25rem;display:flex;& button{appearance:none;color:inherit;background:0 0;border:none;border-radius:999px;place-items:center;padding:.375rem;transition:background-color .2s,color .2s;display:grid;&:hover{cursor:pointer}&:focus-visible{outline:2px solid var(--clr-text);outline-offset:2px}& svg{fill:currentColor;width:1.5rem;height:1.5rem}@media (hover:hover){&:hover{background-color:color-mix(in oklch, var(--clr-border) 70%, transparent)}&[aria-pressed=true]:hover{background-color:var(--clr-text)}}&[aria-pressed=true]{background-color:var(--clr-text);color:var(--clr-background)}}}.sr-only{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}::view-transition-group(changing-theme){animation-duration:1.25s}::view-transition-new(changing-theme),::view-transition-old(changing-theme){mix-blend-mode:normal}::view-transition-new(changing-theme){animation-name:reveal}::view-transition-old(changing-theme){animation:none}@keyframes reveal{0%{clip-path:polygon(-30% 0,-30% 0,-15% 100%,-10% 115%)}to{clip-path:polygon(-30% 0,130% 0,115% 100%,-10% 115%)}}.token-list-wrapper{border:1px solid var(--clr-border);background:light-dark(oklch(99% .02 var(--primary-hue)),oklch(22% .05 var(--primary-hue)));border-radius:16px;margin-block-start:1em;overflow:auto hidden}.token-list{table-layout:fixed;border-collapse:collapse;width:100%;min-width:640px;& th,& td{text-align:left;border-bottom:1px solid var(--clr-border);vertical-align:middle;padding:.9em 1.2em}& th{text-transform:uppercase;letter-spacing:.08em;opacity:.7;font-size:.8em;font-weight:700}& tr.is-revoked{opacity:.6}& a{word-break:break-all}& th:first-child,& td:first-child{width:31%}& th:nth-child(2),& td:nth-child(2){width:24%}& th:nth-child(3),& td:nth-child(3){width:15%}& th:nth-child(4),& td:nth-child(4){width:16%}& th:nth-child(5),& td:nth-child(5){text-align:right;width:14%}& tr:last-child td{border-bottom:none}}.scope-chips{flex-wrap:wrap;gap:.4em;display:flex}.scope-chip{background:light-dark(oklch(92% .05 var(--primary-hue)),oklch(35% .08 var(--primary-hue)));white-space:nowrap;border-radius:999px;padding:.2em .7em;font-size:.85em;display:inline-block}.badge,.token-list button.revoke{white-space:nowrap;border-radius:999px;align-items:center;gap:.4em;padding:.3em .8em;font-size:.85em;font-weight:600;line-height:1.5;display:inline-flex}.badge{background:0 0;border:1px solid}.badge-active{color:light-dark(oklch(35% .16 145),oklch(75% .16 145));& .dot{background:currentColor;border-radius:50%;width:6px;height:6px}}.badge-revoked{color:var(--clr-text);opacity:.7}.token-list button.revoke{color:light-dark(oklch(30% .15 25),oklch(92% .12 25));cursor:pointer;background:light-dark(oklch(92% .15 25),oklch(30% .12 25));border:1px solid #0000;transition:background-color .15s}.token-list button.revoke:hover{background:light-dark(oklch(80% .2 25),oklch(45% .18 25))}.token-reveal{border:1px solid var(--clr-border);background:light-dark(oklch(96% .08 145),oklch(28% .08 145));border-radius:16px;margin-block-end:1em;padding:1em 1.2em;& input{border:1px solid var(--clr-border);border-radius:8px;width:100%;padding:.5em .7em;font-family:monospace}}.scope-checkboxes{flex-wrap:wrap;align-items:center;gap:1em;display:flex}} +@layer reset{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin-block-end:0}ul[role=list],ol[role=list]{list-style:none}body{line-height:1.5}h1,h2,h3,h4,button,input,label{line-height:1.1}h1,h2,h3,h4{text-wrap:balance}a:not([class]){text-decoration-skip-ink:auto;color:currentColor}img,picture{max-width:100%;height:auto;display:block}input,button,textarea,select{font-family:inherit;font-size:inherit}textarea:not([rows]){min-height:10em}:target{scroll-margin-block:5ex}@media (prefers-reduced-motion){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}}}@layer base{:root{color-scheme:light dark;--primary-hue:307;--clr-text:light-dark(oklch(5% .1 var(--primary-hue)),oklch(100% .1 var(--primary-hue)));--clr-background:light-dark(oklch(100% .1 var(--primary-hue)),oklch(15% .15 var(--primary-hue)));--clr-border:light-dark(oklch(90% .2 var(--primary-hue)),oklch(25% .1 var(--primary-hue)));--clr-snow-fall:light-dark(oklch(25% .15 var(--primary-hue)),oklch(85% .2 var(--primary-hue)))}body{background-color:var(--clr-background);color:var(--clr-text)}:root{--border-radius:8px}html{font-size:125%}body{grid-template-rows:min-content 1fr min-content;grid-template-columns:1fr min(80ch,80vw) 1fr;padding-inline:4vw;display:grid;&>header{grid-column:1/-1}&>main{grid-column:1/-1;grid-template-columns:subgrid;display:grid;&>*{grid-column:2/3}&>.full-bleed{grid-column:1/-1}}&>footer{grid-column:1/-1;margin-block-start:2ex;& search form{flex-direction:row;align-items:center;gap:1ex;display:flex;& input{min-width:0}}}}.h-feed{flex-direction:column;gap:2ex;display:flex}}@layer components{.h-entry{& pre{padding:1rem}}.draft-notice{border:2px dashed var(--clr-border);background:light-dark(oklch(95% .08 85),oklch(30% .08 85));border-radius:8px;margin-block-end:2ex;padding:1ex 1em}body>header{grid-template-rows:auto auto;grid-template-columns:auto 1fr auto;align-items:baseline;gap:1rem;display:grid;& a{text-decoration:none}& h1{text-wrap:nowrap;margin:0}& nav{flex-direction:row;gap:.5rem;display:flex;@media screen and (width<=1100px){flex-wrap:wrap;grid-area:2/1/auto/span 3}}}.note{border:1px solid var(--clr-border);border-radius:var(--border-radius);flex-direction:column;padding:1ex 2ex;display:flex;& .e-content{&>p:first-child{margin-block-start:0}& .u-photo{width:100%;height:auto}}& .syndication-links{flex-direction:row;gap:1ex;display:flex;& svg{border-radius:4px;width:auto;height:1lh}}}.reply-to{border:1px solid var(--clr-border);border-top-left-radius:var(--border-radius);border-top-right-radius:var(--border-radius);border-bottom:none;margin-inline:2ex;padding-inline:1ex;font-size:85%}.pagination{flex-direction:row;justify-content:center;align-items:center;display:flex;& div{flex-direction:row;gap:1ex;display:flex}}#theme-selector{background-color:color-mix(in oklch, var(--clr-border) 40%, transparent);border-radius:999px;justify-self:start;align-items:center;gap:.25rem;padding:.25rem;display:flex;& button{appearance:none;color:inherit;background:0 0;border:none;border-radius:999px;place-items:center;padding:.375rem;transition:background-color .2s,color .2s;display:grid;&:hover{cursor:pointer}&:focus-visible{outline:2px solid var(--clr-text);outline-offset:2px}& svg{fill:currentColor;width:1.5rem;height:1.5rem}@media (hover:hover){&:hover{background-color:color-mix(in oklch, var(--clr-border) 70%, transparent)}&[aria-pressed=true]:hover{background-color:var(--clr-text)}}&[aria-pressed=true]{background-color:var(--clr-text);color:var(--clr-background)}}}.sr-only{clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}::view-transition-group(changing-theme){animation-duration:1.25s}::view-transition-new(changing-theme),::view-transition-old(changing-theme){mix-blend-mode:normal}::view-transition-new(changing-theme){animation-name:reveal}::view-transition-old(changing-theme){animation:none}@keyframes reveal{0%{clip-path:polygon(-30% 0,-30% 0,-15% 100%,-10% 115%)}to{clip-path:polygon(-30% 0,130% 0,115% 100%,-10% 115%)}}.token-list-wrapper{border:1px solid var(--clr-border);background:light-dark(oklch(99% .02 var(--primary-hue)),oklch(22% .05 var(--primary-hue)));border-radius:16px;margin-block-start:1em;overflow:auto hidden}.token-list{table-layout:fixed;border-collapse:collapse;width:100%;min-width:640px;& th,& td{text-align:left;border-bottom:1px solid var(--clr-border);vertical-align:middle;padding:.9em 1.2em}& th{text-transform:uppercase;letter-spacing:.08em;opacity:.7;font-size:.8em;font-weight:700}& tr.is-revoked{opacity:.6}& a{word-break:break-all}& th:first-child,& td:first-child{width:31%}& th:nth-child(2),& td:nth-child(2){width:24%}& th:nth-child(3),& td:nth-child(3){width:15%}& th:nth-child(4),& td:nth-child(4){width:16%}& th:nth-child(5),& td:nth-child(5){text-align:right;width:14%}& tr:last-child td{border-bottom:none}}.scope-chips{flex-wrap:wrap;gap:.4em;display:flex}.scope-chip{background:light-dark(oklch(92% .05 var(--primary-hue)),oklch(35% .08 var(--primary-hue)));white-space:nowrap;border-radius:999px;padding:.2em .7em;font-size:.85em;display:inline-block}.badge,.token-list button.revoke{white-space:nowrap;border-radius:999px;align-items:center;gap:.4em;padding:.3em .8em;font-size:.85em;font-weight:600;line-height:1.5;display:inline-flex}.badge{background:0 0;border:1px solid}.badge-active{color:light-dark(oklch(35% .16 145),oklch(75% .16 145));& .dot{background:currentColor;border-radius:50%;width:6px;height:6px}}.badge-revoked{color:var(--clr-text);opacity:.7}.token-list button.revoke{color:light-dark(oklch(30% .15 25),oklch(92% .12 25));cursor:pointer;background:light-dark(oklch(92% .15 25),oklch(30% .12 25));border:1px solid #0000;transition:background-color .15s}.token-list button.revoke:hover{background:light-dark(oklch(80% .2 25),oklch(45% .18 25))}.token-reveal{border:1px solid var(--clr-border);background:light-dark(oklch(96% .08 145),oklch(28% .08 145));border-radius:16px;margin-block-end:1em;padding:1em 1.2em;& input{border:1px solid var(--clr-border);border-radius:8px;width:100%;padding:.5em .7em;font-family:monospace}}.scope-checkboxes{flex-wrap:wrap;align-items:center;gap:1em;display:flex}} /*# sourceMappingURL=/assets/css/app.css.map */ diff --git a/public/assets/css/app.css.br b/public/assets/css/app.css.br index 200e0d258d986670b66f63ebf5d4d71b53405b73..544eb3957112daadec3232450bf4006f46b3b599 100644 GIT binary patch literal 1828 zcmca8aDpKrFk$!FjFJsfyc;*9#6662Hp*Ukhb8ux=Hk+YlcW_L9reB+-TL-K|I0s_ zp`S!|loxoU&iWUkD5~svo40h==fdMxvhRz(OgOr&ahGNq|KyOZ4`tUbo#wDKh%Mmi z$$#h0#BLU7`ptiLIm4dK#s6azKCDrWUc2kavb_OmWwW{?b{n1vQD@x#=7T%$3)Z=3 zCl;M$XRlpw_iEXjA3xm<+Ldcf-9q19Vmk5S*o0kWA)A{mj*x{}F*_Ct4j_eI& zkvdwr;xvQAx|c_$Z*IPrrI*J4)BB(Ojl;~XY2nTdeU8bwG8`r)ZZ10hihv9{*S=RTi!H|8$%?wM(?y7~1w#hm{KB96)&-FfVgpZ|?*y#MFte^0xZ5_s~# z)6U>`(E=CExBg>jrE6Etnv>(EjzbI^Yzk&A8#;AFNmzn z@^&`vS~g+&foF_I?&`<+Ux}KsV9R9*gCEmYJifkp>8dR=!t^$*u$;PfR$J7S2UWS& zt?us^*)QE>b+1)t*XC)vpSsL;6SjSS>E_PgDTy!nX4c7Auq?6Q9$a0N%Pg_ zu-^}LZ{3~8+L99WdHDmDt^1R6HkPi9DSvyY{K2N6X~xg>GTyXWuPc4nGil$$OS@lR z`@Q|m`^p28HT(ALKA(_qaSp58^mhdYQv~$Cm`P+9Trgkp;n|A4-n)J$$u%!lE-|>3 zAk)1sUA)`mcT$CZ;`A)B`WG=hsnQp}7U{fMv-r)0NY6Q&kM7j;E=w)?dg2k&8sq-? zOTJ8Mc^xh@)h=R=H@{I^~$5#eqYX-lyQjVIa{Yd z%N|W1B_sRF4(&}zQ?|47Z+XI9_548Pk>n*oGdH*#UHd`gZ^*42^NEWms66UXI@*!8 zWSPoY(Ic;$iDctUPmP{7{T6pEEQ(DIaJosAX#<(vNy}wv|mV1rT41vai88!}LF?%Z1 zb;OR`Sea6BX}yydck{EVJxR&;^K=Y~IGSXdcZw!IgZy8ew7XHqYn5N0pe?cJf(XRCR6!owFVr+7xxTyIjzntki-!V9zc*b}2N38XSHm{z8(N23Sot4Yx_#dn&ix<6PI*s1wzFo-$G$_yTK)QVvWR~cRlL0F zg{pDH-p*B(uRmX!x9H+Yrtm92wzkeXBBDK~OZBr_n(Q|>$7zb^PEBE3I^|}%x zq6DkF+{N|pyk#}NmUq!Kv9!DE^fZ4it-X&gZnT%T|J1>^r|{4F8?W0RbZw03wc@+} L@3eU4uZ^+*k`atV literal 1792 zcmcck;Sj?E8K3ROt4(x$ID$W=y-!zGnfdkf-qXn;>(tGEugG&2{?+AFtMH}8e`S8z z%vIN|3)K}kkDS=CE^dd~(MXHzQ2yYp5C7he%Hj;sN@ZAjs`iE_o70B!rF%spzwTXV zduM^xq;%Qn4g2z(YfN5TTE;tb+w@~C2K=}7t(W?(a%Ea=7= z={xJXuX(2uHR3zt7RFAS@NBPk=q&z;JMF^TuX`-ckLH@j_sU>*SlfJ0t(g*B`4PLG zR=GQB?3v#X^Ng$ibe@u!<&Ee=f;aUi{^@MV?7enm=l<3%`NhKTry2xsp0AzV<8ajS zu0`(ZsMM`Vq0`R)D^KS?&ej&o$-Jv=Urs1I`yyVO6 zFsBJ-3ae(63tMg1xge=@!}W*kN{utuQ`R>!rtFc{FAe@8>!Q!uFJ7^Xuj^BGxWw!e zCd{RrS??0>TokyzdCRqC&s!dR8Zj!bemz~1SP<10zD}UqP%%vRkgiw8-Ye{W{r8UC zyr`QkX#TJxZ_86py94*Vm}K%3lZ78nc3ZN0$+wElQ7={t%}zX{;Zy53>8Po`Z_r^p-T{&m-nDtrBE`gCK?HoG~N z)+OwdR_49dO=4^9NHh6Z@x}gXm*vsy&8g`x4qmuF;c#?g`_i*YhZk=zU&L1}-P`gq zx^SacXv%}ymR{*?ocyteb5AYf)ahQ}#IUVh{?MQ0W(oya&wV6o_)o}r9Q{~hRJG2# z=V1J;<=PTq9CaH6!bIZcZTAZ=dmIqm^f2}BR9b7N=AymC?A<3z*At-}FKl-z``!$>DYJf~`FKwf5$xt)--^mL$#2$O*kmKevpnDOVk^U4ok}$RIZX&Z`%cFmLSO0tW`w>&1bmI5v=b9!IAF0?{VR!3V z!F;g?rAxEKW(i3yUR3=|S^tyYvB-yYESmf0S#i{q3ELiZvK22hd1>Hbz$3i$@zg1^mA7`THQ6MQvF7X)g|o#Q z4dS&+a{rw3X1U~YsAJDp2dmeMIT!yr{-!uc_PXdPpP~)1oBgB{XCc~D^$ETbNtydDdNXe=k6)%7wx;4Hb=Zl zyQ7uoxO(Karp#Lce@%8dFZi7ma>SbX>ps1tBBuWineX0jaGPeb*vNm@E4O|jH6f$S z#n#Pt*rjhzu8-OL(afHU<+b|ttCLw2SJm^Devf$9J>6D-udYt!g#pX5NmdKD{5p{E zd{-`iU+1ZRx=O7}rfc)*ZQ~8*^RQ?DtQ?_>9f|?IEl)izBy08xV_6r zdcrXw_N;bR#TD0IM4WJ(eKa>*@k5hWmj9|xhT*HW`dR9KWnf-%`7HkLFLvf>UALRiVqtM5MSh85e$NA~ ziuP>Nbr&m7x(ilc=%4pVg;|(1r0L-IQSPd628mp>h-eoU5)Aenpv;EncZ!$-|?YQ z{dvO-{f<4eMfbZ;HkDW~@m-rvdHa&`t^;3r>tfewY8(}7Jz9AB*|J@)#`x@hP6y?-qg@2x!g_5b`Y Vnfeurf?qQI-ZAUE^Rs;=%m5*JZlVAH diff --git a/public/assets/css/app.css.map b/public/assets/css/app.css.map index 6413bbe9..2a11fb01 100644 --- a/public/assets/css/app.css.map +++ b/public/assets/css/app.css.map @@ -1 +1 @@ -{"version":3,"mappings":"AACA,aCGE,uCAOA,oFASA,8DAMA,4CAMA,qBAKA,+CAMA,8BAMA,gEAMA,qDAQA,mEAOA,qCAKA,gCAKA,gCACE,wGDhFJ,YEAE,oaAwBA,kECxBA,0BAIA,oBAIA,iIAME,0BAIA,mEAKE,oBAIA,gCAKF,iDAIE,yEAME,uBAON,oDHjDF,kBGyDE,SACE,oBC1DF,wHAOE,yBAIA,+BAKA,gDAKE,kCAAqC,2CIrBzC,+HAOE,aACE,qCAIA,mCAMF,6DAKE,gDAQJ,4MH/BA,sFAME,+CENF,0LASE,gLAWE,uBAIA,qEAKA,mDAMA,qBACE,iFAIA,6DAKF,oFAOJ,uIAYA,iEAIA,kGAKA,4DAIA,qDAIA,mIDjFA,iNAWA,mFAME,6GAQA,6FAQA,2BAIA,yBAIA,4CAKA,8CAKA,8CAKA,8CAKA,+DAMA,uCAKF,kDAMA,qMAYA,0LAaA,uCAKA,sEAGE,uEAQF,gDAKA,kNAQA,0FAIA,wKAUE,iHASF","sources":["resources/css/app.css","resources/css/colours.css","resources/css/layout.css","resources/css/header.css","resources/css/reset.css","resources/css/pagination.css","resources/css/admin-tokens.css","resources/css/theme-selector.css","resources/css/notes.css"],"sourcesContent":["/* First thing we need to do is declare our cascade layers */\n@layer reset, base, components;\n\n@import url('reset.css');\n@import url('colours.css');\n@import url('layout.css');\n@import url('header.css');\n@import url('notes.css');\n@import url('pagination.css');\n@import url('theme-selector.css');\n@import url('admin-tokens.css');\n","@layer base {\n :root {\n color-scheme: light dark;\n\n --primary-hue: 307;\n --clr-text: light-dark(\n oklch(5% 0.1 var(--primary-hue)),\n oklch(100% 0.1 var(--primary-hue))\n );\n --clr-background: light-dark(\n oklch(100% 0.1 var(--primary-hue)),\n oklch(15% 0.15 var(--primary-hue))\n );\n --clr-border: light-dark(\n oklch(90% 0.2 var(--primary-hue)),\n oklch(25% 0.1 var(--primary-hue))\n );\n\n /* Adding snow fall colour whilst we are using it */\n --clr-snow-fall: light-dark(\n oklch(25% 0.15 var(--primary-hue)),\n oklch(85% 0.2 var(--primary-hue))\n );\n }\n\n body {\n background-color: var(--clr-background);\n color: var(--clr-text);\n }\n}\n","@layer base {\n :root {\n --border-radius: 8px;\n }\n\n html {\n font-size: 125%;\n }\n\n body {\n display: grid;\n grid-template-columns: 1fr min(80ch, 80vw) 1fr;\n grid-template-rows: min-content 1fr min-content;\n padding-inline: 4vw;\n\n > header {\n grid-column: 1/-1;\n }\n\n > main {\n grid-column: 1/-1;\n display: grid;\n grid-template-columns: subgrid;\n\n > * {\n grid-column: 2/3;\n }\n\n > .full-bleed {\n grid-column: 1/-1;\n }\n }\n\n > footer {\n grid-column: 1/-1;\n margin-block-start: 2ex;\n\n search form {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 1ex;\n\n input {\n min-width: 0;\n }\n }\n }\n }\n\n .h-feed {\n display: flex;\n flex-direction: column;\n gap: 2ex;\n }\n}\n\n@layer components {\n .h-entry {\n pre {\n padding: 1rem;\n }\n }\n}\n\n","@layer components {\n body > header {\n display: grid;\n grid-template-columns: auto 1fr auto;\n grid-template-rows: auto auto;\n align-items: baseline;\n gap: 1rem;\n\n a {\n text-decoration: none;\n }\n\n h1 {\n margin: 0;\n text-wrap: nowrap;\n }\n\n nav {\n display: flex;\n flex-direction: row;\n gap: .5rem;\n\n @media screen and (width <= 1100px) {\n grid-row: 2;\n grid-column: 1 / span 3;\n flex-wrap: wrap;\n }\n }\n }\n}\n","@layer reset {\n /* Sourced from https://piccalil.li/blog/a-more-modern-css-reset/ */\n\n /* Box sizing rules */\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n /* Prevent font size inflation */\n html {\n /* stylelint-disable property-no-vendor-prefix */\n -moz-text-size-adjust: none;\n -webkit-text-size-adjust: none;\n /* stylelint-enable property-no-vendor-prefix */\n text-size-adjust: none;\n }\n\n /* Remove default margin in favour of better control in authored CSS */\n body, h1, h2, h3, h4, p,\n figure, blockquote, dl, dd {\n margin-block-end: 0;\n }\n\n /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */\n ul[role='list'],\n ol[role='list'] {\n list-style: none;\n }\n\n /* Set core body defaults */\n body {\n line-height: 1.5;\n }\n\n /* Set shorter line heights on headings and interactive elements */\n h1, h2, h3, h4,\n button, input, label {\n line-height: 1.1;\n }\n\n /* Balance text wrapping on headings */\n h1, h2,\n h3, h4 {\n text-wrap: balance;\n }\n\n /* A elements that don't have a class get default styles */\n a:not([class]) {\n text-decoration-skip-ink: auto;\n color: currentcolor;\n }\n\n /* Make images easier to work with */\n img,\n picture {\n max-width: 100%;\n height: auto;\n display: block;\n }\n\n /* Inherit fonts for inputs and buttons */\n input, button,\n textarea, select {\n font-family: inherit;\n font-size: inherit;\n }\n\n /* Make sure textareas without a rows attribute are not tiny */\n textarea:not([rows]) {\n min-height: 10em;\n }\n\n /* Anything that has been anchored to should have extra scroll margin */\n :target {\n scroll-margin-block: 5ex;\n }\n\n /* Disable view transition animations for users who don’t want them */\n @media (prefers-reduced-motion) {\n ::view-transition-group(*),\n ::view-transition-old(*),\n ::view-transition-new(*) {\n animation: none !important;\n }\n }\n}\n","@layer components {\n .pagination {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n\n div {\n display: flex;\n flex-direction: row;\n gap: 1ex;\n }\n }\n}\n","@layer components {\n .token-list-wrapper {\n margin-block-start: 1em;\n border: 1px solid var(--clr-border);\n border-radius: 16px;\n overflow: auto hidden;\n background: light-dark(\n oklch(99% 0.02 var(--primary-hue)),\n oklch(22% 0.05 var(--primary-hue))\n );\n }\n\n .token-list {\n width: 100%;\n min-width: 640px;\n table-layout: fixed;\n border-collapse: collapse;\n\n th,\n td {\n text-align: left;\n padding: 0.9em 1.2em;\n border-bottom: 1px solid var(--clr-border);\n vertical-align: middle;\n }\n\n th {\n font-size: 0.8em;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n opacity: 0.7;\n }\n\n tr.is-revoked {\n opacity: 0.6;\n }\n\n a {\n word-break: break-all;\n }\n\n th:nth-child(1),\n td:nth-child(1) {\n width: 31%;\n }\n\n th:nth-child(2),\n td:nth-child(2) {\n width: 24%;\n }\n\n th:nth-child(3),\n td:nth-child(3) {\n width: 15%;\n }\n\n th:nth-child(4),\n td:nth-child(4) {\n width: 16%;\n }\n\n th:nth-child(5),\n td:nth-child(5) {\n width: 14%;\n text-align: right;\n }\n\n tr:last-child td {\n border-bottom: none;\n }\n }\n\n .scope-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n }\n\n .scope-chip {\n display: inline-block;\n padding: 0.2em 0.7em;\n border-radius: 999px;\n background: light-dark(\n oklch(92% 0.05 var(--primary-hue)),\n oklch(35% 0.08 var(--primary-hue))\n );\n font-size: 0.85em;\n white-space: nowrap;\n }\n\n .badge,\n .token-list button.revoke {\n display: inline-flex;\n align-items: center;\n gap: 0.4em;\n padding: 0.3em 0.8em;\n border-radius: 999px;\n font-size: 0.85em;\n font-weight: 600;\n line-height: 1.5;\n white-space: nowrap;\n }\n\n .badge {\n background: transparent;\n border: 1px solid currentcolor;\n }\n\n .badge-active {\n color: light-dark(oklch(35% 0.16 145deg), oklch(75% 0.16 145deg));\n\n .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentcolor;\n }\n }\n\n .badge-revoked {\n color: var(--clr-text);\n opacity: 0.7;\n }\n\n .token-list button.revoke {\n background: light-dark(oklch(92% 0.15 25deg), oklch(30% 0.12 25deg));\n color: light-dark(oklch(30% 0.15 25deg), oklch(92% 0.12 25deg));\n border: 1px solid transparent;\n cursor: pointer;\n transition: background-color 150ms ease;\n }\n\n .token-list button.revoke:hover {\n background: light-dark(oklch(80% 0.2 25deg), oklch(45% 0.18 25deg));\n }\n\n .token-reveal {\n margin-block-end: 1em;\n padding: 1em 1.2em;\n border: 1px solid var(--clr-border);\n border-radius: 16px;\n background: light-dark(\n oklch(96% 0.08 145deg),\n oklch(28% 0.08 145deg)\n );\n\n input {\n width: 100%;\n font-family: monospace;\n padding: 0.5em 0.7em;\n border-radius: 8px;\n border: 1px solid var(--clr-border);\n }\n }\n\n .scope-checkboxes {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 1em;\n }\n}\n","@layer components {\n #theme-selector {\n display: flex;\n justify-self: start;\n align-items: center;\n gap: .25rem;\n padding: .25rem;\n border-radius: 999px;\n background-color: color-mix(in oklch, var(--clr-border) 40%, transparent);\n\n button {\n display: grid;\n place-items: center;\n appearance: none;\n border: none;\n border-radius: 999px;\n padding: .375rem;\n background: transparent;\n color: inherit;\n transition: background-color .2s, color .2s;\n\n &:hover {\n cursor: pointer;\n }\n\n &:focus-visible {\n outline: 2px solid var(--clr-text);\n outline-offset: 2px;\n }\n\n svg {\n width: 1.5rem;\n height: 1.5rem;\n fill: currentcolor;\n }\n\n @media (hover: hover) {\n &:hover {\n background-color: color-mix(in oklch, var(--clr-border) 70%, transparent);\n }\n\n &[aria-pressed=\"true\"]:hover {\n background-color: var(--clr-text);\n }\n }\n\n &[aria-pressed=\"true\"] {\n background-color: var(--clr-text);\n color: var(--clr-background);\n }\n }\n }\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n\n ::view-transition-group(changing-theme) {\n animation-duration: 1.25s;\n }\n\n ::view-transition-new(changing-theme),\n ::view-transition-old(changing-theme) {\n mix-blend-mode: normal;\n }\n\n ::view-transition-new(changing-theme) {\n animation-name: reveal;\n }\n\n ::view-transition-old(changing-theme) {\n animation: none;\n }\n\n @keyframes reveal {\n from {\n clip-path: polygon(-30% 0, -30% 0, -15% 100%, -10% 115%);\n }\n\n to {\n clip-path: polygon(-30% 0, 130% 0, 115% 100%, -10% 115%);\n }\n }\n}\n","@layer components {\n .note {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--clr-border);\n border-radius: var(--border-radius);\n padding: 1ex 2ex;\n\n .e-content {\n > p:first-child {\n margin-block-start: 0;\n }\n\n .u-photo {\n width: 100%;\n height: auto;\n }\n }\n\n .syndication-links {\n display: flex;\n flex-direction: row;\n gap: 1ex;\n\n svg {\n border-radius: 4px;\n width: auto;\n height: 1lh;\n }\n }\n }\n\n .reply-to {\n font-size: 85%;\n margin-inline: 2ex;\n padding-inline: 1ex;\n border: 1px solid var(--clr-border);\n border-bottom: none;\n border-top-left-radius: var(--border-radius);\n border-top-right-radius: var(--border-radius);\n }\n}\n"],"names":[]} \ No newline at end of file +{"version":3,"mappings":"AACA,aCGE,uCAOA,oFASA,8DAMA,4CAMA,qBAKA,+CAMA,8BAMA,gEAMA,qDAQA,mEAOA,qCAKA,gCAKA,gCACE,wGDhFJ,YGAE,oaAwBA,kEDxBA,0BAIA,oBAIA,iIAME,0BAIA,mEAKE,oBAIA,gCAKF,iDAIE,yEAME,uBAON,oDFjDF,kBEyDE,SACE,oBAQF,oKGlEA,wHAOE,yBAIA,+BAKA,gDAKE,kCAAqC,2CGrBzC,+HAOE,aACE,qCAIA,mCAMF,6DAKE,gDAQJ,4MJ/BA,sFAME,+CENF,0LASE,gLAWE,uBAIA,qEAKA,mDAMA,qBACE,iFAIA,6DAKF,oFAOJ,uIAYA,iEAIA,kGAKA,4DAIA,qDAIA,mICjFA,iNAWA,mFAME,6GAQA,6FAQA,2BAIA,yBAIA,4CAKA,8CAKA,8CAKA,8CAKA,+DAMA,uCAKF,kDAMA,qMAYA,0LAaA,uCAKA,sEAGE,uEAQF,gDAKA,kNAQA,0FAIA,wKAUE,iHASF","sources":["resources/css/app.css","resources/css/layout.css","resources/css/colours.css","resources/css/reset.css","resources/css/pagination.css","resources/css/header.css","resources/css/admin-tokens.css","resources/css/theme-selector.css","resources/css/notes.css"],"sourcesContent":["/* First thing we need to do is declare our cascade layers */\n@layer reset, base, components;\n\n@import url('reset.css');\n@import url('colours.css');\n@import url('layout.css');\n@import url('header.css');\n@import url('notes.css');\n@import url('pagination.css');\n@import url('theme-selector.css');\n@import url('admin-tokens.css');\n","@layer base {\n :root {\n --border-radius: 8px;\n }\n\n html {\n font-size: 125%;\n }\n\n body {\n display: grid;\n grid-template-columns: 1fr min(80ch, 80vw) 1fr;\n grid-template-rows: min-content 1fr min-content;\n padding-inline: 4vw;\n\n > header {\n grid-column: 1/-1;\n }\n\n > main {\n grid-column: 1/-1;\n display: grid;\n grid-template-columns: subgrid;\n\n > * {\n grid-column: 2/3;\n }\n\n > .full-bleed {\n grid-column: 1/-1;\n }\n }\n\n > footer {\n grid-column: 1/-1;\n margin-block-start: 2ex;\n\n search form {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 1ex;\n\n input {\n min-width: 0;\n }\n }\n }\n }\n\n .h-feed {\n display: flex;\n flex-direction: column;\n gap: 2ex;\n }\n}\n\n@layer components {\n .h-entry {\n pre {\n padding: 1rem;\n }\n }\n}\n\n\n@layer components {\n .draft-notice {\n margin-block-end: 2ex;\n padding: 1ex 1em;\n border: 2px dashed var(--clr-border);\n border-radius: 8px;\n background: light-dark(\n oklch(95% 0.08 85deg),\n oklch(30% 0.08 85deg)\n );\n }\n}\n","@layer base {\n :root {\n color-scheme: light dark;\n\n --primary-hue: 307;\n --clr-text: light-dark(\n oklch(5% 0.1 var(--primary-hue)),\n oklch(100% 0.1 var(--primary-hue))\n );\n --clr-background: light-dark(\n oklch(100% 0.1 var(--primary-hue)),\n oklch(15% 0.15 var(--primary-hue))\n );\n --clr-border: light-dark(\n oklch(90% 0.2 var(--primary-hue)),\n oklch(25% 0.1 var(--primary-hue))\n );\n\n /* Adding snow fall colour whilst we are using it */\n --clr-snow-fall: light-dark(\n oklch(25% 0.15 var(--primary-hue)),\n oklch(85% 0.2 var(--primary-hue))\n );\n }\n\n body {\n background-color: var(--clr-background);\n color: var(--clr-text);\n }\n}\n","@layer reset {\n /* Sourced from https://piccalil.li/blog/a-more-modern-css-reset/ */\n\n /* Box sizing rules */\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n\n /* Prevent font size inflation */\n html {\n /* stylelint-disable property-no-vendor-prefix */\n -moz-text-size-adjust: none;\n -webkit-text-size-adjust: none;\n /* stylelint-enable property-no-vendor-prefix */\n text-size-adjust: none;\n }\n\n /* Remove default margin in favour of better control in authored CSS */\n body, h1, h2, h3, h4, p,\n figure, blockquote, dl, dd {\n margin-block-end: 0;\n }\n\n /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */\n ul[role='list'],\n ol[role='list'] {\n list-style: none;\n }\n\n /* Set core body defaults */\n body {\n line-height: 1.5;\n }\n\n /* Set shorter line heights on headings and interactive elements */\n h1, h2, h3, h4,\n button, input, label {\n line-height: 1.1;\n }\n\n /* Balance text wrapping on headings */\n h1, h2,\n h3, h4 {\n text-wrap: balance;\n }\n\n /* A elements that don't have a class get default styles */\n a:not([class]) {\n text-decoration-skip-ink: auto;\n color: currentcolor;\n }\n\n /* Make images easier to work with */\n img,\n picture {\n max-width: 100%;\n height: auto;\n display: block;\n }\n\n /* Inherit fonts for inputs and buttons */\n input, button,\n textarea, select {\n font-family: inherit;\n font-size: inherit;\n }\n\n /* Make sure textareas without a rows attribute are not tiny */\n textarea:not([rows]) {\n min-height: 10em;\n }\n\n /* Anything that has been anchored to should have extra scroll margin */\n :target {\n scroll-margin-block: 5ex;\n }\n\n /* Disable view transition animations for users who don’t want them */\n @media (prefers-reduced-motion) {\n ::view-transition-group(*),\n ::view-transition-old(*),\n ::view-transition-new(*) {\n animation: none !important;\n }\n }\n}\n","@layer components {\n .pagination {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n\n div {\n display: flex;\n flex-direction: row;\n gap: 1ex;\n }\n }\n}\n","@layer components {\n body > header {\n display: grid;\n grid-template-columns: auto 1fr auto;\n grid-template-rows: auto auto;\n align-items: baseline;\n gap: 1rem;\n\n a {\n text-decoration: none;\n }\n\n h1 {\n margin: 0;\n text-wrap: nowrap;\n }\n\n nav {\n display: flex;\n flex-direction: row;\n gap: .5rem;\n\n @media screen and (width <= 1100px) {\n grid-row: 2;\n grid-column: 1 / span 3;\n flex-wrap: wrap;\n }\n }\n }\n}\n","@layer components {\n .token-list-wrapper {\n margin-block-start: 1em;\n border: 1px solid var(--clr-border);\n border-radius: 16px;\n overflow: auto hidden;\n background: light-dark(\n oklch(99% 0.02 var(--primary-hue)),\n oklch(22% 0.05 var(--primary-hue))\n );\n }\n\n .token-list {\n width: 100%;\n min-width: 640px;\n table-layout: fixed;\n border-collapse: collapse;\n\n th,\n td {\n text-align: left;\n padding: 0.9em 1.2em;\n border-bottom: 1px solid var(--clr-border);\n vertical-align: middle;\n }\n\n th {\n font-size: 0.8em;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n opacity: 0.7;\n }\n\n tr.is-revoked {\n opacity: 0.6;\n }\n\n a {\n word-break: break-all;\n }\n\n th:nth-child(1),\n td:nth-child(1) {\n width: 31%;\n }\n\n th:nth-child(2),\n td:nth-child(2) {\n width: 24%;\n }\n\n th:nth-child(3),\n td:nth-child(3) {\n width: 15%;\n }\n\n th:nth-child(4),\n td:nth-child(4) {\n width: 16%;\n }\n\n th:nth-child(5),\n td:nth-child(5) {\n width: 14%;\n text-align: right;\n }\n\n tr:last-child td {\n border-bottom: none;\n }\n }\n\n .scope-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4em;\n }\n\n .scope-chip {\n display: inline-block;\n padding: 0.2em 0.7em;\n border-radius: 999px;\n background: light-dark(\n oklch(92% 0.05 var(--primary-hue)),\n oklch(35% 0.08 var(--primary-hue))\n );\n font-size: 0.85em;\n white-space: nowrap;\n }\n\n .badge,\n .token-list button.revoke {\n display: inline-flex;\n align-items: center;\n gap: 0.4em;\n padding: 0.3em 0.8em;\n border-radius: 999px;\n font-size: 0.85em;\n font-weight: 600;\n line-height: 1.5;\n white-space: nowrap;\n }\n\n .badge {\n background: transparent;\n border: 1px solid currentcolor;\n }\n\n .badge-active {\n color: light-dark(oklch(35% 0.16 145deg), oklch(75% 0.16 145deg));\n\n .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentcolor;\n }\n }\n\n .badge-revoked {\n color: var(--clr-text);\n opacity: 0.7;\n }\n\n .token-list button.revoke {\n background: light-dark(oklch(92% 0.15 25deg), oklch(30% 0.12 25deg));\n color: light-dark(oklch(30% 0.15 25deg), oklch(92% 0.12 25deg));\n border: 1px solid transparent;\n cursor: pointer;\n transition: background-color 150ms ease;\n }\n\n .token-list button.revoke:hover {\n background: light-dark(oklch(80% 0.2 25deg), oklch(45% 0.18 25deg));\n }\n\n .token-reveal {\n margin-block-end: 1em;\n padding: 1em 1.2em;\n border: 1px solid var(--clr-border);\n border-radius: 16px;\n background: light-dark(\n oklch(96% 0.08 145deg),\n oklch(28% 0.08 145deg)\n );\n\n input {\n width: 100%;\n font-family: monospace;\n padding: 0.5em 0.7em;\n border-radius: 8px;\n border: 1px solid var(--clr-border);\n }\n }\n\n .scope-checkboxes {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 1em;\n }\n}\n","@layer components {\n #theme-selector {\n display: flex;\n justify-self: start;\n align-items: center;\n gap: .25rem;\n padding: .25rem;\n border-radius: 999px;\n background-color: color-mix(in oklch, var(--clr-border) 40%, transparent);\n\n button {\n display: grid;\n place-items: center;\n appearance: none;\n border: none;\n border-radius: 999px;\n padding: .375rem;\n background: transparent;\n color: inherit;\n transition: background-color .2s, color .2s;\n\n &:hover {\n cursor: pointer;\n }\n\n &:focus-visible {\n outline: 2px solid var(--clr-text);\n outline-offset: 2px;\n }\n\n svg {\n width: 1.5rem;\n height: 1.5rem;\n fill: currentcolor;\n }\n\n @media (hover: hover) {\n &:hover {\n background-color: color-mix(in oklch, var(--clr-border) 70%, transparent);\n }\n\n &[aria-pressed=\"true\"]:hover {\n background-color: var(--clr-text);\n }\n }\n\n &[aria-pressed=\"true\"] {\n background-color: var(--clr-text);\n color: var(--clr-background);\n }\n }\n }\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n\n ::view-transition-group(changing-theme) {\n animation-duration: 1.25s;\n }\n\n ::view-transition-new(changing-theme),\n ::view-transition-old(changing-theme) {\n mix-blend-mode: normal;\n }\n\n ::view-transition-new(changing-theme) {\n animation-name: reveal;\n }\n\n ::view-transition-old(changing-theme) {\n animation: none;\n }\n\n @keyframes reveal {\n from {\n clip-path: polygon(-30% 0, -30% 0, -15% 100%, -10% 115%);\n }\n\n to {\n clip-path: polygon(-30% 0, 130% 0, 115% 100%, -10% 115%);\n }\n }\n}\n","@layer components {\n .note {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--clr-border);\n border-radius: var(--border-radius);\n padding: 1ex 2ex;\n\n .e-content {\n > p:first-child {\n margin-block-start: 0;\n }\n\n .u-photo {\n width: 100%;\n height: auto;\n }\n }\n\n .syndication-links {\n display: flex;\n flex-direction: row;\n gap: 1ex;\n\n svg {\n border-radius: 4px;\n width: auto;\n height: 1lh;\n }\n }\n }\n\n .reply-to {\n font-size: 85%;\n margin-inline: 2ex;\n padding-inline: 1ex;\n border: 1px solid var(--clr-border);\n border-bottom: none;\n border-top-left-radius: var(--border-radius);\n border-top-right-radius: var(--border-radius);\n }\n}\n"],"names":[]} \ No newline at end of file diff --git a/public/assets/css/app.css.zst b/public/assets/css/app.css.zst index 2fda63037f8da7e97d557de0283fb3148b4aa42e..23a21d7720e31f959cbb407ddbb5b05f401a4cb8 100644 GIT binary patch literal 2158 zcmdPcs{c2IS%TG>K`NqwUqc|fL+*icgPmc#qUh0M{M-M=*B??oF?C&!OtQwW{(mX4 zpSP`E#m$iMjNvuITZXd<+=pJSy_Mhod9(EGiTZBK?!TCCt|~wKfOut;{_~Xmrf*Ia zSjW4)E*5NhY|~TqUCH}WTy6D~!XxFpdvd<7GMrzqEu-$}8^!yJ-Um*f^ZsUf_I866 zQxl^bD)!w~ah@iT+JA$6+mQ)ByG|WH@iX3Y`bC>Jn#Z%Vw&qy;-1R0!qNaAk!iQg2 zH|*jo-gJG^`G+!Vi+M_X{;!d-xz!?cInMIlk$^NuL$0nyjb{sIbk4q1Yw)-$%J1a9 zFkYGUb)Nr!eV<^pNvFsE?$ft5US|~VH-}G8@w)a~J|^-oxozS! zKEE_ihYMM|9*3U%r+tb4(2Ur$n03)lZ-sxIcT3ah;d3jo9h0`K`{(Yui1lEi!oJlt zJypxP?QXnbIkj_-8_%v!`314r*0F!)h|9cdik0KIU$XzT$yw1_(_H75SJ@-CW)&y3 z|5jTYXZo6Vrbx0@bU;b5$nx1K&zDWny|Tg6Nm4>0Zkuta1WWotgRUBmlijDES22Ef znm600KePDppYQXf^sMUt&2qTqcQ8Fsc0!c=TT9E8*U!xti49GbtqF;a|LfZ$WN{;J zK}Kc4HTQ)c%S) zJaw+p!U=D`)t&k(yu14?|K_(LmNz$5w-mkcMqEJL? zTjH&U5m|c5-<%e2==Pd@?$`uN=fsTxIty5x0`vvtSX21(riUHb)KvO+#)NO7@sIm9 z3uKB(nf+{g{P+HZ=%aZ#s%FlIId<+%?^`&}io3O=bSY1)_q`+gW(l{gUU(!w*QnWWNM63CGx(kv8J$^2=717DI% z^Blz!g2E@xcnJ79I%>5(6jms5V76f4o-LR9?`}%R;x*F61@_&Gr~2==S{;&Kts?y@ zG>B>HMb|dH|KBI}uGubri)rbD^*a2|wu@F=w`yKcU**;+*ytfPDQ2N$Xy6t`!@s=d zCzpCY?+*yJEvjUHs1e`!$iY@`|0y+LnJlG3hA5eYUuF6VnPIK(aw;U;`HGIpu6w{X zgXM&Mdfwa_(^~>(N*%qiXG+iEy=g*>dj${1?mN<;{E9K=b)#(n|H6sIO6>(KOq|nG ztPfr*m@Rp&S~iSvE5pPi3WpwT_)&j6t6iD$tz^O}^-{!f@S2^&?w5#j;v%RI9qizdL;Y_;ICRi|iAgrtyz6MVHQM3_5e#W#zxp zjUsEzgrz*=WW>y>SsQ2fZ(T5Lib<)x=f1{Oy_pStb}M(OiZh3s9-C_OJ|?zi`WAh= zlNQ>$KiF!2SuQU2_DM#=ok`9L-4%b@Jv3iDI?^G1HgryN!g(p#efRDNHpjTUNH*O0 ze#W*xEnUY98TwbbNC=%jBkjvxuyqy-lRszvI$?+6Nncb9XG$;AOETDc!R^zQkGp1g zE#y?*^2a<;Bu_EH<@v(qeurhQ^TN|PIFHR~v~BS{(cp1>@(~N3$=b=Q*^YO$l%#~L zW{_zQx^!Aj&u_6~#+@l0n&y`!{N=;02cNmly-0QY@ym_DT7U2G_A08&#yn?tUu|%b zb%$oOm9>P%%QG^kZyr3f$J4$l_g_*PgLgxS+nzlW+|y0%YmB@>Os&j0(x&~Wvo>M4#sn}4}BR-AXfSF?w&lJj&lOUkl> zV@=ZTncTUWYr?KyUG&lG<3=x+gB=UpdrvX0ifF$Q<=Pa<_{X!jG`iN@b^5pUtbGA9 z4XkLl6TE26+)K5Ovi;^)i4+<-orOyN%oAiFh4Vm0Cb7nZ+T%(b{ zQ*TH2^t;8@Tb@h3JE;6wfHgzBjOmv?%P*r1x60&NEFQIJ{Pk82%L-s?Z=Ta$@^yt! z%h`F`7|wXF-%?_g;BQs2dhh<=ImPO^i)CdQOzcHoWGMV&oyWI5x9Rz&tf1mr_r~Yh zbC|snmrRIz*!^gC1-2`*V4+idRif5h&K1aengW`3tT^iWdL-Vh8|Lv-*bs literal 2117 zcmdPcs{c17UVN`3gH@OfzlMOX3;%{i7v^{9YchQg-2B^U(=8bVpW5v*h20x(EuJrM zx|@%|p-V&HA;U9<=L~Cay>I)xS$g+GeWzvdFXn$&k?%er9vP*-JY~P>n_UIg@ouk+ z1Dq~frGC%3rg#3%oS9qWM{$}&mV3{kYHkdlt z+|Qe&=-#u*c@495(heKfr_oRTEMK%cLFQZV+&a`Zev|R^TpDM$5^+{nN+(}&LZw_ z=>K2OJES)0^w{5h`nJYxjbeODc(|$Z>%T&qm)3g8ZcJ_XyY6jgdCS8_zQuPM_QaPZ zf7Lx1kX@e_%Bw0}>vi(VoZV{dD_*tl{T%*&ey~x&j&;&*+)iFeB0}dII#zuuG%46| z&dcJ=?LvPxK7l6-6F#kNt2t-bDsp0RqE2wO1V>ovk_AUp)hloIuB~e`+4Y>a-6&$| zyyW=TS`#+O9bGVw#PEdQNV;yI2Mws&V-yZT>^V~L6h_mT3c{l4c*F0#+=b+DWs ze!?;MA8X9@TYlp9&)M2fCy25iQos9N%d^4L^2)rtIpdtVgqZK#$%@4Dppl%0kDetYwZ z&$X|2Rmh#cfH^cIQMx}WvD(k=)z;6?UBr(@u;0IOExcZ6F&p2und&R0f)tM0T4VEfX>VPaF0$Pxn? zXN5`b%$h8defqEDn9dS;WO1F>YKOP$-c`%{3&MKt&5}vioIOeWi1U1&-v9k7r=#+3 z-n5zFbkSz!{V4}xcQd?cs8gM$Fu|2S{L5M^%TLQ@1Sw8>Zj^H2ds4B$&Yrr5FT#U7 z6wd1{?Kl%NY1Vex19O=L?y)gFI_PoXue98aiRaflG-$tISjPD2;F+7Tzl8o7+dkT8 zxzhCI&bgjqRcZ(S{5;TdaV49~n~Ao8{0k>1w>sZoZ{(bwXuUCQhpSOfh3w*ntqmSY z6aGDNc=|sqtG)X{xJ=$9vkCix0&V9>{4P5wadY0I)eSsJf81B^JbSo6A>46Chu&x9 zW9vEA=pKuoxySd}2BoZJFPip0yHnSyeUiu3tR+xgTVy(8(}C8#oR=@Z4h~ZC{I=53 z>_M|?dU12{@#c4}C)sY^4D{W4VNa0cVc{=c8Zn%u(Pzb~ZiI;6*d^52w&>WVV`hTw zDp6^Q4)04D!=mKtKFiPc>Aq%=ruA`_%j$wa;};$3GXqxDo9eipYP-4a?w?5}=^Jlu zRM_w&qFTyh;j(vJk!6mz4Pzbpo7Db<@fNC1F86zT_vhC59e!L&yRzw6U)f0w zBcFg~1%{Q!<&*+0Z0&iGHtV1TyR^;m*_C&<7oO-{@bpPo&vVtNyaHXHJo~?k1$ho6 zYYW;g=6|$cH}fAYp}+&d)BaxOTwoblHE)LS@|T)#40129eVn;*LyOnymdxCj%jU70 z>m;yzm~hzr{}!3M(Z6bVk`0`>wsWo)cyCkL^EcsF`q5P38)>b#0l%$s{T+rQpV3f4|L-f~dQ{D$+E`+r+`Z$2&A(01>_xOdrv)kG( zSF}8sVgIA-58K&RT>sU2ASsYt=)>z-N$a->t^KOLaiQY-Ij^EhYrU^L;H*5XFCn0; z{GK^U{MNkUB!;D%7WtIM-VoO>Jiqrsp2;27{xh>RPfXk(e4$?Pk~qiezt0^{D980) zik%g_Gg?yUdtu!5tBbt^%x>qZn!n$BX;F#8{E|y+YyM{zUgO)?+h}EAqHX(IQE492 zcDD+hI-#uZbw-VH>*ogD<4#lMZt?55`{;jB?!J;$t7RFERx>Bf4U4FaDU7Q9zAfZP z?~Q1ygiiaq&j*f8uT6TzzF>h<(42h}7E~-ez*DnR+?OXkKl|T`6Dy2l41YMxZ$JFA y#r{v@&yy!pSeIYPT%>&4=P%>AJ#QPEt_!QYd?WEJCUKwo^X#TWDWU152igI{*Ztitle) }} « @stop +@section('title'){{ $article->published ? '' : '[Draft] ' }}{{ strip_tags($article->title) }} « @stop + +@if (! $article->published) + @push('head') + + @endpush +@endif @section('content') + @unless ($article->published) + + @endunless @include('templates.article', ['article' => $article]) @stop diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index b33d1fa9..80e6780d 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -22,6 +22,7 @@ @if (File::exists(public_path('gpg.key'))) @endif + @stack('head')
diff --git a/tests/Feature/ArticlesTest.php b/tests/Feature/ArticlesTest.php index 5001e923..a7685a74 100644 --- a/tests/Feature/ArticlesTest.php +++ b/tests/Feature/ArticlesTest.php @@ -29,6 +29,39 @@ class ArticlesTest extends TestCase $response->assertViewIs('articles.show'); } + #[Test] + public function articles_page_does_not_list_drafts(): void + { + Article::factory()->create(['title' => 'Published Article']); + Article::factory()->create(['title' => 'Draft Article', 'published' => 0]); + + $this->get('/blog') + ->assertSee('Published Article') + ->assertDontSee('Draft Article'); + } + + #[Test] + public function draft_article_is_viewable_and_marked_as_draft(): void + { + $article = Article::factory()->create(['published' => 0]); + + $this->get($article->link) + ->assertOk() + ->assertSee('draft-notice', false) + ->assertSee('', false) + ->assertSee('[Draft] ', false); + } + + #[Test] + public function published_article_is_not_marked_as_draft(): void + { + $article = Article::factory()->create(); + + $this->get($article->link) + ->assertDontSee('draft-notice', false) + ->assertDontSee('noindex', false); + } + #[Test] public function wrong_date_in_url_redirects_to_correct_date() { diff --git a/tests/Feature/FrontPageTest.php b/tests/Feature/FrontPageTest.php index 8c8a5706..3a542d4a 100644 --- a/tests/Feature/FrontPageTest.php +++ b/tests/Feature/FrontPageTest.php @@ -31,4 +31,15 @@ class FrontPageTest extends TestCase ->assertSee('https://example.com') ->assertSee('Like 1'); } + + #[Test] + public function front_page_does_not_show_draft_articles(): void + { + Article::factory()->create(['title' => 'Published Article']); + Article::factory()->create(['title' => 'Draft Article', 'published' => 0]); + + $this->get('/') + ->assertSee('Published Article') + ->assertDontSee('Draft Article'); + } }