Compare commits
6 commits
a75de86d68
...
08ec221138
Author | SHA1 | Date | |
---|---|---|---|
Julien Oculi | 08ec221138 | ||
Julien Oculi | 28397a383a | ||
Julien Oculi | a2f872ae9d | ||
Julien Oculi | b982504705 | ||
Julien Oculi | eb887377a5 | ||
Julien Oculi | d44771dab9 |
28
components/Picture.tsx
Normal file
28
components/Picture.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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'] },
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)!)
|
||||||
|
|
|
@ -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)
|
|
||||||
},
|
|
||||||
}
|
|
20
src/utils.ts
20
src/utils.ts
|
@ -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 |
BIN
static/assets/screenshots/pwa_install_ui-desktop.jpg
Normal file
BIN
static/assets/screenshots/pwa_install_ui-desktop.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB |
BIN
static/assets/sponsors/cap_emploi_33.avif
Normal file
BIN
static/assets/sponsors/cap_emploi_33.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/ehpad_terre_negre.avif
Normal file
BIN
static/assets/sponsors/ehpad_terre_negre.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/imb.avif
Normal file
BIN
static/assets/sponsors/imb.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/inrae.avif
Normal file
BIN
static/assets/sponsors/inrae.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/iut_bordeaux.avif
Normal file
BIN
static/assets/sponsors/iut_bordeaux.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/maison_pour_la_science.avif
Normal file
BIN
static/assets/sponsors/maison_pour_la_science.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/technoshop.avif
Normal file
BIN
static/assets/sponsors/technoshop.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/universite_bordeaux.avif
Normal file
BIN
static/assets/sponsors/universite_bordeaux.avif
Normal file
Binary file not shown.
BIN
static/assets/sponsors/vegetal-signals.avif
Normal file
BIN
static/assets/sponsors/vegetal-signals.avif
Normal file
Binary file not shown.
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue