Compare commits

...

7 commits

10 changed files with 79 additions and 18 deletions

View file

@ -37,7 +37,7 @@ header div {
} }
} }
header nav { header menu {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
@ -151,7 +151,7 @@ header details {
} }
} }
.components__header__nav_button { .components__header__menu_button {
color: var(--_font-color); color: var(--_font-color);
display: inline-block; display: inline-block;
text-decoration: none; text-decoration: none;

View file

@ -15,21 +15,21 @@ export function Header() {
<span class='components__header__brand__text'>bit</span> <span class='components__header__brand__text'>bit</span>
</a> </a>
</span> </span>
<nav> <menu>
<li> <li>
<a href='/machines' class='components__header__nav_button'> <a href='/machines' class='components__header__menu_button'>
<i class='ri-hammer-line'></i> <i class='ri-hammer-line'></i>
<span>Machines</span> <span>Machines</span>
</a> </a>
</li> </li>
<li> <li>
<a href='/projets' class='components__header__nav_button'> <a href='/projets' class='components__header__menu_button'>
<i class='ri-organization-chart'></i> <i class='ri-organization-chart'></i>
<span>Projets</span> <span>Projets</span>
</a> </a>
</li> </li>
<li> <li>
<a href='/blog' class='components__header__nav_button'> <a href='/blog' class='components__header__menu_button'>
<i class='ri-question-answer-line'></i> <i class='ri-question-answer-line'></i>
<span>Blog</span> <span>Blog</span>
</a> </a>
@ -53,11 +53,11 @@ export function Header() {
</ul> </ul>
</details> </details>
</li> </li>
</nav> </menu>
<SearchBox /> <SearchBox />
<MoreBox> <MoreBox>
<ThemePicker /> <ThemePicker />
<a href='/profil'> <a href='/profil' title={'Accéder à mon compte'}>
<i class='ri-user-line'></i> <i class='ri-user-line'></i>
</a> </a>
<AiChatBox /> <AiChatBox />

View file

@ -14,7 +14,11 @@ export function ProjectCard(
) { ) {
return ( return (
<div class='components__project_card'> <div class='components__project_card'>
<img src={icon} class='components__project_card__icon' /> <img
alt='Icon du projet'
src={icon}
class='components__project_card__icon'
/>
<div class='components__project_card__content'> <div class='components__project_card__content'>
<h3>{title}</h3> <h3>{title}</h3>
<div class='components__project_card__state'> <div class='components__project_card__state'>

View file

@ -88,6 +88,7 @@ export default function AiChatBox() {
<button <button
class='islands__ai_chat_box__button' class='islands__ai_chat_box__button'
onClick={() => dialog.current?.showModal()} onClick={() => dialog.current?.showModal()}
title={'Ouvrir la chat box IA'}
> >
<i class='ri-bard-line'></i> <i class='ri-bard-line'></i>
</button> </button>

View file

@ -17,6 +17,7 @@ export default function MoreBox({ children }: { children: VNode | VNode[] }) {
<button <button
class='islands__more_box__button' class='islands__more_box__button'
onClick={() => dialog.current?.showModal()} onClick={() => dialog.current?.showModal()}
title={"Afficher plus d'options"}
> >
<i class='ri-more-2-line'></i> <i class='ri-more-2-line'></i>
</button> </button>

View file

@ -16,12 +16,19 @@ export default function SearchBox() {
<button <button
class='islands__search_box__button' class='islands__search_box__button'
onClick={() => dialog.current?.showModal()} onClick={() => dialog.current?.showModal()}
title={'Ouvrir la recherche'}
> >
<i class='ri-search-line'></i> <i class='ri-search-line'></i>
<span>&nbsp;Rechercher</span> <span>&nbsp;Rechercher</span>
</button> </button>
<dialog ref={dialog} class='islands__search_box__dialog'> <dialog ref={dialog} class='islands__search_box__dialog'>
<input type='search' name='' id='' placeholder='Rechercher' autoFocus /> <input
type='search'
name=''
id=''
placeholder='Rechercher'
autoFocus
/>
<div class='islands__search_box__dialog__content'> <div class='islands__search_box__dialog__content'>
<h3>Machines</h3> <h3>Machines</h3>
<ul> <ul>

View file

@ -39,7 +39,7 @@ export default function ThemePicker() {
}) })
return ( return (
<label class='islands__theme_picker'> <label class='islands__theme_picker' title={'Sélectionner un thème'}>
<span>{themeIcon}</span> <span>{themeIcon}</span>
<select <select
value={theme} value={theme}

View file

@ -6,10 +6,28 @@ export async function handler(request: Request, ctx: FreshContext) {
// Update fresh context state with session // Update fresh context state with session
ctx.state = { ...ctx.state, session: SessionStore.getFromRequest(request) } ctx.state = { ...ctx.state, session: SessionStore.getFromRequest(request) }
// Allow service worker to serve root scope // Get response
const response = await ctx.next() const response = await ctx.next()
const url = new URL(request.url)
if (url.pathname.endsWith('island-startserviceworker.js')) { //Add security headers
// See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#http_strict_transport_security
response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload')
response.headers.set('Content-Security-Policy', "frame-ancestors 'none'; upgrade-insecure-requests")
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Referrer_policy
response.headers.set('Referrer-Policy', 'no-referrer, strict-origin-when-cross-origin')
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/MIME_types
response.headers.set('X-Content-Type-Options', 'nosniff')
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Clickjacking
response.headers.set('X-Frame-Options', 'DENY')
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CORP
response.headers.set('Cross-Origin-Resource-Policy', 'same-origin')
//See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
//? SRI plugin for non local resources only ?
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSP
//? fresh useCSP https://fresh.deno.dev/docs/examples/using-csp
// Allow service worker to serve root scope
if (ctx.url.pathname.endsWith('island-startserviceworker.js')) {
response.headers.set('Service-Worker-Allowed', '/') response.headers.set('Service-Worker-Allowed', '/')
} }
@ -31,7 +49,7 @@ export async function handler(request: Request, ctx: FreshContext) {
// Set session cookie // Set session cookie
setCookie(response.headers, { setCookie(response.headers, {
name: '_SESSION', name: '__Secure-SESSION',
value: session.uuid, value: session.uuid,
httpOnly: true, httpOnly: true,
sameSite: 'Strict', sameSite: 'Strict',
@ -45,7 +63,7 @@ export async function handler(request: Request, ctx: FreshContext) {
session.set('_csrf', csrf) session.set('_csrf', csrf)
setCookie(response.headers, { setCookie(response.headers, {
name: '_CSRF', name: '__Host-CSRF',
value: csrf, value: csrf,
httpOnly: false, httpOnly: false,
sameSite: 'Strict', sameSite: 'Strict',

View file

@ -58,6 +58,16 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
@media (prefers-contrast: more) {
:root {
--_font-color: black;
--_accent-color: var(--lime-12);
--_translucent: var(--sand-2);
--_background-color: white;
--_background-image: '';
}
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root:has(body:not([data-theme='light'])) { :root:has(body:not([data-theme='light'])) {
--_font-color: var(--choco-1); --_font-color: var(--choco-1);
@ -72,6 +82,26 @@
--_background-color: var(--sand-12); --_background-color: var(--sand-12);
} }
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
:root:has(body:not([data-theme='light'])) {
--_font-color: white;
--_accent-color: var(--lime-9);
--_translucent: var(--sand-10);
--_background-color: black;
--_background-image: '';
}
}
@media (prefers-contrast: more) {
:root:has(body[data-theme='dark']) {
--_font-color: white;
--_accent-color: var(--lime-9);
--_translucent: var(--sand-10);
--_background-color: black;
--_background-image: '';
}
}
body { body {
color: var(--_font-color); color: var(--_font-color);
font-family: var(--_font-family); font-family: var(--_font-family);

View file

@ -40,7 +40,7 @@ export async function requestApi<
method: 'GET' | 'POST' | 'DELETE' | 'PATCH', method: 'GET' | 'POST' | 'DELETE' | 'PATCH',
payload?: Payload | null, payload?: Payload | null,
): Promise<ApiResponse> { ): Promise<ApiResponse> {
const csrf = getCookie('_CSRF') ?? '' const csrf = getCookie('__Host-CSRF') ?? ''
const base = new URL('/api/', location.origin) const base = new URL('/api/', location.origin)
const endpoint = new URL( const endpoint = new URL(
@ -116,7 +116,7 @@ export async function* requestApiStream<
method: 'GET' | 'POST' | 'DELETE' | 'PATCH', method: 'GET' | 'POST' | 'DELETE' | 'PATCH',
payload?: Payload | null, payload?: Payload | null,
): AsyncGenerator<ApiResponse, void, void> { ): AsyncGenerator<ApiResponse, void, void> {
const csrf = getCookie('_CSRF') ?? '' const csrf = getCookie('__Host-CSRF') ?? ''
const base = new URL('/api/', location.origin) const base = new URL('/api/', location.origin)
const endpoint = new URL( const endpoint = new URL(