Compare commits
7 commits
08ec221138
...
906f31b240
Author | SHA1 | Date | |
---|---|---|---|
Julien Oculi | 906f31b240 | ||
Julien Oculi | bf7ed471dd | ||
Julien Oculi | b5b37b173f | ||
Julien Oculi | e1e462b7ca | ||
Julien Oculi | 38ce5042ff | ||
Julien Oculi | 8a3b2bfa29 | ||
Julien Oculi | 7322070876 |
|
@ -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;
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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'>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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> Rechercher</span>
|
<span> 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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue