Merge pull request 'MTM Redesign 2025.1' (#57) from develop into main
Reviewed-on: #57
This commit is contained in:
commit
4409e42029
40 changed files with 2441 additions and 1553 deletions
|
|
@ -4,6 +4,7 @@ use App\Http\Middleware\LinkHeadersMiddleware;
|
|||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Spatie\LaravelFlare\Facades\Flare;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
|
|
@ -24,5 +25,5 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
Flare::handles($exceptions);
|
||||
})->create();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
"mf2/mf2": "~0.3",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"spatie/commonmark-highlighter": "^3.0",
|
||||
"spatie/laravel-ignition": "^2.1",
|
||||
"spatie/laravel-flare": "^2.2",
|
||||
"symfony/html-sanitizer": "^7.0",
|
||||
"symfony/property-access": "^7.0",
|
||||
"symfony/serializer": "^7.0",
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"openai-php/client": "^0.10.1",
|
||||
"openai-php/client": "^0.17.1",
|
||||
"phpunit/php-code-coverage": "^11.0",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"spatie/laravel-ray": "^1.12",
|
||||
|
|
|
|||
2098
composer.lock
generated
2098
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,7 +12,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'enabled' => null,
|
||||
'enabled' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
206
config/flare.php
Normal file
206
config/flare.php
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
use Spatie\FlareClient\Api;
|
||||
use Spatie\LaravelFlare\AttributesProviders\LaravelUserAttributesProvider;
|
||||
use Spatie\LaravelFlare\FlareConfig;
|
||||
|
||||
return [
|
||||
/*
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
| Flare API key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify Flare's API key below to enable error reporting to the service.
|
||||
|
|
||||
| More info: https://flareapp.io/docs/flare/general/getting-started
|
||||
|
|
||||
*/
|
||||
|
||||
'key' => env('FLARE_KEY'),
|
||||
|
||||
/*
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
| Flare Base URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Which server should be used to send the reports/traces to.
|
||||
|
|
||||
*/
|
||||
'base_url' => env('FLARE_BASE_URL', Api::BASE_URL),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Collects
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Flare will collect a lot of information about your application. You can
|
||||
| disable some of the collectors here, configure them or add your own.
|
||||
|
|
||||
*/
|
||||
|
||||
'collects' => FlareConfig::defaultCollects(
|
||||
ignore: [],
|
||||
extra: []
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Attribute providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When sending an error report or trace to Flare attributes can be added to
|
||||
| the report or trace for common entries. An example of such an entry is
|
||||
| the currently authenticated user. In an attribute provider you can
|
||||
| specify which attributes should be sent.
|
||||
|
|
||||
*/
|
||||
|
||||
'attribute_providers' => [
|
||||
'user' => LaravelUserAttributesProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Censor data
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| It is possible to censor sensitive data from the reports and sent to
|
||||
| Flare. Below you can specify which fields and header should be
|
||||
| censored. It is also possible to hide the client's IP address.
|
||||
|
|
||||
*/
|
||||
|
||||
'censor' => [
|
||||
'body_fields' => [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
],
|
||||
'headers' => [
|
||||
'API-KEY',
|
||||
'Authorization',
|
||||
'Cookie',
|
||||
'Set-Cookie',
|
||||
'X-CSRF-TOKEN',
|
||||
'X-XSRF-TOKEN',
|
||||
],
|
||||
'client_ips' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reporting log statements
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If this setting is `false` log statements won't be sent as events to Flare,
|
||||
| no matter which error level you specified in the Flare log channel.
|
||||
|
|
||||
*/
|
||||
|
||||
'send_logs_as_events' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Report error levels
|
||||
|--------------------------------------------------------------------------
|
||||
| When reporting errors, you can specify which error levels should be
|
||||
| reported. By default, all error levels are reported by setting
|
||||
| this value to `null`.
|
||||
*/
|
||||
|
||||
'report_error_levels' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Share button
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Flare automatically adds a Share button to the laravel error page. This
|
||||
| button allows you to easily share errors with colleagues or friends. It
|
||||
| is enabled by default, but you can disable it here.
|
||||
|
|
||||
*/
|
||||
|
||||
'enable_share_button' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Override grouping
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Flare will try to group errors and exceptions as best as possible, that
|
||||
| being said, sometimes you might want to override the grouping. You can
|
||||
| do this by adding exception classes to this array which should always
|
||||
| be grouped by exception class, exception message or exception class
|
||||
| and message.
|
||||
|
|
||||
*/
|
||||
|
||||
'overridden_groupings' => [
|
||||
// Illuminate\Http\Client\ConnectionException::class => Spatie\FlareClient\Enums\OverriddenGrouping::ExceptionMessageAndClass,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sender
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The sender is responsible for sending the error reports and traces to
|
||||
| Flare it can be configured if needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'sender' => [
|
||||
'class' => \Spatie\LaravelFlare\Senders\LaravelHttpSender::class,
|
||||
'config' => [
|
||||
'timeout' => 10,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Trace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Tracing allows you to see the flow of your application. It shows you
|
||||
| which parts of your application are slow and which parts are fast.
|
||||
|
|
||||
*/
|
||||
|
||||
'trace' => env('FLARE_TRACE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sampler
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The sampler is used to determine which traces should be recorded and
|
||||
| which traces should be dropped. It is possible to set the rate
|
||||
| at which traces should be recorded. The default rate is 0.1
|
||||
| which means that 10% of the traces will be recorded.
|
||||
|
|
||||
*/
|
||||
'sampler' => [
|
||||
'class' => \Spatie\FlareClient\Sampling\RateSampler::class,
|
||||
'config' => [
|
||||
'rate' => 0.1,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Trace limits
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Limits for the tracing data. These limits are used to prevent
|
||||
| the tracing data from growing too large.
|
||||
|
|
||||
*/
|
||||
'trace_limits' => [
|
||||
'max_spans' => 512,
|
||||
'max_attributes_per_span' => 128,
|
||||
'max_span_events_per_span' => 128,
|
||||
'max_attributes_per_span_event' => 128,
|
||||
],
|
||||
];
|
||||
812
package-lock.json
generated
812
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
|
@ -8,7 +8,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.6.0",
|
||||
"@stylistic/eslint-plugin": "^5.1.0",
|
||||
"esbuild": "^0.25.2",
|
||||
"esbuild": "^0.27.1",
|
||||
"eslint": "^9.7.0",
|
||||
"globals": "^16.0.0",
|
||||
"lightningcss": "^1.29.3",
|
||||
|
|
@ -17,13 +17,18 @@
|
|||
"stylelint-config-standard": "^39.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"eslint": "eslint public/assets/js/*.js",
|
||||
"eslint": "eslint resources/js/*.js",
|
||||
"stylelint": "stylelint resources/css/*.css",
|
||||
"lint": "npm run eslint && npm run stylelint",
|
||||
"lightningcss": "lightningcss --output-dir public/assets/css --sourcemap --bundle --minify resources/css/app.css",
|
||||
"fix-sourcemap": "./scripts/fix-sourcemap.sh",
|
||||
"build-css": "npm run lightningcss && npm run fix-sourcemap",
|
||||
"build-js": "esbuild resources/js/app.js --bundle --minify --sourcemap --outfile=public/assets/js/app.js",
|
||||
"compress": "./scripts/compress.sh",
|
||||
"build": "npm run lint && npm run compress"
|
||||
"build": "npm run lint && npm run build-css && npm run build-js && npm run compress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@11ty/is-land": "^5.0.0",
|
||||
"@zachleat/snow-fall": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
:root{--font-family-headings:"Rockwell","Rockwell Nova","Roboto Slab","DejaVu Serif","Sitka Small",serif;--font-family-body:"Charter","Bitstream Charter","Sitka Text","Cambria",serif;--font-family-monospace:ui-monospace,"Cascadia Code","Source Code Pro","Menlo","Consolas","DejaVu Sans Mono",monospace;--font-size-sm:.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:oklch(36.8% .1 125.505);--color-secondary:oklch(96.3% .1 125.505);--color-link:oklch(48.09% .146 241.41);--color-link-visited:oklch(70.44% .21 304.41);--color-primary-shadow:oklch(19.56% .054 125.505/.4);--rss-color-link:oklch(67.59% .189 42.04);--color-danger:oklch(64.41% .281 23.29);--color-danger-shadow:oklch(64.41% .281 23.29/.1)}body{font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{grid-template-rows:min-content 1fr min-content;grid-template-columns:5vw 1fr 5vw;row-gap:1rem;display:grid}#site-header{grid-area:1/2/2/3;& .rss-icon{& svg{width:auto;height:1rem}}}main{grid-area:2/2/3/3}.h-feed{flex-direction:column;gap:2rem;display:flex}.h-entry{& p:first-of-type,& h1:first-of-type{margin-block-start:0}}.pagination{margin-block-start:1rem}footer{grid-area:3/2/4/3;& .iwc-logo{max-width:85vw}& .footer-actions{flex-direction:row;gap:1rem;display:flex}}body{background-color:var(--color-secondary);color:var(--color-primary)}a{color:var(--color-link);&:visited{color:var(--color-link-visited)}&.auth:visited{color:var(--color-link)}}#site-header{& a:visited{color:var(--color-link)}& .rss-icon{& svg{color:var(--rss-color-link)}}}.hljs{border-radius:.5rem}.h-card{& .hovercard{z-index:100;box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);background-color:var(--color-secondary);opacity:0;border-radius:1rem;flex-direction:column;gap:.5rem;width:fit-content;padding:1rem;transition:opacity .5s ease-in-out;display:none;position:absolute;& .u-photo{max-width:6rem}& .social-icon{width:1rem;height:1rem}}&:hover{& .hovercard{opacity:1;display:flex}}}.h-entry{border-inline-start:1px solid var(--color-primary);padding-inline-start:.5rem;& .reply-to{font-style:italic}& .post-info{& a{text-decoration:none}}& .note-metadata{flex-direction:row;gap:1rem;display:flex;& .replies,& .likes,& .reposts{flex-direction:row;align-items:center;gap:.5rem;display:inline-flex}& .syndication-links{flex-flow:wrap;& a{text-decoration:none;& svg{width:1rem;height:1rem}}}}}.feather{stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;fill:none;width:24px;height:24px}.sr-only{clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}main{&>.u-comment{border-inline-start:1px solid var(--color-primary);margin-block-start:2rem;margin-inline-start:2rem;padding-inline-start:.5rem;& .mini-h-card{flex-direction:row;align-items:baseline;display:inline-flex;& .u-photo{border-radius:50%;width:2rem;height:2rem;margin-block-end:.5rem}}}& .notes-subtitle{font-size:1.2rem;font-weight:600}& .webmentions-author-list{flex-flow:wrap;gap:1rem;display:flex;& img{border-radius:50%;width:4rem;height:4rem}}}.indieauth{& .error{color:var(--color-danger);background-color:var(--color-danger-shadow);border:1px solid var(--color-danger);border-radius:.5rem;width:fit-content;margin-block-end:1rem;padding-block:.5rem;padding-inline:1rem;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%;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}}& .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 */
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,2 +1,2 @@
|
|||
(()=>{var i=class{constructor(){}async register(){let n=await this.getCreateOptions(),e={challenge:this.base64URLStringToBuffer(n.challenge),rp:{id:n.rp.id,name:n.rp.name},user:{id:new TextEncoder().encode(window.atob(n.user.id)),name:n.user.name,displayName:n.user.displayName},pubKeyCredParams:n.pubKeyCredParams,excludeCredentials:[],authenticatorSelection:n.authenticatorSelection,timeout:6e4},t=await navigator.credentials.create({publicKey:e});if(!t)throw new Error("Error generating a passkey");let r={id:t.id?t.id:null,type:t.type?t.type:null,rawId:t.rawId?this.bufferToBase64URLString(t.rawId):null,response:{attestationObject:t.response.attestationObject?this.bufferToBase64URLString(t.response.attestationObject):null,clientDataJSON:t.response.clientDataJSON?this.bufferToBase64URLString(t.response.clientDataJSON):null}};if(!(await window.fetch("/admin/passkeys/register",{method:"POST",body:JSON.stringify(r),cache:"no-cache",headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Error saving the passkey");window.location.reload()}async getCreateOptions(){return await(await fetch("/admin/passkeys/register",{method:"GET"})).json()}async login(){let n=await this.getLoginData(),e=await navigator.credentials.get({publicKey:{challenge:this.base64URLStringToBuffer(n.challenge),userVerification:n.userVerification,timeout:6e4}});if(!e)throw new Error("Authentication failed");let t={id:e.id?e.id:"",type:e.type?e.type:"",rawId:e.rawId?this.bufferToBase64URLString(e.rawId):"",response:{authenticatorData:e.response.authenticatorData?this.bufferToBase64URLString(e.response.authenticatorData):"",clientDataJSON:e.response.clientDataJSON?this.bufferToBase64URLString(e.response.clientDataJSON):"",signature:e.response.signature?this.bufferToBase64URLString(e.response.signature):"",userHandle:e.response.userHandle?this.bufferToBase64URLString(e.response.userHandle):""}};if(!(await window.fetch("/login/passkey",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Login failed");window.location.assign("/admin")}async getLoginData(){return await(await fetch("/login/passkey",{method:"GET"})).json()}base64URLStringToBuffer(n){let e=n.replace(/-/g,"+").replace(/_/g,"/"),t=(4-e.length%4)%4,r=e.padEnd(e.length+t,"="),a=window.atob(r),c=new ArrayBuffer(a.length),d=new Uint8Array(c);for(let o=0;o<a.length;o++)d[o]=a.charCodeAt(o);return c}bufferToBase64URLString(n){let e=new Uint8Array(n),t="";for(let a of e)t+=String.fromCharCode(a);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}};var l=new i;document.querySelectorAll(".add-passkey").forEach(s=>{s.addEventListener("click",()=>{l.register()})});document.querySelectorAll(".login-passkey").forEach(s=>{s.addEventListener("click",()=>{l.login()})});})();
|
||||
(()=>{var l=class{constructor(){}async register(){let r=await this.getCreateOptions(),e={challenge:this.base64URLStringToBuffer(r.challenge),rp:{id:r.rp.id,name:r.rp.name},user:{id:new TextEncoder().encode(window.atob(r.user.id)),name:r.user.name,displayName:r.user.displayName},pubKeyCredParams:r.pubKeyCredParams,excludeCredentials:[],authenticatorSelection:r.authenticatorSelection,timeout:6e4},t=await navigator.credentials.create({publicKey:e});if(!t)throw new Error("Error generating a passkey");let n={id:t.id?t.id:null,type:t.type?t.type:null,rawId:t.rawId?this.bufferToBase64URLString(t.rawId):null,response:{attestationObject:t.response.attestationObject?this.bufferToBase64URLString(t.response.attestationObject):null,clientDataJSON:t.response.clientDataJSON?this.bufferToBase64URLString(t.response.clientDataJSON):null}};if(!(await window.fetch("/admin/passkeys/register",{method:"POST",body:JSON.stringify(n),cache:"no-cache",headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Error saving the passkey");window.location.reload()}async getCreateOptions(){return await(await fetch("/admin/passkeys/register",{method:"GET"})).json()}async login(){let r=await this.getLoginData(),e=await navigator.credentials.get({publicKey:{challenge:this.base64URLStringToBuffer(r.challenge),userVerification:r.userVerification,timeout:6e4}});if(!e)throw new Error("Authentication failed");let t={id:e.id?e.id:"",type:e.type?e.type:"",rawId:e.rawId?this.bufferToBase64URLString(e.rawId):"",response:{authenticatorData:e.response.authenticatorData?this.bufferToBase64URLString(e.response.authenticatorData):"",clientDataJSON:e.response.clientDataJSON?this.bufferToBase64URLString(e.response.clientDataJSON):"",signature:e.response.signature?this.bufferToBase64URLString(e.response.signature):"",userHandle:e.response.userHandle?this.bufferToBase64URLString(e.response.userHandle):""}};if(!(await window.fetch("/login/passkey",{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json","X-CSRF-TOKEN":document.querySelector('meta[name="csrf-token"]').getAttribute("content")}})).ok)throw new Error("Login failed");window.location.assign("/admin")}async getLoginData(){return await(await fetch("/login/passkey",{method:"GET"})).json()}base64URLStringToBuffer(r){let e=r.replace(/-/g,"+").replace(/_/g,"/"),t=(4-e.length%4)%4,n=e.padEnd(e.length+t,"="),a=window.atob(n),o=new ArrayBuffer(a.length),i=new Uint8Array(o);for(let s=0;s<a.length;s++)i[s]=a.charCodeAt(s);return o}bufferToBase64URLString(r){let e=new Uint8Array(r),t="";for(let a of e)t+=String.fromCharCode(a);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}};var d=class{constructor(){this.widget=document.querySelector("#theme-selector")}setupEventListeners(){this.widget.querySelectorAll("#theme-selector-dropdown input").forEach(r=>r.addEventListener("input",e=>{let t=e.target.value;this.widget.querySelectorAll(".toggle svg").forEach(u=>{u.classList.contains(t)?u.style.display="":u.style.display="none"});let n;switch(t){case"dark":case"light":n=t;break;default:n="light dark"}let a=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light",o=document.querySelector("html"),i=o.style.getPropertyValue("color-scheme");i===""&&(i="light dark");let s=!1;n!==i&&(n==="light dark"?s=i!==a:i==="light dark"?s=n!==a:s=!0),o.style.viewTransitionName="changing-theme",s&&document.startViewTransition?document.startViewTransition(()=>{document.querySelector("#theme-selector-dropdown").togglePopover(),o.style.setProperty("color-scheme",n)}):(document.querySelector("#theme-selector-dropdown").togglePopover(),o.style.setProperty("color-scheme",n))}))}};var h=new l;document.querySelectorAll(".add-passkey").forEach(c=>{c.addEventListener("click",()=>{h.register()})});document.querySelectorAll(".login-passkey").forEach(c=>{c.addEventListener("click",()=>{h.login()})});var p=new d;p.setupEventListeners();})();
|
||||
//# sourceMappingURL=app.js.map
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
2
public/assets/js/is-land.min.js
vendored
Normal file
2
public/assets/js/is-land.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
public/assets/js/is-land.min.js.br
Normal file
BIN
public/assets/js/is-land.min.js.br
Normal file
Binary file not shown.
BIN
public/assets/js/is-land.min.js.zst
Normal file
BIN
public/assets/js/is-land.min.js.zst
Normal file
Binary file not shown.
125
public/assets/js/snow-fall.js
Normal file
125
public/assets/js/snow-fall.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
class Snow extends HTMLElement {
|
||||
static random(min, max) {
|
||||
return min + Math.floor(Math.random() * (max - min) + 1);
|
||||
}
|
||||
|
||||
static attrs = {
|
||||
count: "count", // default: 100
|
||||
mode: "mode",
|
||||
text: "text", // text in snow flake (emoji, too)
|
||||
}
|
||||
|
||||
generateCss(mode, count) {
|
||||
let css = [];
|
||||
css.push(`
|
||||
:host([mode="element"]) {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
:host([mode="page"]) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
:host([mode="page"]),
|
||||
:host([mode="element"]) > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
:host([mode="element"]) ::slotted(*) {
|
||||
pointer-events: all;
|
||||
}
|
||||
* {
|
||||
position: absolute;
|
||||
}
|
||||
:host([text]) * {
|
||||
font-size: var(--snow-fall-size, 1em);
|
||||
}
|
||||
:host(:not([text])) * {
|
||||
width: var(--snow-fall-size, 10px);
|
||||
height: var(--snow-fall-size, 10px);
|
||||
background: var(--snow-fall-color, rgba(255,255,255,.5));
|
||||
border-radius: 50%;
|
||||
}
|
||||
`);
|
||||
|
||||
// using vw units (max 100)
|
||||
let dimensions = { width: 100, height: 100 };
|
||||
let units = { x: "vw", y: "vh"};
|
||||
|
||||
if(mode === "element") {
|
||||
dimensions = {
|
||||
width: this.firstElementChild.clientWidth,
|
||||
height: this.firstElementChild.clientHeight,
|
||||
};
|
||||
units = { x: "px", y: "px"};
|
||||
}
|
||||
|
||||
// Thank you @alphardex: https://codepen.io/alphardex/pen/dyPorwJ
|
||||
for(let j = 1; j<= count; j++ ) {
|
||||
let x = Snow.random(1, 100) * dimensions.width/100; // vw
|
||||
let offset = Snow.random(-10, 10) * dimensions.width/100; // vw
|
||||
|
||||
let yoyo = Math.round(Snow.random(30, 100)); // % time
|
||||
let yStart = yoyo * dimensions.height/100; // vh
|
||||
let yEnd = dimensions.height; // vh
|
||||
|
||||
let scale = Snow.random(1, 10000) * .0001;
|
||||
let duration = Snow.random(10, 30);
|
||||
let delay = Snow.random(0, 30) * -1;
|
||||
|
||||
css.push(`
|
||||
:nth-child(${j}) {
|
||||
opacity: ${Snow.random(0, 1000) * 0.001};
|
||||
transform: translate(${x}${units.x}, -10px) scale(${scale});
|
||||
animation: fall-${j} ${duration}s ${delay}s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes fall-${j} {
|
||||
${yoyo}% {
|
||||
transform: translate(${x + offset}${units.x}, ${yStart}${units.y}) scale(${scale});
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate(${x + offset / 2}${units.x}, ${yEnd}${units.y}) scale(${scale});
|
||||
}
|
||||
}`)
|
||||
}
|
||||
return css.join("\n");
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// https://caniuse.com/mdn-api_cssstylesheet_replacesync
|
||||
if(this.shadowRoot || !("replaceSync" in CSSStyleSheet.prototype)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = parseInt(this.getAttribute(Snow.attrs.count)) || 100;
|
||||
|
||||
let mode;
|
||||
if(this.hasAttribute(Snow.attrs.mode)) {
|
||||
mode = this.getAttribute(Snow.attrs.mode);
|
||||
} else {
|
||||
mode = this.firstElementChild ? "element" : "page";
|
||||
this.setAttribute(Snow.attrs.mode, mode);
|
||||
}
|
||||
|
||||
let sheet = new CSSStyleSheet();
|
||||
sheet.replaceSync(this.generateCss(mode, count));
|
||||
|
||||
let shadowroot = this.attachShadow({ mode: "open" });
|
||||
shadowroot.adoptedStyleSheets = [sheet];
|
||||
|
||||
let d = document.createElement("div");
|
||||
let text = this.getAttribute(Snow.attrs.text);
|
||||
d.innerText = text || "";
|
||||
for(let j = 0, k = count; j<k; j++) {
|
||||
shadowroot.appendChild(d.cloneNode(true));
|
||||
}
|
||||
|
||||
shadowroot.appendChild(document.createElement("slot"));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("snow-fall", Snow);
|
||||
BIN
public/assets/js/snow-fall.js.br
Normal file
BIN
public/assets/js/snow-fall.js.br
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue