Compare commits

..

6 commits

22 changed files with 68 additions and 68 deletions

28
components/Picture.tsx Normal file
View file

@ -0,0 +1,28 @@
import { unwrapSignalOrValue } from ':src/utils.ts'
import { Ensure } from ':types'
import { JSX } from 'preact'
export type PictureProps =
& { formats: string[] }
& Ensure<JSX.HTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'loading'>
export function Picture(
{ formats, src, ...props }: PictureProps,
) {
const groups = unwrapSignalOrValue(src)?.match(/(?<path>.*)(?<ext>\.\w+)/)
?.groups
if (groups === undefined) {
throw new SyntaxError(`unable to parse path of "${src.valueOf()}"`)
}
const { path } = groups
return (
<picture>
{formats.map((format) => (
<source type={`image/${format}`} srcset={`${path}.${format}`} />
))}
<img src={src} {...props} />
</picture>
)
}

View file

@ -1,4 +1,5 @@
import { asset } from '$fresh/runtime.ts' import { asset } from '$fresh/runtime.ts'
import { Picture } from ':components/Picture.tsx'
export function SponsorCards() { export function SponsorCards() {
return ( return (
@ -13,7 +14,12 @@ function SponsorCard(
) { ) {
return ( return (
<a class='components__sponsor_card' href={href} target='_blank'> <a class='components__sponsor_card' href={href} target='_blank'>
<img src={asset(src)} alt={alt} /> <Picture
src={asset(src)}
alt={alt}
loading={'lazy'}
formats={['avif']}
/>
</a> </a>
) )
} }

View file

@ -34,6 +34,7 @@
":components/": "./components/", ":components/": "./components/",
":islands/": "./islands/", ":islands/": "./islands/",
":src/": "./src/", ":src/": "./src/",
":types": "./types.ts",
"@cohabit/cohamail/": "./packages/@cohabit__cohamail@0.2.1/", "@cohabit/cohamail/": "./packages/@cohabit__cohamail@0.2.1/",
"@cohabit/ressources_manager/": "./packages/@cohabit__ressources_manager@0.1.3/", "@cohabit/ressources_manager/": "./packages/@cohabit__ressources_manager@0.1.3/",
"@deno/gfm": "jsr:@deno/gfm@^0.8.2", "@deno/gfm": "jsr:@deno/gfm@^0.8.2",

View file

@ -3,7 +3,9 @@ import { cssBundler } from '@jotsr/smart-css-bundler/fresh'
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
cssBundler(['./src/stylesheets/main.css'] //TODO fix bundler out paths , { bundleSubDir: 'css' } cssBundler(
['./src/stylesheets/main.css'], //TODO fix bundler out paths , { bundleSubDir: 'css' }
{ externalPaths: ['assets'] },
), ),
], ],
}) })

View file

@ -1,5 +1,5 @@
import { JsonParseStream } from '$std/json/mod.ts' import { JsonParseStream } from '$std/json/mod.ts'
import { CSS, render as renderMd } from '@deno/gfm' import { Markdown } from ':components/Markdown.tsx'
import { Signal, signal, useSignal } from '@preact/signals' import { Signal, signal, useSignal } from '@preact/signals'
import { JSX } from 'preact' import { JSX } from 'preact'
import { useEffect, useRef } from 'preact/hooks' import { useEffect, useRef } from 'preact/hooks'
@ -15,16 +15,6 @@ const currentReader = signal<ReadableStreamDefaultReader<BotResponse> | null>(
) )
let currentResponse: string[] = [] let currentResponse: string[] = []
function MdCell({ children }: { children: Signal<string> }) {
return (
<div
class='markdown-body'
dangerouslySetInnerHTML={{ __html: renderMd(children.value) }}
>
</div>
)
}
type BotMessage = { type BotMessage = {
role: string role: string
content: string content: string
@ -95,7 +85,6 @@ export default function AiChatBox() {
return ( return (
<> <>
<style dangerouslySetInnerHTML={{ __html: CSS }}></style>
<button <button
class='islands__ai_chat_box__button' class='islands__ai_chat_box__button'
onClick={() => dialog.current?.showModal()} onClick={() => dialog.current?.showModal()}
@ -103,7 +92,9 @@ export default function AiChatBox() {
<i class='ri-bard-line'></i> <i class='ri-bard-line'></i>
</button> </button>
<dialog ref={dialog} class='islands__ai_chat_box__dialog'> <dialog ref={dialog} class='islands__ai_chat_box__dialog'>
<div class='islands__ai_chat_box__dialog__content'>{history}</div> <div class='islands__ai_chat_box__dialog__content'>
{history}
</div>
<form ref={form} class='islands__ai_chat_box__dialog__form'> <form ref={form} class='islands__ai_chat_box__dialog__form'>
<input <input
type='text' type='text'
@ -139,7 +130,7 @@ async function chatListener(event: Event, history: Signal<JSX.Element[]>) {
const botEntry = ( const botEntry = (
<span class='islands__ai_chat_box__history_bot'> <span class='islands__ai_chat_box__history_bot'>
<MdCell>{botMessage}</MdCell> <Markdown>{botMessage}</Markdown>
</span> </span>
) )

View file

@ -22,7 +22,7 @@ export default async function Member(_: Request, { params }: PageProps) {
const Member = memberMock.at(id) const Member = memberMock.at(id)
if (!Member) { if (!Member) {
return <h3>Membre inconnu, peut être serai vous le prochain</h3> return <h3>Membre inconnu, peut être serez vous le prochain</h3>
} }
const carnet = await getCarnet(db.at(id)!) const carnet = await getCarnet(db.at(id)!)

View file

@ -1,50 +0,0 @@
import { Handlers, RouteConfig } from '$fresh/server.ts'
import { contentType } from '$std/media_types/mod.ts'
import { parse } from '$std/path/mod.ts'
const db = [
'julien.oculi',
]
async function getPortfolio(
user: string,
pathname: string,
): Promise<Response> {
const url = new URL(
pathname,
`https://git.cohabit.fr/${user}/.portfolio/raw/branch/main/`,
)
const { ext } = parse(pathname)
try {
const response = await fetch(url)
if (response.ok) {
return new Response(response.body, {
headers: {
'Content-Type': contentType(ext) ?? 'text/plain; charset=utf-8',
},
})
}
throw new Error(response.statusText)
} catch (error) {
return new Response(
'Portfolio introuvable\n```js\n' + String(error) + '\n```',
)
}
}
export const config: RouteConfig = {
skipAppWrapper: true,
}
export const handler: Handlers = {
GET(req, _ctx) {
const url = new URL(req.url)
const id = Number(url.pathname.split('/')[2])
const user = db[id]
const query = url.pathname.split('/').slice(4).join('/')
return getPortfolio(user, query === '' ? 'index.html' : query)
},
}

View file

@ -1,3 +1,4 @@
import { SignalLike } from '$fresh/src/types.ts'
import { JsonValue } from '$std/json/common.ts' import { JsonValue } from '$std/json/common.ts'
import { decodeBase64 } from '@std/encoding/base64' import { decodeBase64 } from '@std/encoding/base64'
import { JsonStringifyStream } from '@std/json' import { JsonStringifyStream } from '@std/json'
@ -171,3 +172,22 @@ export function base64ToString(base64: string): string {
const bytes = decodeBase64(base64) const bytes = decodeBase64(base64)
return new TextDecoder().decode(bytes) return new TextDecoder().decode(bytes)
} }
export function unwrapSignalOrValue<T>(valueOrSignal: T | SignalLike<T>): T {
if (typeof valueOrSignal !== 'object') {
return valueOrSignal
}
if (valueOrSignal === null) {
return valueOrSignal
}
if (
'value' in valueOrSignal && 'peek' in valueOrSignal &&
'subscribe' in valueOrSignal
) {
return valueOrSignal.value
}
return valueOrSignal
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -43,7 +43,7 @@
], ],
"screenshots": [ "screenshots": [
{ {
"src": "/assets/screenshots/pwa_install_ui-desktop.png", "src": "/assets/screenshots/pwa_install_ui-desktop.jpg",
"sizes": "1920x1080", "sizes": "1920x1080",
"type": "image/png", "type": "image/png",
"form_factor": "wide", "form_factor": "wide",

View file

@ -4,3 +4,5 @@ export interface User {
mail: string mail: string
groupes: string[] groupes: string[]
} }
export type Ensure<T, K extends keyof T> = T & Required<Pick<T, K>>