From 6b4607e4f9b7b28abdffc8f902b7efc398c224ac Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Thu, 11 Dec 2025 19:58:17 +0000 Subject: [PATCH 01/26] Small mobile layout tweaks --- public/assets/css/app.css | 2 +- public/assets/css/app.css.br | Bin 1196 -> 1202 bytes public/assets/css/app.css.map | 2 +- public/assets/css/app.css.zst | Bin 1446 -> 1450 bytes resources/css/layout.css | 6 +++- resources/css/notes.css | 58 +++++++++++++++---------------- resources/views/master.blade.php | 4 +-- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 2f656e08..18f1f58d 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%;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,95vw) 1fr;padding-inline:4vw;display:grid;&>header{grid-column:1/-1}&>main{grid-column:2/3}&>footer{grid-column:1/-1;margin-block-start:2ex;& search form{flex-direction:row;align-items:center;gap:1ex;display:flex}}}.h-feed{flex-direction:column;gap:2ex;display:flex}}@layer components{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}}}.h-feed{& .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}& .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{& button{appearance:none;background:0 0;border:none;&:hover{cursor:pointer}}& #theme-selector-dropdown{position-area:block-end span-inline-start;border:2px solid var(--clr-border);background-color:var(--clr-background);color:var(--clr-text);margin:0;position:absolute;& fieldset{border:0;flex-direction:row;gap:1ch;display:flex;& input{display:none}}}@media (prefers-reduced-motion:no-preference){& #theme-selector-dropdown{&:popover-open{opacity:1;transform:translateY(0)scale(1);@starting-style{opacity:0;transform:translateY(30px)scale(0)}}opacity:0;transition:transform,opacity,display allow-discrete,overlay allow-discrete;transform-origin:100% 0;transition-duration:.5s;transform:translateY(0)scale(0)}}}::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%)}}} +@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%;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:2/3}&>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{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}& .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{& button{appearance:none;background:0 0;border:none;&:hover{cursor:pointer}}& #theme-selector-dropdown{position-area:block-end span-inline-start;border:2px solid var(--clr-border);background-color:var(--clr-background);color:var(--clr-text);margin:0;position:absolute;& fieldset{border:0;flex-direction:row;gap:1ch;display:flex;& input{display:none}}}@media (prefers-reduced-motion:no-preference){& #theme-selector-dropdown{&:popover-open{opacity:1;transform:translateY(0)scale(1);@starting-style{opacity:0;transform:translateY(30px)scale(0)}}opacity:0;transition:transform,opacity,display allow-discrete,overlay allow-discrete;transform-origin:100% 0;transition-duration:.5s;transform:translateY(0)scale(0)}}}::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%)}}} /*# sourceMappingURL=/assets/css/app.css.map */ diff --git a/public/assets/css/app.css.br b/public/assets/css/app.css.br index 285d0f59050f05a157f555631a9cd020ee341c04..74dedb608c0fbcc8d358975837e331b87128e37d 100644 GIT binary patch literal 1202 zcmX?DP|EPYV?*+Uw+geY%bc!g7vRZ3qwy#<{H!vfP>2`%b-*Sz2@yn&ocrMh{ zDaziu!f}(7gwcARt+TI9lQ_8HG_&276+gH9JTLb3TMlP?$e;d2#YcQrZ}m0#=B}o_ zvviTIHq)j>JJ)-#9yVDv^Yp%`3E{pmYpeQR{#UQI2%2;|bb&*#L7SwiKxM{+&Wh%B zl5*Sk3ib4FS-Wn*jdga0<-y+~x#or)+R^!Aww>hM;^#si;}5I4Zg&iwl$l`NCg`8t zAJ}Gc@|;@2AswCCe+!T1CxU+ev#33zb76tx*_K=9ToQS|HD7$%IPaS!1M6>g9`*j#x*~?NY5N!MIr`y$ z<{b006Qy!KJ2lHPY6w#Vc)mkhlWNUIa?oU2hZt-ESEO=9a@&(qrb*rOM0Ag)je?+UY;H4(vzH*Tz4qj(J0FH zKu9Tm$`ji{i|-~g+Dv~rPxF3$Wsl^G<_SO7J&?$}QryGM)u%pfuKdSKtI}Ik16Q>C z@JME8`Few0W?$-}SM$_=U2$yiaGce-!)=qZMuErjy&-!im)U$?(Ed~U#?=PpqC)?Z zwt?mXeM=l~`MsSecI#oyTo(Q4i>zn-4b>O5RyAz-+T!ZTA5^jYh+9B!i;vm14qq-^0V5Q%l;0;n=zA!kqBs^F$sl#IC10nHK+73U~ ziYRYpGF#xBN%(x9uI-?h#P-Y<%vs;;Xw_*iU8vma$bV literal 1196 zcmX?TP|A?7*JM00hv?cRGjv^YKJs!4(utz&z*w?*osdY1ofXRbuuKAK$rbW>22`>n}}3X-#K zcd1xBGSrRvcGYFiDetYha+m+>e=As)wq1LHL-CEA>PZgAoLzlV%3nlHIJ?8B>Eq<% zuegqyeeGN_J8{1A!pUwD<%ac(#TOgP#T@#-dWp(=vlU(&BqYsm3!cAiz;$v$%FUas zYHJo6#XP+{caGn3@g)Hh>Ns^fOocfg^PNz1RJiY%eoMt(f$wcrg0!Aw9Vhz__2iZR zD&{B(3T9mBVVb;6I5^@^oxo0;3zrr7HEzg9%x60Lb~5{{#Qn$j*#4-O`n@o6`BVO> zmZj^yW!{{2O^tuU@#iZ_p3b^vIqQJdpZ+!C?0U;rEbe1Fa$4!`qU|5z8jcHXDC*M< zE4WZ}|MQ$(4y7eA;*2-fwpg#NwR#@r$-P`u!=Xar%AuDI=O-_`BGJ>Zl~qOo%-x59wukxS+QL+YL0#scj?xePw&(> zDn~qIE3%xU@?Pz(&ua0XHH`Ppgl25t-g4&De>c}1zg#T$+;@xPd&pH{xUXOQ{c+AX zw?Kn{=+MiHq}JVSowV9TCHk;+r1qh{4NWJx*R{@y7V;^#D^mZ-XnR;cFDY40LH~3* zAA^akMz8aZYVCS71^r>W~cACcre}O0OzJVyVssO(!9(s^{J4NR{sBz2hJ!y@>%-p?T9Z@L%=L<<^ZPzIcPiO!IUz6W4u>F`R!c^i})GHSTKusN5G+p0<9? zCb|2kRWfdNua97xcQfh)!>Kfh=0!m+8~j$-s!N}Wym7;kJ1u{zv^@3X3H{*z&M7R(=FUml%M=@XTB`Q zWAo2^tJS1PpSj=W7jiy&yrM)(tSNL~?dhzELLOUIY%q$PB5Apsd71;y-VOZ66Py&I zCtcg&V&KZs*OS(EVcwaa%9}nJ_`G=8$WqhxP@!>);yM%Ql{?o&yw~@*64uH;VXOI; z#5*y8{E4SuhDmFveYV?a&8awlQMj(nmMyugXNy1GY>!rnwpy_&X`B5IDZWOD&BDHg zJJvojJ38l| p:first-child {\n margin-block-start: 0;\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}\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 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 #theme-selector {\n button {\n appearance: none;\n border: none;\n background: none;\n\n &:hover {\n cursor: pointer;\n }\n }\n\n /* This is the element with the [popover] attribute */\n #theme-selector-dropdown {\n position: absolute;\n position-area: block-end span-inline-start;\n margin: 0;\n border: 2px solid var(--clr-border);\n background-color: var(--clr-background);\n color: var(--clr-text);\n\n fieldset {\n border: 0;\n display: flex;\n flex-direction: row;\n gap: 1ch;\n\n input {\n display: none;\n }\n }\n }\n\n /*\n * This is the element with the [popover] attribute\n * Here we are styling the open and closing animations\n */\n @media (prefers-reduced-motion: no-preference) {\n #theme-selector-dropdown {\n &:popover-open {\n transform: translateY(0) scale(1);\n opacity: 1;\n\n /*\n * Start styles for the opening transition.\n * Added to :popover-open, but after the opened styles.\n */\n @starting-style {\n transform: translateY(30px) scale(0);\n opacity: 0;\n }\n }\n\n /*\n * End styles for the closing transition.\n */\n transform: translateY(0) scale(0);\n opacity: 0;\n\n /*\n * Enumerate transitioning properties, including display and overlay.\n */\n transition: transform, opacity, display allow-discrete, overlay allow-discrete;\n transition-duration: 0.5s;\n transform-origin: top right;\n }\n }\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 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 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 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, 95vw) 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: 2/3;\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 }\n }\n\n .h-feed {\n display: flex;\n flex-direction: column;\n gap: 2ex;\n }\n}\n"],"names":[]} \ No newline at end of file +{"version":3,"mappings":"AACA,aIGE,uCAOA,oFASA,8DAMA,4CAMA,qBAKA,+CAMA,8BAMA,gEAMA,yCAOA,mEAOA,qCAKA,gCAKA,gCACE,wGJ/EJ,YMAE,oaAwBA,kECxBA,0BAIA,oBAIA,iIAME,0BAIA,uBAIA,iDAIE,yEAME,uBAON,oDPvCF,kBCAE,wHAOE,yBAIA,+BAKA,gDAKE,kCAAqC,2CCrBzC,+HAOE,gDAIA,6DAKE,gDAQJ,4MCxBA,sFAME,+CENF,gBACE,oDAKE,wBAMF,gMAQE,4DAME,uBAUJ,8CACE,2BACE,yDAQE,gBAAiB,8CAST,uKAahB,iEAIA,kGAKA,4DAIA,qDAIA","sources":["resources/css/app.css","resources/css/notes.css","resources/css/header.css","resources/css/pagination.css","resources/css/reset.css","resources/css/colours.css","resources/css/theme-selector.css","resources/css/layout.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","@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 > p:first-child {\n margin-block-start: 0;\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","@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 .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 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 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 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 components {\n #theme-selector {\n button {\n appearance: none;\n border: none;\n background: none;\n\n &:hover {\n cursor: pointer;\n }\n }\n\n /* This is the element with the [popover] attribute */\n #theme-selector-dropdown {\n position: absolute;\n position-area: block-end span-inline-start;\n margin: 0;\n border: 2px solid var(--clr-border);\n background-color: var(--clr-background);\n color: var(--clr-text);\n\n fieldset {\n border: 0;\n display: flex;\n flex-direction: row;\n gap: 1ch;\n\n input {\n display: none;\n }\n }\n }\n\n /*\n * This is the element with the [popover] attribute\n * Here we are styling the open and closing animations\n */\n @media (prefers-reduced-motion: no-preference) {\n #theme-selector-dropdown {\n &:popover-open {\n transform: translateY(0) scale(1);\n opacity: 1;\n\n /*\n * Start styles for the opening transition.\n * Added to :popover-open, but after the opened styles.\n */\n @starting-style {\n transform: translateY(30px) scale(0);\n opacity: 0;\n }\n }\n\n /*\n * End styles for the closing transition.\n */\n transform: translateY(0) scale(0);\n opacity: 0;\n\n /*\n * Enumerate transitioning properties, including display and overlay.\n */\n transition: transform, opacity, display allow-discrete, overlay allow-discrete;\n transition-duration: 0.5s;\n transform-origin: top right;\n }\n }\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 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: 2/3;\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"],"names":[]} \ No newline at end of file diff --git a/public/assets/css/app.css.zst b/public/assets/css/app.css.zst index d6c155b813daa070abfc1ffbec68b3492043d74f..5adb7c02d39790cbf69121da35d830d40bede8b1 100644 GIT binary patch literal 1450 zcmdPcs{c1-J?~Q;1}^72-0BLxbJ#a9{g^Gb?IG9p-dT6gX>K{jAiVQbN?i%B@q;q1 zYGD=*K_}OZ3>z4>F!<^5y?E8s|3+@()4#JU|6W|@UgEXuc*w@P>&+i8^O#XA-`((I z?U_gWa&>mfEj;`9*uJOAl6QrE+h>KiEsEr~lyUA--ha~HwR+-*%eE|kZYl3sVYJ#c zOv|cWf5}?i^}2s#11mPv^|sm;?MR=<&6ttcFpYJK$pxd#yDLPcwAz!8e>I+UA?%~~ zkE^@go0YrleguS`JG*tfR!&}d|uq(g4TKSyEb-UX6gSA(V*S#>V zvzeZHf7g?evyul4&Nl7a*)eH@`+27SAA&dSkN2II(mqdlBFhS9qvy>##ng=*Zx;&$ z+uybIR(o(n$+By)$@eO?&K!+;_coDmzt%;8Dw`Y&7$!#D5t%vH*Nth@#7N#Le6tm* z7kySfK5fIzMTYiVULkIW&Q5w=Fvs2~`tr&he97g$oMbCH;AiXqWT5)unkjxy8mIGaWmW#4;mo9cu@Hxz6xQuaQTFb=Ae=;w*r+I&T zKY0Tyg9x{$o{x%1vC+|*dBvg}5*8H-Pt6jZ1)f()aAaKYay{2(0}Yp)<4?B6Tv#7+ z$<~eCz{I!h_4yWgE{Q|?B=Tyw_qtBAh)_?Sm?2ZB7^bc)X!>-|)kE)Rb$zLxJ#A?i zn_J^z6OOvm%MK|h=q!(%T%MD9`NXEFHUX`ntv5dB^xr)!Z(3&AePz-y5A&P%LP~X~ zfB(`EnC{{H`hZGXWK8_DqD-rR{I5(|tvk#r@0@aJ{rjG=QDsHp+9VH`loShh*! zy-4|26C%iTD|*WA_4`F8#fJwiKIRoZ)3>E_5lfU5ry=u1Egz+~-chC z{h3>EY{q1dOG-;Lf_xtbWwxO3ccmM=2HRE21W&OJgRaJSL+>~)x0k>)!3Hj&YC`# zIH6X_V!u%9sgowVJp8F%JE7mTxlu&kKENauUYFHEiutn;)B44mrCBT&U5Z7*a|W|h!+bD&Df*%c1G6npF6rI zuD|YZ>h(iIK0Bd)jo=a@O*lc51?WfjzU;W&i zs9R{xbhBZOvVq!3Sw3U6D`(n2O5OHTizxR<^gQvY@m^v9kCPnpjy)@@?Ai`@hnjrX zQZ!rapRz$Csqnh^DqpbLQ^zZ!3N*RmriT`S`{{w&~klO+K7*KAe3f&HC$7#w_>uot@&78fv)wG^7~h zPus4qIR9DJ^~~B^tB!8eWmwIr=e$NrVp-pU$V-<_Kbds$spgxbbquDRVyV{)mLZ-u+lyq~o?JjGl8C`WduS#_V@ T$^9(%{=*uL;AmFwf;H*@sn46T literal 1446 zcmdPcs{c1-Iqy*&1}^72+^P!wcP4yvm^RIQli8jr5$m>?KB;3ncyiBA-bcL>6$j-O z2{1XZ@NZz)$gquJqSX}9&297F)Y(4Q{q6eq`mU2by~jLrH$PtgJ^4n!m0h2FP3ny6 zil2J*tqV8s*tF%+&+ca4 z+L9Vw9w&FF_WkhnIW}y*6jz_9rVO^^VhQ}?WVb= zC*C)`S!?D0X3N2xmK?(rv1vQhQh%?$Vf^&B-sT@OFRt2>e$oATmRtG6i?>$(eZ%?t z_@vdF-^{j*Qtn}!$6j>xa73oqx7YDo_V15XNj<;G%S|k*A#e)U)sx)?ajTe{jl$N~ zezmaNbwE(qG?01mn^O@QEg!#^81!%$Prr7YIZH<*$Rop`K+kyQ->V*v7QCsE2{G(R z)hT0JZC`1&^_Qc<=PO$6t%~l`yXMq?DmLA=^I~%UhR6uN8Bc8uuYP`d%0P+R`OfdU z-eo74jUGpF_wZ!#pRhdqG^3#&3yfTCn)dE1~UOm0)NZ~=Ty|WL?C$8Qy z<+O$0G*`K{6^&9l->*K)^w_~sG`&ARGkJ2R`Kbd^d6Hp!pL3}m@BBaMnybJvO*3uY zOzWJDD?hKRvpqBO($r*|Gl%on=?CwcQC{81p&uNR^>zRMBb!(oRaP`^403ZxNm23S znsm&Oqw%4GrsW=HrJE+_gSZ6KIE)=np6F>>5p*+x*}7wmn#9Sp?b9x7%vgP-lOfh) zn&_&wucr07t11{}MO3~`u6JBwvDT$iYj;W2!hH_cW*XeI2;AzK7xU8TbV~i)iT;x` z*O`iU6^G3JBV(z*gjY_~*Q7ei%dFCw>y(YtWUh~mD$JM8O?VwC`>bEodZ&}OXxNl* ztqJAK{*U&!na)%ddgFWjR!H_Wmy;8IZs5(53O9_2S=Cv7vh%46&ufd*xlDfyeJ>q} zn_^nK72jc{_(@QuWTxp8v-pqK(>xQvJ&QiV|E2Hb!8K$>;d#z$R zERtDu%2~hg%?yJ>aqOH`m(9=g&UP;RAlT>_&BQh1bKUcBF(;ps%MGnA))}7t-h3&Q z!+O)DzExuW^EP&8>=nB|eV+f+x9X2`e*b^zekXSQmAwx*qxN`gy#969fgQYTZzlXs zI=i-2V43Qr!(4adUy7c+yUDic@q$lv|DLVA zA;;yg^a;c4m)_Zv{szx6PJfZ;!G8VYJ??wlE>;&d9M(&7;obG|Da%oJ=W0(K7A8&( z2K7UX`t8hNA8igxyC0P{I+iti?SfF&=_+B`5@CG{G%u-4Z^>V}Gq{-Fs^Ue-<@o^z zA4_B>|4m?;IFb802T1XH~;_u diff --git a/resources/css/layout.css b/resources/css/layout.css index beb30eff..488d0f95 100644 --- a/resources/css/layout.css +++ b/resources/css/layout.css @@ -9,7 +9,7 @@ body { display: grid; - grid-template-columns: 1fr min(80ch, 95vw) 1fr; + grid-template-columns: 1fr min(80ch, 80vw) 1fr; grid-template-rows: min-content 1fr min-content; padding-inline: 4vw; @@ -30,6 +30,10 @@ flex-direction: row; align-items: center; gap: 1ex; + + input { + min-width: 0; + } } } } diff --git a/resources/css/notes.css b/resources/css/notes.css index 005b89d3..ea85f2d2 100644 --- a/resources/css/notes.css +++ b/resources/css/notes.css @@ -1,37 +1,35 @@ @layer components { - .h-feed { - .note { - display: flex; - flex-direction: column; - border: 1px solid var(--clr-border); - border-radius: var(--border-radius); - padding: 1ex 2ex; + .note { + display: flex; + flex-direction: column; + border: 1px solid var(--clr-border); + border-radius: var(--border-radius); + padding: 1ex 2ex; - .e-content > p:first-child { - margin-block-start: 0; - } - - .syndication-links { - display: flex; - flex-direction: row; - gap: 1ex; - - svg { - border-radius: 4px; - width: auto; - height: 1lh; - } - } + .e-content > p:first-child { + margin-block-start: 0; } - .reply-to { - font-size: 85%; - margin-inline: 2ex; - padding-inline: 1ex; - border: 1px solid var(--clr-border); - border-bottom: none; - border-top-left-radius: var(--border-radius); - border-top-right-radius: var(--border-radius); + .syndication-links { + display: flex; + flex-direction: row; + gap: 1ex; + + svg { + border-radius: 4px; + width: auto; + height: 1lh; + } } } + + .reply-to { + font-size: 85%; + margin-inline: 2ex; + padding-inline: 1ex; + border: 1px solid var(--clr-border); + border-bottom: none; + border-top-left-radius: var(--border-radius); + border-top-right-radius: var(--border-radius); + } } diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index 235c6b15..dfb7a5a1 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -88,8 +88,8 @@ HTML; - $html = str_replace('href=""', 'href="' . config('app.url') . '/notes/A"', $html); + $html = str_replace('href=""', 'href="'.config('app.url').'/notes/A"', $html); $mock = new MockHandler([ new Response(200, ['X-Foo' => 'Bar'], $html), ]); diff --git a/tests/Unit/Jobs/ProcessMediaJobTest.php b/tests/Unit/Jobs/ProcessMediaJobTest.php index 29eb780c..da9f070c 100644 --- a/tests/Unit/Jobs/ProcessMediaJobTest.php +++ b/tests/Unit/Jobs/ProcessMediaJobTest.php @@ -20,18 +20,18 @@ class ProcessMediaJobTest extends TestCase $job = new ProcessMedia('file.txt'); $job->handle($manager); - $this->assertFileDoesNotExist(storage_path('app/media/') . 'file.txt'); + $this->assertFileDoesNotExist(storage_path('app/media/').'file.txt'); } #[Test] public function small_images_are_not_resized(): void { $manager = app()->make(ImageManager::class); - Storage::disk('local')->put('media/aaron.png', file_get_contents(__DIR__ . '/../../aaron.png')); + Storage::disk('local')->put('media/aaron.png', file_get_contents(__DIR__.'/../../aaron.png')); $job = new ProcessMedia('aaron.png'); $job->handle($manager); - $this->assertFileDoesNotExist(storage_path('app/media/') . 'aaron.png'); + $this->assertFileDoesNotExist(storage_path('app/media/').'aaron.png'); // Tidy up files created by the job Storage::disk('local')->delete('public/media/aaron.png'); @@ -51,7 +51,7 @@ class ProcessMediaJobTest extends TestCase Storage::disk('public')->assertExists('media/test-image-small.jpg'); Storage::disk('public')->assertExists('media/test-image-medium.jpg'); - $this->assertFileDoesNotExist(storage_path('app/media/') . 'test-image.jpg'); + $this->assertFileDoesNotExist(storage_path('app/media/').'test-image.jpg'); // Tidy up files created by the job Storage::disk('public')->delete('media/test-image.jpg'); diff --git a/tests/Unit/Jobs/ProcessWebMentionJobTest.php b/tests/Unit/Jobs/ProcessWebMentionJobTest.php index 549dee60..5eb98d9e 100644 --- a/tests/Unit/Jobs/ProcessWebMentionJobTest.php +++ b/tests/Unit/Jobs/ProcessWebMentionJobTest.php @@ -27,8 +27,8 @@ class ProcessWebMentionJobTest extends TestCase protected function tearDown(): void { $fs = new FileSystem; - if ($fs->exists(storage_path() . '/HTML/https')) { - $fs->deleteDirectory(storage_path() . '/HTML/https'); + if ($fs->exists(storage_path().'/HTML/https')) { + $fs->deleteDirectory(storage_path().'/HTML/https'); } parent::tearDown(); } @@ -64,7 +64,7 @@ class ProcessWebMentionJobTest extends TestCase I liked a note. HTML; - $html = str_replace('href="', 'href="' . config('app.url'), $html); + $html = str_replace('href="', 'href="'.config('app.url'), $html); $mock = new MockHandler([ new Response(200, [], $html), ]); @@ -117,7 +117,7 @@ class ProcessWebMentionJobTest extends TestCase 'source' => $source, 'type' => 'in-reply-to', // phpcs:ignore Generic.Files.LineLength.TooLong - 'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"content": [{"html": "Updated reply", "value": "Updated reply"}], "in-reply-to": ["' . $note->uri . '"]}}], "rel-urls": []}', + 'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"content": [{"html": "Updated reply", "value": "Updated reply"}], "in-reply-to": ["'.$note->uri.'"]}}], "rel-urls": []}', ]); } @@ -142,7 +142,7 @@ class ProcessWebMentionJobTest extends TestCase $source = 'https://example.org/reply/1'; $webmention = new WebMention; $webmention->source = $source; - $webmention->target = config('app.url') . '/notes/E'; + $webmention->target = config('app.url').'/notes/E'; $webmention->type = 'in-reply-to'; $webmention->save(); @@ -179,7 +179,7 @@ class ProcessWebMentionJobTest extends TestCase $source = 'https://example.org/reply/1'; $webmention = new WebMention; $webmention->source = $source; - $webmention->target = config('app.url') . '/notes/E'; + $webmention->target = config('app.url').'/notes/E'; $webmention->type = 'like-of'; $webmention->save(); @@ -207,7 +207,7 @@ class ProcessWebMentionJobTest extends TestCase I liked a note. HTML; - $html = str_replace('href="', 'href="' . config('app.url'), $html); + $html = str_replace('href="', 'href="'.config('app.url'), $html); $mock = new MockHandler([ new Response(200, [], $html), ]); @@ -248,7 +248,7 @@ class ProcessWebMentionJobTest extends TestCase $source = 'https://example.org/reply/1'; $webmention = new WebMention; $webmention->source = $source; - $webmention->target = config('app.url') . '/notes/E'; + $webmention->target = config('app.url').'/notes/E'; $webmention->type = 'repost-of'; $webmention->save(); diff --git a/tests/Unit/Jobs/SaveProfileImageJobTest.php b/tests/Unit/Jobs/SaveProfileImageJobTest.php index 67d441fa..f9b92bb3 100644 --- a/tests/Unit/Jobs/SaveProfileImageJobTest.php +++ b/tests/Unit/Jobs/SaveProfileImageJobTest.php @@ -18,9 +18,9 @@ class SaveProfileImageJobTest extends TestCase { protected function tearDown(): void { - if (file_exists(public_path() . '/assets/profile-images/example.org/image')) { - unlink(public_path() . '/assets/profile-images/example.org/image'); - rmdir(public_path() . '/assets/profile-images/example.org'); + if (file_exists(public_path().'/assets/profile-images/example.org/image')) { + unlink(public_path().'/assets/profile-images/example.org/image'); + rmdir(public_path().'/assets/profile-images/example.org'); } parent::tearDown(); } @@ -77,7 +77,7 @@ class SaveProfileImageJobTest extends TestCase $job = new SaveProfileImage($mf); $job->handle($authorship); - $this->assertFileExists(public_path() . '/assets/profile-images/example.org/image'); + $this->assertFileExists(public_path().'/assets/profile-images/example.org/image'); } #[Test] @@ -103,8 +103,8 @@ class SaveProfileImageJobTest extends TestCase $job = new SaveProfileImage($mf); $job->handle($authorship); $this->assertFileEquals( - public_path() . '/assets/profile-images/default-image', - public_path() . '/assets/profile-images/example.org/image' + public_path().'/assets/profile-images/default-image', + public_path().'/assets/profile-images/example.org/image' ); } @@ -133,7 +133,7 @@ class SaveProfileImageJobTest extends TestCase $job = new SaveProfileImage($mf); $job->handle($authorship); - $this->assertFileExists(public_path() . '/assets/profile-images/example.org/image'); + $this->assertFileExists(public_path().'/assets/profile-images/example.org/image'); } #[Test] @@ -164,6 +164,6 @@ class SaveProfileImageJobTest extends TestCase $job = new SaveProfileImage($mf); $job->handle($authorship); - $this->assertFileExists(public_path() . '/assets/profile-images/example.org/image'); + $this->assertFileExists(public_path().'/assets/profile-images/example.org/image'); } } diff --git a/tests/Unit/Jobs/SaveScreenshotJobTest.php b/tests/Unit/Jobs/SaveScreenshotJobTest.php index f8066bbd..a407e3d3 100644 --- a/tests/Unit/Jobs/SaveScreenshotJobTest.php +++ b/tests/Unit/Jobs/SaveScreenshotJobTest.php @@ -7,6 +7,7 @@ namespace Tests\Unit\Jobs; use App\Jobs\SaveScreenshot; use App\Models\Bookmark; use GuzzleHttp\Client; +use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; @@ -27,7 +28,7 @@ class SaveScreenshotJobTest extends TestCase $guzzleMock = new MockHandler([ new Response(201, ['Content-Type' => 'application/json'], '{"data":{"id":"68d52633-e170-465e-b13e-746c97d01ffb","job_id":null,"status":"finished","credits":null,"code":null,"message":null,"percent":100,"operation":"capture-website","engine":"chrome","engine_version":"107","result":null,"created_at":"2023-01-07T21:05:48+00:00","started_at":null,"ended_at":null,"retry_of_task_id":null,"copy_of_task_id":null,"user_id":61485254,"priority":-10,"host_name":null,"storage":"ceph-fra","depends_on_task_ids":[],"links":{"self":"https:\/\/api.cloudconvert.com\/v2\/tasks\/68d52633-e170-465e-b13e-746c97d01ffb"}}}'), new Response(201, ['Content-Type' => 'application/json'], '{"data":{"id":"27f33137-cc03-4468-aba4-1e1aa8c096fb","job_id":null,"status":"finished","credits":null,"code":null,"message":null,"percent":100,"operation":"export\/url","result":null,"created_at":"2023-01-07T21:10:02+00:00","started_at":null,"ended_at":null,"retry_of_task_id":null,"copy_of_task_id":null,"user_id":61485254,"priority":-10,"host_name":null,"storage":"ceph-fra","depends_on_task_ids":["68d52633-e170-465e-b13e-746c97d01ffb"],"links":{"self":"https:\/\/api.cloudconvert.com\/v2\/tasks\/27f33137-cc03-4468-aba4-1e1aa8c096fb"}}}'), - new Response(200, ['Content-Type' => 'image/png'], fopen(__DIR__ . '/../../theverge.com.png', 'rb')), + new Response(200, ['Content-Type' => 'image/png'], fopen(__DIR__.'/../../theverge.com.png', 'rb')), ]); $guzzleHandler = HandlerStack::create($guzzleMock); $guzzleClient = new Client(['handler' => $guzzleHandler]); @@ -45,7 +46,7 @@ class SaveScreenshotJobTest extends TestCase } // Retry connection exceptions - if ($exception instanceof \GuzzleHttp\Exception\ConnectException) { + if ($exception instanceof ConnectException) { return true; } @@ -82,7 +83,7 @@ class SaveScreenshotJobTest extends TestCase $bookmark->refresh(); $this->assertEquals('68d52633-e170-465e-b13e-746c97d01ffb', $bookmark->screenshot); - Storage::disk('public')->assertExists('/assets/img/bookmarks/' . $bookmark->screenshot . '.png'); + Storage::disk('public')->assertExists('/assets/img/bookmarks/'.$bookmark->screenshot.'.png'); } #[Test] @@ -92,7 +93,7 @@ class SaveScreenshotJobTest extends TestCase $guzzleMock = new MockHandler([ new Response(201, ['Content-Type' => 'application/json'], '{"id":1,"data":{"id":"68d52633-e170-465e-b13e-746c97d01ffb","job_id":null,"status":"waiting","credits":null,"code":null,"message":null,"percent":100,"operation":"capture-website","engine":"chrome","engine_version":"107","result":null,"created_at":"2023-01-07T21:05:48+00:00","started_at":null,"ended_at":null,"retry_of_task_id":null,"copy_of_task_id":null,"user_id":61485254,"priority":-10,"host_name":null,"storage":"ceph-fra","depends_on_task_ids":[],"links":{"self":"https:\/\/api.cloudconvert.com\/v2\/tasks\/68d52633-e170-465e-b13e-746c97d01ffb"}}}'), new Response(201, ['Content-Type' => 'application/json'], '{"id":2,"data":{"id":"27f33137-cc03-4468-aba4-1e1aa8c096fb","job_id":null,"status":"waiting","credits":null,"code":null,"message":null,"percent":100,"operation":"export\/url","result":null,"created_at":"2023-01-07T21:10:02+00:00","started_at":null,"ended_at":null,"retry_of_task_id":null,"copy_of_task_id":null,"user_id":61485254,"priority":-10,"host_name":null,"storage":"ceph-fra","depends_on_task_ids":["68d52633-e170-465e-b13e-746c97d01ffb"],"links":{"self":"https:\/\/api.cloudconvert.com\/v2\/tasks\/27f33137-cc03-4468-aba4-1e1aa8c096fb"}}}'), - new Response(200, ['Content-Type' => 'image/png'], fopen(__DIR__ . '/../../theverge.com.png', 'rb')), + new Response(200, ['Content-Type' => 'image/png'], fopen(__DIR__.'/../../theverge.com.png', 'rb')), ]); $guzzleHandler = HandlerStack::create($guzzleMock); $guzzleClient = new Client(['handler' => $guzzleHandler]); @@ -115,7 +116,7 @@ class SaveScreenshotJobTest extends TestCase } // Retry connection exceptions - if ($exception instanceof \GuzzleHttp\Exception\ConnectException) { + if ($exception instanceof ConnectException) { return true; } @@ -152,7 +153,7 @@ class SaveScreenshotJobTest extends TestCase $bookmark->refresh(); $this->assertEquals('68d52633-e170-465e-b13e-746c97d01ffb', $bookmark->screenshot); - Storage::disk('public')->assertExists('/assets/img/bookmarks/' . $bookmark->screenshot . '.png'); + Storage::disk('public')->assertExists('/assets/img/bookmarks/'.$bookmark->screenshot.'.png'); // Also assert we made the correct number of requests $this->assertCount(2, $container); // However with retries there should be more than 4 responses for the 2 requests diff --git a/tests/Unit/Jobs/SendWebMentionJobTest.php b/tests/Unit/Jobs/SendWebMentionJobTest.php index 7bfd24a1..29973328 100644 --- a/tests/Unit/Jobs/SendWebMentionJobTest.php +++ b/tests/Unit/Jobs/SendWebMentionJobTest.php @@ -29,7 +29,7 @@ class SendWebMentionJobTest extends TestCase { $url = 'https://example.org/webmention'; $mock = new MockHandler([ - new Response(200, ['Link' => '<' . $url . '>; rel="webmention"']), + new Response(200, ['Link' => '<'.$url.'>; rel="webmention"']), ]); $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); diff --git a/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php b/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php index e225cbf2..b90b621b 100644 --- a/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php +++ b/tests/Unit/Jobs/SyndicateNoteToBlueskyJobTest.php @@ -25,7 +25,7 @@ class SyndicateNoteToBlueskyJobTest extends TestCase $faker = Factory::create(); $randomNumber = $faker->randomNumber(); $mock = new MockHandler([ - new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber]), + new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/'.$randomNumber]), ]); $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); @@ -35,7 +35,7 @@ class SyndicateNoteToBlueskyJobTest extends TestCase $job->handle($client); $this->assertDatabaseHas('notes', [ - 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber, + 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/'.$randomNumber, ]); } @@ -49,7 +49,7 @@ class SyndicateNoteToBlueskyJobTest extends TestCase $container = []; $history = Middleware::history($container); $mock = new MockHandler([ - new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber]), + new Response(201, ['Location' => 'https://bsky.app/profile/jonnybarnes.uk/'.$randomNumber]), ]); $handler = HandlerStack::create($mock); $handler->push($history); @@ -60,7 +60,7 @@ class SyndicateNoteToBlueskyJobTest extends TestCase $job->handle($client); $this->assertDatabaseHas('notes', [ - 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/' . $randomNumber, + 'bluesky_url' => 'https://bsky.app/profile/jonnybarnes.uk/'.$randomNumber, ]); $expectedRequestContent = '{"type":["h-entry"],"properties":{"content":["This is a **test**"]}}'; diff --git a/tests/Unit/Jobs/SyndicateNoteToMastodonJobTest.php b/tests/Unit/Jobs/SyndicateNoteToMastodonJobTest.php index 47ecdd97..64c0cc90 100644 --- a/tests/Unit/Jobs/SyndicateNoteToMastodonJobTest.php +++ b/tests/Unit/Jobs/SyndicateNoteToMastodonJobTest.php @@ -25,7 +25,7 @@ class SyndicateNoteToMastodonJobTest extends TestCase $faker = Factory::create(); $randomNumber = $faker->randomNumber(); $mock = new MockHandler([ - new Response(201, ['Location' => 'https://mastodon.example/@jonny/' . $randomNumber]), + new Response(201, ['Location' => 'https://mastodon.example/@jonny/'.$randomNumber]), ]); $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); @@ -35,7 +35,7 @@ class SyndicateNoteToMastodonJobTest extends TestCase $job->handle($client); $this->assertDatabaseHas('notes', [ - 'mastodon_url' => 'https://mastodon.example/@jonny/' . $randomNumber, + 'mastodon_url' => 'https://mastodon.example/@jonny/'.$randomNumber, ]); } @@ -49,7 +49,7 @@ class SyndicateNoteToMastodonJobTest extends TestCase $container = []; $history = Middleware::history($container); $mock = new MockHandler([ - new Response(201, ['Location' => 'https://mastodon.example/@jonny/' . $randomNumber]), + new Response(201, ['Location' => 'https://mastodon.example/@jonny/'.$randomNumber]), ]); $handler = HandlerStack::create($mock); $handler->push($history); @@ -60,7 +60,7 @@ class SyndicateNoteToMastodonJobTest extends TestCase $job->handle($client); $this->assertDatabaseHas('notes', [ - 'mastodon_url' => 'https://mastodon.example/@jonny/' . $randomNumber, + 'mastodon_url' => 'https://mastodon.example/@jonny/'.$randomNumber, ]); $expectedRequestContent = '{"type":["h-entry"],"properties":{"content":["This is a **test**"]}}'; diff --git a/tests/Unit/MediaTest.php b/tests/Unit/MediaTest.php index 675f761d..baf48500 100644 --- a/tests/Unit/MediaTest.php +++ b/tests/Unit/MediaTest.php @@ -7,6 +7,7 @@ namespace Tests\Unit; use App\Models\Media; use App\Models\Note; use Illuminate\Foundation\Testing\RefreshDatabase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; @@ -38,4 +39,68 @@ class MediaTest extends TestCase $this->assertEquals($absoluteUrl, $media->url); } + + #[Test] + public function local_paths_get_storage_url_prepended(): void + { + $media = new Media; + $media->path = 'photo.jpg'; + + $this->assertEquals(config('app.url').'/storage/photo.jpg', $media->url); + } + + #[Test] + public function medium_url_returns_resized_path(): void + { + $media = new Media; + $media->path = 'photo.jpg'; + + $this->assertEquals(config('app.url').'/storage/photo-medium.jpg', $media->mediumurl); + } + + #[Test] + public function small_url_returns_resized_path(): void + { + $media = new Media; + $media->path = 'photo.jpg'; + + $this->assertEquals(config('app.url').'/storage/photo-small.jpg', $media->smallurl); + } + + #[Test] + public function size_url_handles_dotted_basename(): void + { + $media = new Media; + $media->path = 'file.name.png'; + + $this->assertEquals(config('app.url').'/storage/file.name-medium.png', $media->mediumurl); + } + + /** + * @dataProvider mimeTypeProvider + */ + #[DataProvider('mimeTypeProvider')] + public function mimetype_returns_correct_mime_for_extension(string $path, string $expected): void + { + $media = new Media; + $media->path = $path; + + $this->assertEquals($expected, $media->mimetype); + } + + public static function mimeTypeProvider(): array + { + return [ + ['photo.gif', 'image/gif'], + ['photo.jpeg', 'image/jpeg'], + ['photo.jpg', 'image/jpeg'], + ['photo.png', 'image/png'], + ['photo.svg', 'image/svg+xml'], + ['photo.tiff', 'image/tiff'], + ['photo.webp', 'image/webp'], + ['video.mp4', 'video/mp4'], + ['video.mkv', 'video/mkv'], + ['file.bin', 'application/octet-stream'], + ]; + } } diff --git a/tests/Unit/MicropubUpdateDataTest.php b/tests/Unit/MicropubUpdateDataTest.php new file mode 100644 index 00000000..46eabdb9 --- /dev/null +++ b/tests/Unit/MicropubUpdateDataTest.php @@ -0,0 +1,110 @@ + 'application/json', + ], json_encode([ + 'token_data' => ['me' => 'https://example.com'], + 'url' => 'https://example.com/notes/1', + 'replace' => ['content' => ['new content']], + 'add' => ['syndication' => ['https://twitter.com']], + 'delete' => ['category' => ['old-tag']], + ], JSON_THROW_ON_ERROR)); + + $dto = UpdateData::fromRequest($request); + + $this->assertEquals(['me' => 'https://example.com'], $dto->tokenData); + $this->assertEquals('https://example.com/notes/1', $dto->updateUrl); + $this->assertEquals(['content' => ['new content']], $dto->updateReplace); + $this->assertEquals(['syndication' => ['https://twitter.com']], $dto->updateAdd); + $this->assertEquals(['category' => ['old-tag']], $dto->updateDelete); + } + + #[Test] + public function from_request_parses_form_encoded_body(): void + { + $request = Request::create('/micropub', 'POST', [ + 'token_data' => ['me' => 'https://example.com'], + 'url' => 'https://example.com/notes/1', + 'replace' => ['content' => ['new content']], + 'add' => ['syndication' => ['https://twitter.com']], + 'delete' => ['category' => ['old-tag']], + ]); + + $dto = UpdateData::fromRequest($request); + + $this->assertEquals(['me' => 'https://example.com'], $dto->tokenData); + $this->assertEquals('https://example.com/notes/1', $dto->updateUrl); + $this->assertEquals(['content' => ['new content']], $dto->updateReplace); + $this->assertEquals(['syndication' => ['https://twitter.com']], $dto->updateAdd); + $this->assertEquals(['category' => ['old-tag']], $dto->updateDelete); + } + + /** + * @throws JsonException + */ + #[Test] + public function from_request_nullable_fields_default_to_null(): void + { + $request = Request::create('/micropub', 'POST', [], [], [], [ + 'CONTENT_TYPE' => 'application/json', + ], json_encode([ + 'token_data' => ['me' => 'https://example.com'], + 'url' => 'https://example.com/notes/1', + ], JSON_THROW_ON_ERROR)); + + $dto = UpdateData::fromRequest($request); + + $this->assertNull($dto->updateReplace); + $this->assertNull($dto->updateAdd); + $this->assertNull($dto->updateDelete); + } + + #[Test] + public function from_array_maps_all_fields(): void + { + $dto = UpdateData::fromArray([ + 'token_data' => ['me' => 'https://example.com'], + 'update_url' => 'https://example.com/notes/1', + 'update_replace' => ['content' => ['updated']], + 'update_add' => ['category' => ['new-tag']], + 'update_delete' => ['syndication' => ['https://old.example.com']], + ]); + + $this->assertEquals(['me' => 'https://example.com'], $dto->tokenData); + $this->assertEquals('https://example.com/notes/1', $dto->updateUrl); + $this->assertEquals(['content' => ['updated']], $dto->updateReplace); + $this->assertEquals(['category' => ['new-tag']], $dto->updateAdd); + $this->assertEquals(['syndication' => ['https://old.example.com']], $dto->updateDelete); + } + + #[Test] + public function from_array_nullable_fields_default_to_null(): void + { + $dto = UpdateData::fromArray([ + 'token_data' => ['me' => 'https://example.com'], + ]); + + $this->assertNull($dto->updateUrl); + $this->assertNull($dto->updateReplace); + $this->assertNull($dto->updateAdd); + $this->assertNull($dto->updateDelete); + } +} diff --git a/tests/Unit/NotesTest.php b/tests/Unit/NotesTest.php index 330d93fe..0778ce2a 100644 --- a/tests/Unit/NotesTest.php +++ b/tests/Unit/NotesTest.php @@ -31,7 +31,7 @@ class NotesTest extends TestCase public function get_note_attribute_method_calls_sub_methods(): void { // phpcs:ignore - $expected = '

Having a at the local. 🍺

' . PHP_EOL; + $expected = '

Having a at the local. 🍺

'.PHP_EOL; $note = Note::factory([ 'note' => 'Having a #beer at the local. 🍺', ])->create(); @@ -53,7 +53,7 @@ class NotesTest extends TestCase t -

' . PHP_EOL; +

'.PHP_EOL; Contact::factory()->create([ 'nick' => 'tantek', 'name' => 'Tantek Çelik', @@ -98,7 +98,7 @@ class NotesTest extends TestCase Facebook -

' . PHP_EOL; +

'.PHP_EOL; $this->assertEquals($expected, $note->note); } @@ -108,7 +108,7 @@ class NotesTest extends TestCase #[Test] public function twitter_link_is_created_when_no_contact_found(): void { - $expected = '

Hi @bob

' . PHP_EOL; + $expected = '

Hi @bob

'.PHP_EOL; $note = Note::factory()->create([ 'note' => 'Hi @bob', ]); @@ -312,7 +312,7 @@ class NotesTest extends TestCase $note->media()->save($media); $expected = 'A nice image -'; +'; $this->assertEquals($expected, $note->content); } @@ -329,7 +329,7 @@ class NotesTest extends TestCase $note->media()->save($media); $expected = 'A nice video -