Compare commits
No commits in common. "3db5f389e3824e8850ad036391c8052c637729d0" and "1cc3ce65eb03cc9b636ece5a157157e75804da36" have entirely different histories.
3db5f389e3
...
1cc3ce65eb
|
|
@ -1,8 +1,5 @@
|
||||||
import { ComponentChildren, JSX } from 'preact'
|
import { ComponentChildren, JSX } from 'preact'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
type Units = 'rem' | '%' | 'px'
|
type Units = 'rem' | '%' | 'px'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Markdown } from ':components/Markdown.tsx'
|
import { Markdown } from ':components/Markdown.tsx'
|
||||||
import { NewsFrontMatter } from ':src/blog/types.ts'
|
import { NewsFrontMatter } from ':src/blog/types.ts'
|
||||||
import { useSmartStylesheet } from ':plugins/SmartStylesheetIsland.tsx'
|
import { useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export type BlogProps = {
|
export type BlogProps = {
|
||||||
title: string
|
title: string
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useSmartStylesheet } from ':plugins/SmartStylesheetIsland.tsx'
|
import { useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export function CohabitInfoTable() {
|
export function CohabitInfoTable() {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(Footer)
|
const scope = getStyleScope(Footer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,7 @@ import AiChatBox from ':islands/AiChatBox.tsx'
|
||||||
import MoreBox from ':islands/MoreBox.tsx'
|
import MoreBox from ':islands/MoreBox.tsx'
|
||||||
import SearchBox from ':islands/SearchBox.tsx'
|
import SearchBox from ':islands/SearchBox.tsx'
|
||||||
import ThemePicker from ':islands/ThemePicker.tsx'
|
import ThemePicker from ':islands/ThemePicker.tsx'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(Header)
|
const scope = getStyleScope(Header)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(Heros)
|
const scope = getStyleScope(Heros)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
type MachineCardProps = {
|
type MachineCardProps = {
|
||||||
img: string
|
img: string
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
export type MemberCardProps = {
|
export type MemberCardProps = {
|
||||||
id: string
|
id: string
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import { JSX } from 'preact'
|
import { JSX } from 'preact'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
type ProjectCardProps = {
|
type ProjectCardProps = {
|
||||||
id: string
|
id: string
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { asset } from 'fresh/runtime'
|
import { asset } from 'fresh/runtime'
|
||||||
import { Picture } from ':components/Picture.tsx'
|
import { Picture } from ':components/Picture.tsx'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(SponsorCards)
|
const scope = getStyleScope(SponsorCards)
|
||||||
|
|
||||||
|
|
|
||||||
41
deno.json
41
deno.json
|
|
@ -8,18 +8,30 @@
|
||||||
"update": "deno run -A -r jsr:@fresh/update .",
|
"update": "deno run -A -r jsr:@fresh/update .",
|
||||||
"dev:add_package": "deno run --allow-net=git.cohabit.fr --allow-read=. --allow-write=./deno.json,./packages --allow-run=git,deno ./scripts/add_package.ts"
|
"dev:add_package": "deno run --allow-net=git.cohabit.fr --allow-read=. --allow-write=./deno.json,./packages --allow-run=git,deno ./scripts/add_package.ts"
|
||||||
},
|
},
|
||||||
"fmt": { "singleQuote": true, "semiColons": false, "useTabs": true },
|
"fmt": {
|
||||||
"lint": { "rules": { "tags": ["fresh", "recommended"] } },
|
"singleQuote": true,
|
||||||
"exclude": ["**/_fresh/*", "packages/"],
|
"semiColons": false,
|
||||||
|
"useTabs": true
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"rules": {
|
||||||
|
"tags": [
|
||||||
|
"fresh",
|
||||||
|
"recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/_fresh/*",
|
||||||
|
"packages/"
|
||||||
|
],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@deno/emit": "jsr:@deno/emit@^0.46.0",
|
"@deno/emit": "jsr:@deno/emit@^0.46.0",
|
||||||
"@deno/gfm": "jsr:@deno/gfm@^0.10.0",
|
"@deno/gfm": "jsr:@deno/gfm@^0.10.0",
|
||||||
"@mdxeditor/editor": "npm:@mdxeditor/editor@^3.32.3",
|
"@mdxeditor/editor": "npm:@mdxeditor/editor@^3.32.3",
|
||||||
"@std/fs": "jsr:@std/fs@^1.0.6",
|
"@std/fs": "jsr:@std/fs@^1.0.6",
|
||||||
"@std/media-types": "jsr:@std/media-types@^1.1.0",
|
|
||||||
"@std/path": "jsr:@std/path@^1.0.8",
|
"@std/path": "jsr:@std/path@^1.0.8",
|
||||||
"esbuild": "npm:esbuild@^0.25.4",
|
"fresh": "jsr:@fresh/core@^2.0.0-alpha.25",
|
||||||
"fresh": "jsr:@fresh/core@^2.0.0-alpha.34",
|
|
||||||
":components/": "./components/",
|
":components/": "./components/",
|
||||||
":islands/": "./islands/",
|
":islands/": "./islands/",
|
||||||
":src/": "./src/",
|
":src/": "./src/",
|
||||||
|
|
@ -29,7 +41,7 @@
|
||||||
"@cohabit/resources-manager": "jsr:@cohabit/resources-manager@^0.2.1",
|
"@cohabit/resources-manager": "jsr:@cohabit/resources-manager@^0.2.1",
|
||||||
"@jotsr/delayed": "jsr:@jotsr/delayed@^2.1.1",
|
"@jotsr/delayed": "jsr:@jotsr/delayed@^2.1.1",
|
||||||
"@jotsr/smart-css-bundler": "jsr:@jotsr/smart-css-bundler@^0.3.0",
|
"@jotsr/smart-css-bundler": "jsr:@jotsr/smart-css-bundler@^0.3.0",
|
||||||
"@preact/signals": "npm:@preact/signals@^2.0.4",
|
"@preact/signals": "npm:@preact/signals@^1.3.0",
|
||||||
"@simplewebauthn/browser": "npm:@simplewebauthn/browser@^10.0.0",
|
"@simplewebauthn/browser": "npm:@simplewebauthn/browser@^10.0.0",
|
||||||
"@simplewebauthn/server": "npm:@simplewebauthn/server@^10.0.0",
|
"@simplewebauthn/server": "npm:@simplewebauthn/server@^10.0.0",
|
||||||
"@simplewebauthn/types": "npm:@simplewebauthn/types@^10.0.0",
|
"@simplewebauthn/types": "npm:@simplewebauthn/types@^10.0.0",
|
||||||
|
|
@ -38,7 +50,7 @@
|
||||||
"@std/http": "jsr:@std/http@^0.224.4",
|
"@std/http": "jsr:@std/http@^0.224.4",
|
||||||
"@std/json": "jsr:@std/json@^1.0.1",
|
"@std/json": "jsr:@std/json@^1.0.1",
|
||||||
"@std/streams": "jsr:@std/streams@^0.224.5",
|
"@std/streams": "jsr:@std/streams@^0.224.5",
|
||||||
"preact": "npm:preact@^10.26.6",
|
"preact": "npm:preact@^10.24.2",
|
||||||
"react": "npm:react@^19.1.0",
|
"react": "npm:react@^19.1.0",
|
||||||
"react-dom": "npm:react-dom@^19.1.0",
|
"react-dom": "npm:react-dom@^19.1.0",
|
||||||
"web-push": "npm:web-push@^3.6.7"
|
"web-push": "npm:web-push@^3.6.7"
|
||||||
|
|
@ -53,7 +65,16 @@
|
||||||
],
|
],
|
||||||
"jsx": "precompile",
|
"jsx": "precompile",
|
||||||
"jsxImportSource": "preact",
|
"jsxImportSource": "preact",
|
||||||
"jsxPrecompileSkipElements": ["a", "img", "source", "body", "html", "head"]
|
"jsxPrecompileSkipElements": [
|
||||||
|
"a",
|
||||||
|
"img",
|
||||||
|
"source",
|
||||||
|
"body",
|
||||||
|
"html",
|
||||||
|
"head"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"unstable": ["kv"]
|
"unstable": [
|
||||||
|
"kv"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
fresh.config.ts
Normal file
11
fresh.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { defineConfig } from '$fresh/server.ts'
|
||||||
|
import { cssBundler } from '@jotsr/smart-css-bundler/fresh'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
cssBundler(
|
||||||
|
['./src/stylesheets/main.css'], //TODO fix bundler out paths , { bundleSubDir: 'css' }
|
||||||
|
{ externalPaths: ['assets'] },
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
@ -3,7 +3,7 @@ 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'
|
||||||
import { useSmartStylesheet } from ':plugins/SmartStylesheetIsland.tsx'
|
import { useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
const systemHistory = signal<BotMessage[]>([{
|
const systemHistory = signal<BotMessage[]>([{
|
||||||
role: 'system',
|
role: 'system',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { type Signal, useComputed, useSignal } from '@preact/signals'
|
import { type Signal, useComputed, useSignal } from '@preact/signals'
|
||||||
import { useEffect } from 'preact/hooks'
|
import { useEffect } from 'preact/hooks'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
type NetworkConnection = {
|
type NetworkConnection = {
|
||||||
addEventListener: (
|
addEventListener: (
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,7 @@ import type {
|
||||||
} from '../routes/api/webauthn/login/[step].ts'
|
} from '../routes/api/webauthn/login/[step].ts'
|
||||||
import { Button } from ':components/Button.tsx'
|
import { Button } from ':components/Button.tsx'
|
||||||
import { Input } from ':components/Input.tsx'
|
import { Input } from ':components/Input.tsx'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(LoginForm)
|
const scope = getStyleScope(LoginForm)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { VNode } from 'preact'
|
import { VNode } from 'preact'
|
||||||
import { useEffect, useRef } from 'preact/hooks'
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
import { useSmartStylesheet } from ':plugins/SmartStylesheetIsland.tsx'
|
import { useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export default function MoreBox({ children }: { children: VNode | VNode[] }) {
|
export default function MoreBox({ children }: { children: VNode | VNode[] }) {
|
||||||
useSmartStylesheet(import.meta)
|
useSmartStylesheet(import.meta)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useRef } from 'preact/hooks'
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
import { useSmartStylesheet } from ':plugins/SmartStylesheetIsland.tsx'
|
import { useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export default function SearchBox() {
|
export default function SearchBox() {
|
||||||
useSmartStylesheet(import.meta)
|
useSmartStylesheet(import.meta)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
:scope {
|
:scope {
|
||||||
display: inline-flex !important;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--_gap-half);
|
gap: var(--_gap-half);
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { useComputed, useSignal, useSignalEffect } from '@preact/signals'
|
import { useComputed, useSignal, useSignalEffect } from '@preact/signals'
|
||||||
import { IS_BROWSER } from 'fresh/runtime'
|
import { IS_BROWSER } from 'fresh/runtime'
|
||||||
import {
|
import { getStyleScope, useSmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
getStyleScope,
|
|
||||||
useSmartStylesheet,
|
|
||||||
} from ':plugins/SmartStylesheetIsland.tsx'
|
|
||||||
|
|
||||||
const scope = getStyleScope(ThemePicker)
|
const scope = getStyleScope(ThemePicker)
|
||||||
|
|
||||||
|
|
|
||||||
26
main.ts
26
main.ts
|
|
@ -1,5 +1,6 @@
|
||||||
import { App, fsRoutes, staticFiles } from 'fresh'
|
import { App, fsRoutes, staticFiles } from 'fresh'
|
||||||
import { type State } from './utils.ts'
|
import { type State } from './utils.ts'
|
||||||
|
import { contentType } from 'jsr:@std/media-types@1/content-type'
|
||||||
import { smartStylesheetPlugin } from ':plugins/SmartStylesheet.tsx'
|
import { smartStylesheetPlugin } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export const app = new App<State>()
|
export const app = new App<State>()
|
||||||
|
|
@ -15,6 +16,31 @@ app.use(staticFiles())
|
||||||
|
|
||||||
smartStylesheetPlugin(app)
|
smartStylesheetPlugin(app)
|
||||||
|
|
||||||
|
//TEMP fix before updating cssBundler middleware
|
||||||
|
app.use(async (ctx) => {
|
||||||
|
const response = await ctx.next()
|
||||||
|
if (
|
||||||
|
response.status === 404 &&
|
||||||
|
!ctx.url.pathname.match(/\/js\/[0-9a-f]+\/\S+\.js/)
|
||||||
|
) {
|
||||||
|
const ext = ctx.url.pathname.split('.').at(-1) ?? '.bin'
|
||||||
|
const mime = contentType(ext) ?? 'application/octet-stream'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = await Deno.readFile(`./_fresh/static/${ctx.url.pathname}`)
|
||||||
|
return new Response(file, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': mime,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
//TEMP don't handle specific error for now
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
|
||||||
await fsRoutes(app, {
|
await fsRoutes(app, {
|
||||||
dir: './',
|
dir: './',
|
||||||
loadIsland: (path) => import(`./islands/${path}`),
|
loadIsland: (path) => import(`./islands/${path}`),
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,125 @@
|
||||||
import { asset } from 'fresh/runtime'
|
import { asset, IS_BROWSER } from 'fresh/runtime'
|
||||||
import { App } from 'fresh'
|
import { App } from 'fresh'
|
||||||
import { build, type Plugin } from 'esbuild'
|
|
||||||
import { extname, fromFileUrl, toFileUrl } from '@std/path'
|
|
||||||
import { exists } from '@std/fs/exists'
|
|
||||||
import { ensureDir } from '@std/fs'
|
|
||||||
import { contentType } from '@std/media-types'
|
|
||||||
import { styles } from './SmartStylesheetCommon.tsx'
|
|
||||||
|
|
||||||
const bundledFiles = new Set<string>()
|
/**
|
||||||
|
* List of css files imported by the current fresh route.
|
||||||
|
*/
|
||||||
|
const styles = new Map<
|
||||||
|
string,
|
||||||
|
{ url: string; layer: string | undefined; scope: string | undefined }
|
||||||
|
>()
|
||||||
|
|
||||||
const baseRoute = '__smart_css__'
|
const baseRoute = '__smart_css__'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a css scope for the given component/island based on its name.
|
||||||
|
*
|
||||||
|
* @param component - Component or island to scope.
|
||||||
|
* @returns scope - css scope class.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // ./(components|islands)/Button.tsx
|
||||||
|
* import type { JSX } from 'preact'
|
||||||
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
||||||
|
*
|
||||||
|
* const scope = getStyleScope(Button)
|
||||||
|
*
|
||||||
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
||||||
|
* useSmartStylesheet(import.meta, { scope })
|
||||||
|
*
|
||||||
|
* return <button class={scope} {...props} />
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function getStyleScope<T extends { name: Readonly<string> }>(
|
||||||
|
component: T,
|
||||||
|
): string {
|
||||||
|
// generate scope/class hash
|
||||||
|
return `_scope_${
|
||||||
|
btoa(component.name).slice(-10).replaceAll('=', '_').toLowerCase()
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to load component/island stylesheet only when
|
||||||
|
* it is imported by the served route.
|
||||||
|
* For any `./(component|islands)/Element.tsx` it will
|
||||||
|
* load the corresponding `./(component|islands)/Element.css`
|
||||||
|
*
|
||||||
|
* @param meta - Component ImportMeta used to resolve
|
||||||
|
* stylesheet path, name and layer.
|
||||||
|
* @param options - CSS scope to use (default: none)
|
||||||
|
* and css layer ('components' or 'islands') depending of the ImportMeta.
|
||||||
|
*
|
||||||
|
* @example Basic usage
|
||||||
|
* ```ts
|
||||||
|
* // ./(components|islands)/Button.tsx
|
||||||
|
* import type { JSX } from 'preact'
|
||||||
|
* import { useSmartStylesheet } from './SmartStylesheet.tsx'
|
||||||
|
*
|
||||||
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
||||||
|
* useSmartStylesheet(import.meta)
|
||||||
|
*
|
||||||
|
* return <button class={scope} {...props} />
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Use css scope
|
||||||
|
* ```ts
|
||||||
|
* // ./(components|islands)/Button.tsx
|
||||||
|
* import type { JSX } from 'preact'
|
||||||
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
||||||
|
*
|
||||||
|
* const scope = getStyleScope(Button)
|
||||||
|
*
|
||||||
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
||||||
|
* useSmartStylesheet(import.meta, { scope })
|
||||||
|
*
|
||||||
|
* return <button class={scope} {...props} />
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Use custom layer
|
||||||
|
* ```ts
|
||||||
|
* // ./(components|islands)/Button.tsx
|
||||||
|
* import type { JSX } from 'preact'
|
||||||
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
||||||
|
*
|
||||||
|
* const scope = getStyleScope(Button)
|
||||||
|
*
|
||||||
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
||||||
|
* useSmartStylesheet(import.meta, { scope, layer: 'custom' })
|
||||||
|
*
|
||||||
|
* return <button class={scope} {...props} />
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useSmartStylesheet(
|
||||||
|
meta: ImportMeta,
|
||||||
|
options?: { scope?: string; layer?: string },
|
||||||
|
) {
|
||||||
|
if (IS_BROWSER) return
|
||||||
|
|
||||||
|
// resolve filename
|
||||||
|
const css = meta.filename
|
||||||
|
?.replace('.tsx', '.css')
|
||||||
|
.replace(Deno.cwd(), '')
|
||||||
|
.replaceAll('\\', '/')
|
||||||
|
|
||||||
|
if (!css) return
|
||||||
|
if (styles.has(css)) return
|
||||||
|
|
||||||
|
// set css layer
|
||||||
|
const layerValue = options?.layer ?? css.includes('/components/')
|
||||||
|
? 'components'
|
||||||
|
: css.includes('/islands/')
|
||||||
|
? 'islands'
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
styles.set(css, { url: css, scope: options?.scope, layer: layerValue })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic components and islands stylesheet.
|
* Dynamic components and islands stylesheet.
|
||||||
*
|
*
|
||||||
|
|
@ -56,20 +165,12 @@ export function SmartStylesheet(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StaticStylesheet(
|
|
||||||
options: { href: string },
|
|
||||||
) {
|
|
||||||
return <link rel='stylesheet' href={asset(`/${baseRoute}/${options.href}`)} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export function smartStylesheetPlugin<T>(
|
export function smartStylesheetPlugin<T>(
|
||||||
app: App<T>,
|
app: App<T>,
|
||||||
options: { baseRoute?: string } = {},
|
options: { baseRoute?: string } = {},
|
||||||
) {
|
) {
|
||||||
options.baseRoute ??= baseRoute
|
options.baseRoute ??= baseRoute
|
||||||
|
|
||||||
bundleCss(['./src/stylesheets/main.css'], app, options.baseRoute)
|
|
||||||
|
|
||||||
//resolve dynamic styles imports
|
//resolve dynamic styles imports
|
||||||
app.get(`/${options.baseRoute}/:path+`, async (ctx) => {
|
app.get(`/${options.baseRoute}/:path+`, async (ctx) => {
|
||||||
const { path } = ctx.params
|
const { path } = ctx.params
|
||||||
|
|
@ -98,117 +199,20 @@ export function smartStylesheetPlugin<T>(
|
||||||
if (path.startsWith('components') || path.startsWith('islands')) {
|
if (path.startsWith('components') || path.startsWith('islands')) {
|
||||||
const scope = ctx.url.searchParams.get('__scope')
|
const scope = ctx.url.searchParams.get('__scope')
|
||||||
|
|
||||||
try {
|
const css = await Deno.readTextFile(`./${path}`)
|
||||||
const css = await Deno.readTextFile(`./${path}`)
|
|
||||||
const file = scope ? `@scope (.${scope}) {\n\n${css}\n}` : css
|
|
||||||
|
|
||||||
return new Response(file, {
|
const file = scope ? `@scope (.${scope}) {\n\n${css}\n}` : css
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/css; charset=utf-8',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Deno.errors.NotFound) {
|
|
||||||
return new Response(null, { status: 404 })
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = await Deno.readFile(
|
|
||||||
`${app.config.build.outDir}/static/${options.baseRoute}/${path}`,
|
|
||||||
)
|
|
||||||
const mime = contentType(extname(path)) ?? 'application/octet-stream'
|
|
||||||
return new Response(file, {
|
return new Response(file, {
|
||||||
headers: { 'Content-Type': mime },
|
headers: {
|
||||||
})
|
'Content-Type': 'text/css; charset=utf-8',
|
||||||
} catch {
|
},
|
||||||
return new Response(null, {
|
|
||||||
status: 400,
|
|
||||||
statusText: 'Bad Request - Invalid url',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Response(null, {
|
||||||
|
status: 400,
|
||||||
|
statusText: 'Bad Request - Invalid url',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bundleCss<T extends unknown>(
|
|
||||||
files: string[],
|
|
||||||
app: App<T>,
|
|
||||||
baseRoute?: string,
|
|
||||||
) {
|
|
||||||
const cssPlugin: Plugin = {
|
|
||||||
name: 'smart-stylesheet-bundler',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
||||||
const importer = args.importer.length
|
|
||||||
? args.importer
|
|
||||||
: `${app.config.root}/`
|
|
||||||
const importerUrl = importer.match(/\w+:\/\/.+/)
|
|
||||||
? new URL(importer)
|
|
||||||
: toFileUrl(importer)
|
|
||||||
const url = new URL(args.path, importerUrl)
|
|
||||||
|
|
||||||
const external = url.protocol === 'file:' && !(await exists(url))
|
|
||||||
const local = url.protocol === 'file:'
|
|
||||||
|
|
||||||
if (!external) bundledFiles.add(url.href)
|
|
||||||
const watchFiles: string[] = []
|
|
||||||
if (local && !external) watchFiles.push(fromFileUrl(url.href))
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: external ? args.path : url.href,
|
|
||||||
namespace: external ? 'external' : 'internal',
|
|
||||||
external,
|
|
||||||
watchFiles,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'internal' }, async (args) => {
|
|
||||||
const url = new URL(args.path)
|
|
||||||
let isCss = url.pathname.match(/.css$/) !== null
|
|
||||||
const buffer = url.protocol === 'file:'
|
|
||||||
? await Deno.readFile(url)
|
|
||||||
: await fetch(url).then((r) => {
|
|
||||||
if (r.headers.get('Content-type')?.includes('text/css')) {
|
|
||||||
isCss = true
|
|
||||||
}
|
|
||||||
return r.bytes()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isCss) {
|
|
||||||
return {
|
|
||||||
contents: new TextDecoder().decode(buffer),
|
|
||||||
loader: 'css',
|
|
||||||
resolveDir: url.href,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
contents: buffer,
|
|
||||||
loader: url.protocol === 'data:' ? 'dataurl' : 'file',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const outdir = baseRoute
|
|
||||||
? `${app.config.build.outDir}/static/${baseRoute}`
|
|
||||||
: `${app.config.build.outDir}/static`
|
|
||||||
await ensureDir(outdir)
|
|
||||||
|
|
||||||
const result = await build({
|
|
||||||
entryPoints: files,
|
|
||||||
bundle: true,
|
|
||||||
minify: true,
|
|
||||||
sourcemap: app.config.mode === 'development' ? 'inline' : false,
|
|
||||||
plugins: [cssPlugin],
|
|
||||||
outdir,
|
|
||||||
write: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const file of result.outputFiles) {
|
|
||||||
const path = new URL(file.path).pathname
|
|
||||||
Deno.writeFile(path, file.contents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
/**
|
|
||||||
* List of css files imported by the current fresh route.
|
|
||||||
*/
|
|
||||||
export const styles = new Map<
|
|
||||||
string,
|
|
||||||
{ url: string; layer: string | undefined; scope: string | undefined }
|
|
||||||
>()
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
import { IS_BROWSER } from 'fresh/runtime'
|
|
||||||
import { styles } from './SmartStylesheetCommon.tsx'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a css scope for the given component/island based on its name.
|
|
||||||
*
|
|
||||||
* @param component - Component or island to scope.
|
|
||||||
* @returns scope - css scope class.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* // ./(components|islands)/Button.tsx
|
|
||||||
* import type { JSX } from 'preact'
|
|
||||||
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
||||||
*
|
|
||||||
* const scope = getStyleScope(Button)
|
|
||||||
*
|
|
||||||
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
||||||
* useSmartStylesheet(import.meta, { scope })
|
|
||||||
*
|
|
||||||
* return <button class={scope} {...props} />
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function getStyleScope<T extends { name: Readonly<string> }>(
|
|
||||||
component: T,
|
|
||||||
): string {
|
|
||||||
// generate scope/class hash
|
|
||||||
return `_scope_${
|
|
||||||
btoa(component.name).slice(-10).replaceAll('=', '_').toLowerCase()
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to load component/island stylesheet only when
|
|
||||||
* it is imported by the served route.
|
|
||||||
* For any `./(component|islands)/Element.tsx` it will
|
|
||||||
* load the corresponding `./(component|islands)/Element.css`
|
|
||||||
*
|
|
||||||
* @param meta - Component ImportMeta used to resolve
|
|
||||||
* stylesheet path, name and layer.
|
|
||||||
* @param options - CSS scope to use (default: none)
|
|
||||||
* and css layer ('components' or 'islands') depending of the ImportMeta.
|
|
||||||
*
|
|
||||||
* @example Basic usage
|
|
||||||
* ```ts
|
|
||||||
* // ./(components|islands)/Button.tsx
|
|
||||||
* import type { JSX } from 'preact'
|
|
||||||
* import { useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
||||||
*
|
|
||||||
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
||||||
* useSmartStylesheet(import.meta)
|
|
||||||
*
|
|
||||||
* return <button class={scope} {...props} />
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example Use css scope
|
|
||||||
* ```ts
|
|
||||||
* // ./(components|islands)/Button.tsx
|
|
||||||
* import type { JSX } from 'preact'
|
|
||||||
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
||||||
*
|
|
||||||
* const scope = getStyleScope(Button)
|
|
||||||
*
|
|
||||||
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
||||||
* useSmartStylesheet(import.meta, { scope })
|
|
||||||
*
|
|
||||||
* return <button class={scope} {...props} />
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example Use custom layer
|
|
||||||
* ```ts
|
|
||||||
* // ./(components|islands)/Button.tsx
|
|
||||||
* import type { JSX } from 'preact'
|
|
||||||
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
||||||
*
|
|
||||||
* const scope = getStyleScope(Button)
|
|
||||||
*
|
|
||||||
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
||||||
* useSmartStylesheet(import.meta, { scope, layer: 'custom' })
|
|
||||||
*
|
|
||||||
* return <button class={scope} {...props} />
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function useSmartStylesheet(
|
|
||||||
meta: ImportMeta,
|
|
||||||
options?: { scope?: string; layer?: string },
|
|
||||||
) {
|
|
||||||
if (IS_BROWSER) return
|
|
||||||
|
|
||||||
// resolve filename
|
|
||||||
const css = meta.filename
|
|
||||||
?.replace('.tsx', '.css')
|
|
||||||
.replace(Deno.cwd(), '')
|
|
||||||
.replaceAll('\\', '/')
|
|
||||||
|
|
||||||
if (!css) return
|
|
||||||
if (styles.has(css)) return
|
|
||||||
|
|
||||||
// set css layer
|
|
||||||
const layerValue = options?.layer ?? css.includes('/components/')
|
|
||||||
? 'components'
|
|
||||||
: css.includes('/islands/')
|
|
||||||
? 'islands'
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
styles.set(css, { url: css, scope: options?.scope, layer: layerValue })
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Footer } from ':components/Footer.tsx'
|
||||||
import { Header } from ':components/Header.tsx'
|
import { Header } from ':components/Header.tsx'
|
||||||
import IsOnline from ':islands/IsOnline.tsx'
|
import IsOnline from ':islands/IsOnline.tsx'
|
||||||
import RegisterServiceWorker from ':islands/RegisterServiceWorker.tsx'
|
import RegisterServiceWorker from ':islands/RegisterServiceWorker.tsx'
|
||||||
import { SmartStylesheet, StaticStylesheet } from ':plugins/SmartStylesheet.tsx'
|
import { SmartStylesheet } from ':plugins/SmartStylesheet.tsx'
|
||||||
|
|
||||||
export default function App(
|
export default function App(
|
||||||
{ Component, data, url }: PageProps<{ title?: string } | undefined>,
|
{ Component, data, url }: PageProps<{ title?: string } | undefined>,
|
||||||
|
|
@ -42,11 +42,7 @@ export default function App(
|
||||||
type='image/x-icon'
|
type='image/x-icon'
|
||||||
/>
|
/>
|
||||||
<SmartStylesheet pathname={url.pathname} />
|
<SmartStylesheet pathname={url.pathname} />
|
||||||
<link
|
<link rel='stylesheet' href={asset('/main.css')} />
|
||||||
rel='stylesheet'
|
|
||||||
href='https://cdn.jsdelivr.net/npm/@mdxeditor/editor@3.32.3/dist/style.min.css'
|
|
||||||
/>
|
|
||||||
<StaticStylesheet href='main.css' />
|
|
||||||
<link rel='stylesheet' href={asset('/imports/markdown_css')} />
|
<link rel='stylesheet' href={asset('/imports/markdown_css')} />
|
||||||
<title>{data?.title ?? 'Fablab Coh@bit'}</title>
|
<title>{data?.title ?? 'Fablab Coh@bit'}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { SessionHandlers } from ':src/session/mod.ts'
|
||||||
import { respondApi, respondApiStream } from ':src/utils.ts'
|
import { respondApi, respondApiStream } from ':src/utils.ts'
|
||||||
|
|
||||||
export const handler: SessionHandlers = {
|
export const handler: SessionHandlers = {
|
||||||
GET(ctx) {
|
GET(_req, ctx) {
|
||||||
try {
|
try {
|
||||||
const memberList = dbToMemberCardProps(db)
|
const memberList = dbToMemberCardProps(db)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ export type WebAuthnLoginStartPayload = {
|
||||||
export type WebAuthnLoginFinishPayload = AuthenticationResponseJSON
|
export type WebAuthnLoginFinishPayload = AuthenticationResponseJSON
|
||||||
|
|
||||||
export const handler: SessionHandlers = {
|
export const handler: SessionHandlers = {
|
||||||
async POST(ctx) {
|
async POST(req, ctx) {
|
||||||
const req = ctx.req
|
|
||||||
const relyingParty = getRelyingParty(ctx.url)
|
const relyingParty = getRelyingParty(ctx.url)
|
||||||
|
|
||||||
const { step } = ctx.params as Params
|
const { step } = ctx.params as Params
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ export type WebAuthnRegisterStartPayload = { name: string }
|
||||||
export type WebAuthnRegisterFinishPayload = RegistrationResponseJSON
|
export type WebAuthnRegisterFinishPayload = RegistrationResponseJSON
|
||||||
|
|
||||||
export const handler: SessionHandlers = {
|
export const handler: SessionHandlers = {
|
||||||
async POST(ctx) {
|
async POST(req, ctx) {
|
||||||
const req = ctx.req
|
|
||||||
const relyingParty = getRelyingParty(ctx.url)
|
const relyingParty = getRelyingParty(ctx.url)
|
||||||
|
|
||||||
const { step } = ctx.params as Params
|
const { step } = ctx.params as Params
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { define } from '../../../utils.ts'
|
||||||
|
|
||||||
export const handler = define.handlers({
|
export const handler = define.handlers({
|
||||||
GET() {
|
GET() {
|
||||||
|
console.log('VAPID', publicKey)
|
||||||
return respondApi('success', publicKey)
|
return respondApi('success', publicKey)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import MdxEditor from ':islands/MdxEditor.tsx'
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
//TODO implement route
|
//TODO implement route
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MdxEditor />
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Filters ... - os [] [] [] - cohabit []
|
Filters ... - os [] [] [] - cohabit []
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ if (IS_SW) {
|
||||||
})
|
})
|
||||||
|
|
||||||
self.addEventListener('push', (event) => {
|
self.addEventListener('push', (event) => {
|
||||||
|
console.log('push')
|
||||||
const { title, options } = (event.data?.json() ?? {}) as {
|
const { title, options } = (event.data?.json() ?? {}) as {
|
||||||
title?: string
|
title?: string
|
||||||
options?: Partial<NotificationOptions>
|
options?: Partial<NotificationOptions>
|
||||||
|
|
@ -120,9 +121,13 @@ if (IS_SW) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO handle notifications actions
|
self.addEventListener('notificationclick', (event) => {
|
||||||
// self.addEventListener('notificationclick', (event) => {})
|
console.log('SW - NOT_CLICK', event)
|
||||||
// self.addEventListener('notificationclose', (event) => {})
|
})
|
||||||
|
|
||||||
|
self.addEventListener('notificationclose', (event) => {
|
||||||
|
console.log('SW - NOT_CLOSE', event)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchHandler(event: FetchEvent) {
|
async function fetchHandler(event: FetchEvent) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@
|
||||||
/* font */
|
/* font */
|
||||||
--_font-size: var(--font-size-1);
|
--_font-size: var(--font-size-1);
|
||||||
--_font-family: 'MuseoModerno', 'Adjusted Verdana Fallback', sans-serif;
|
--_font-family: 'MuseoModerno', 'Adjusted Verdana Fallback', sans-serif;
|
||||||
--_font-family-accent: 'Hepta Slab', 'Adjusted Lucida Bright Fallback', serif;
|
--_font-family-accent: 'Hepta Slab', 'Adjusted Lucida Bright Fallback',
|
||||||
|
serif;
|
||||||
--_font-family-code: 'Fira Code', 'Adjusted Courier New Fallback', monospace;
|
--_font-family-code: 'Fira Code', 'Adjusted Courier New Fallback', monospace;
|
||||||
--_font-color: var(--choco-12);
|
--_font-color: var(--choco-12);
|
||||||
|
|
||||||
|
|
@ -46,11 +47,11 @@
|
||||||
/* color */
|
/* color */
|
||||||
--_accent-color: var(--lime-6);
|
--_accent-color: var(--lime-6);
|
||||||
--_translucent: hsl(from var(--_font-color) h s l / 0.1);
|
--_translucent: hsl(from var(--_font-color) h s l / 0.1);
|
||||||
--_translucent-bg: hsl(from var(--_background-color) h s l / 0.6);
|
--_translucent-bg: hsl(from var(--_background-color) h s l / .6);
|
||||||
|
|
||||||
/* other */
|
/* other */
|
||||||
--_background-color: var(--sand-0);
|
--_background-color: var(--sand-0);
|
||||||
--_background-image: url('/assets/css_bg_stardust_alpha_30.png');
|
--_background-image: url('assets/css_bg_stardust_alpha_30.png');
|
||||||
--_blur: var(--size-3);
|
--_blur: var(--size-3);
|
||||||
--_transition-delay: 0.2s;
|
--_transition-delay: 0.2s;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue