Compare commits

...

16 Commits

Author SHA1 Message Date
0838ea9a47
feat(article): conf serveur +
feat(config): noopener norefer on links +
fix(style): larger wrapper for editorial content +
fix(structure): add assets folder in src
2025-01-29 15:23:19 +01:00
202303f8f7
feat(sitemap): add sitemap 2025-01-29 10:44:24 +01:00
ad39e4c76b
prevent AIs bots 2025-01-29 10:39:00 +01:00
b22f9266c4
server conf article 2025-01-29 10:38:42 +01:00
836d4652e0
feat(website): new contents and styles 2025-01-22 14:38:21 +01:00
d7392a0bd6
feat(security): add security.txt file 2025-01-08 16:00:43 +01:00
53915b7e49
added missing lang attribute 2025-01-07 13:39:37 +01:00
7d39c84461
meta stuff 2025-01-02 16:28:55 +01:00
069baaf81c
remove redirects & fix rss + add rel=me links + en link 2025-01-02 14:52:11 +01:00
fa61a28160
cleanup + better code blocks + redirects 2025-01-01 22:35:54 +01:00
703d1d7f08
cleanup and refactor i18n + code 2024-12-29 23:54:33 +01:00
65c6003313 better sitemap organization 2024-12-29 14:49:35 +01:00
b2b6887fdf
v5 migration + remove i18n 2024-12-29 12:33:22 +01:00
b7ce5b7f20
astro native i18n + i18n utils 2024-12-28 11:52:32 +01:00
99ef7634e3 update to v5 + remove unmaintained i18n lib 2024-12-27 08:55:12 +01:00
59687fbade moved computer 2024-06-21 15:05:35 +02:00
177 changed files with 2277 additions and 6777 deletions

View File

@ -1,52 +0,0 @@
type DefaultLangCode = "fr"
type SupportedLangCode = "en"
type LangCode = DefaultLangCode | SupportedLangCode
type RouteUri = | "/articles/[slug]" | "/articles" | "/agments/[slug]" | "/agments" | "/references" | "/veille" | "/" | "/plan-du-site"
type RouteParams = {"/articles/[slug]": { "slug": string; }; "/articles": undefined; "/agments/[slug]": { "slug": string; }; "/agments": undefined; "/references": undefined; "/veille": undefined; "/": undefined; "/plan-du-site": undefined; }
type TranslationPath = "accueil" | "tagline" | "copyright" | "contact.title" | "contact.email" | "contact.tel" | "contenuVide" | "header.skipLink" | "header.mainNav" | "header.homeLink" | "sitemap" | "prevNext.contenus" | "prevNext.precedent" | "prevNext.suivant" | "article.titre" | "article.tagline" | "article.published" | "meta.publication" | "meta.modification" | "meta.credit" | "fragments.titre" | "fragments.tagline" | "references.titre" | "references.slug" | "references.cta" | "references.tagline" | "veille.titre" | "veille.tagline" | "erreur.introuvable" | "erreur.autre" | "erreur.lienRetour" | "seo.meta.description" | "seo.article.title" | "seo.article.description" | "seo.code.title" | "seo.code.description" | "seo.references.title" | "seo.references.description" | "index.articles.pageName" | "index.articles.subtitle" | "index.fragments.pageName" | "index.fragments.subtitle" | "index.references.pageName" | "index.references.subtitle" | "index.veille.pageName" | "index.veille.subtitle" | "index.title" | "index.subtitle" | "index.quoi" | "index.comment" | "index.opensource" | "index.writing" | "index.latestProjects" | "index.latestArticles" | "index.allProjects" | "index.allArticles" | "index.latestSnippets" | "index.allSnippets" | "index.toc" | "contact.contenuVide"
type TranslationOptions = { "accueil": {} | undefined; "tagline": {} | undefined; "copyright": {} | undefined; "contact.title": {} | undefined; "contact.email": {} | undefined; "contact.tel": {} | undefined; "contenuVide": {} | undefined; "header.skipLink": {} | undefined; "header.mainNav": {} | undefined; "header.homeLink": {} | undefined; "sitemap": {} | undefined; "prevNext.contenus": {} | undefined; "prevNext.precedent": {} | undefined; "prevNext.suivant": {} | undefined; "article.titre": {} | undefined; "article.tagline": {} | undefined; "article.published": { datetime: unknown; options: unknown; }; "meta.publication": {} | undefined; "meta.modification": {} | undefined; "meta.credit": {} | undefined; "fragments.titre": {} | undefined; "fragments.tagline": {} | undefined; "references.titre": {} | undefined; "references.slug": {} | undefined; "references.cta": {} | undefined; "references.tagline": {} | undefined; "veille.titre": {} | undefined; "veille.tagline": {} | undefined; "erreur.introuvable": {} | undefined; "erreur.autre": {} | undefined; "erreur.lienRetour": {} | undefined; "seo.meta.description": {} | undefined; "seo.article.title": {} | undefined; "seo.article.description": {} | undefined; "seo.code.title": {} | undefined; "seo.code.description": {} | undefined; "seo.references.title": {} | undefined; "seo.references.description": {} | undefined; "index.articles.pageName": {} | undefined; "index.articles.subtitle": {} | undefined; "index.fragments.pageName": {} | undefined; "index.fragments.subtitle": {} | undefined; "index.references.pageName": {} | undefined; "index.references.subtitle": {} | undefined; "index.veille.pageName": {} | undefined; "index.veille.subtitle": {} | undefined; "index.title": {} | undefined; "index.subtitle": {} | undefined; "index.quoi": {} | undefined; "index.comment": {} | undefined; "index.opensource": {} | undefined; "index.writing": {} | undefined; "index.latestProjects": {} | undefined; "index.latestArticles": {} | undefined; "index.allProjects": {} | undefined; "index.allArticles": {} | undefined; "index.latestSnippets": {} | undefined; "index.allSnippets": {} | undefined; "index.toc": {} | undefined; "contact.contenuVide": {} | undefined; }
declare module "astro-i18n" {
export * from "astro-i18n/"
export function l<Uri extends RouteUri>(
route: Uri | string & {},
...args: Uri extends keyof RouteParams
? undefined extends RouteParams[Uri]
? [params?: Record<string, string>, targetLangCode?: LangCode, routeLangCode?: LangCode]
: [params: RouteParams[Uri], targetLangCode?: LangCode, routeLangCode?: LangCode]
: [params?: Record<string, string>, targetLangCode?: LangCode, routeLangCode?: LangCode]
): string
export function t<Path extends TranslationPath>(
path: Path | string & {},
...args: undefined extends TranslationOptions[Path]
? [options?: keyof TranslationOptions extends Path ? Record<string, unknown> : TranslationOptions[Path], langCode?: LangCode]
: [options: TranslationOptions[Path], langCode?: LangCode]
): string
export function extractRouteLangCode(route: string): LangCode | undefined
type Translation = string | { [translationKey: string]: string | Translation }
type Translations = { [langCode: string]: Record<string, Translation> }
type RouteTranslations = { [langCode: string]: Record<string, string> }
type InterpolationFormatter = (value: unknown, ...args: unknown[]) => string
class AstroI18n {
defaultLangCode: DefaultLangCode
supportedLangCodes: SupportedLangCode[]
showDefaultLangCode: boolean
translations: Translations
routeTranslations: RouteTranslations
get langCodes(): LangCode[]
get langCode(): LangCode
set langCode(langCode: LangCode)
get formatters(): Record<string, InterpolationFormatter>
init(Astro: { url: URL }, formatters?: Record<string, InterpolationFormatter>): void
addTranslations(translations: Translations): void
addRouteTranslations(routeTranslations: RouteTranslations): void
getFormatter(name: string): InterpolationFormatter | undefined
setFormatter(name: string, formatter: InterpolationFormatter): void
deleteFormatter(name: string): void
}
export const astroI18n: AstroI18n
}

View File

@ -0,0 +1,48 @@
{
"$ref": "#/definitions/HPsections",
"definitions": {
"HPsections": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"lang": {
"type": "string",
"enum": [
"fr",
"en"
]
},
"uid": {
"type": "string"
},
"image": {
"type": "string"
},
"order": {
"type": "number"
},
"quickTitle": {
"type": "string"
},
"quickImage": {
"type": "string"
},
"reference": {
"type": "string"
},
"$schema": {
"type": "string"
}
},
"required": [
"type",
"lang",
"order"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@ -0,0 +1,61 @@
{
"$ref": "#/definitions/articles",
"definitions": {
"articles": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"subtitle": {
"type": "string"
},
"lang": {
"type": "string",
"enum": [
"fr",
"en"
]
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string"
},
"slug": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"code": {
"type": "boolean"
},
"draft": {
"type": "boolean"
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"subtitle",
"lang",
"tags",
"type",
"slug",
"createdAt"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@ -0,0 +1,61 @@
{
"$ref": "#/definitions/fragments",
"definitions": {
"fragments": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"subtitle": {
"type": "string"
},
"lang": {
"type": "string",
"enum": [
"fr",
"en"
]
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string"
},
"slug": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"code": {
"type": "boolean"
},
"draft": {
"type": "boolean"
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"subtitle",
"lang",
"tags",
"type",
"slug",
"createdAt"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@ -0,0 +1,61 @@
{
"$ref": "#/definitions/references",
"definitions": {
"references": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"subtitle": {
"type": "string"
},
"url": {
"type": "string"
},
"lang": {
"type": "string",
"enum": [
"fr",
"en"
]
},
"slug": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"code": {
"type": "boolean"
},
"draft": {
"type": "boolean"
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"subtitle",
"url",
"lang",
"slug",
"tags",
"createdAt"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@ -0,0 +1,32 @@
{
"$ref": "#/definitions/veille",
"definitions": {
"veille": {
"type": "object",
"properties": {
"lang": {
"type": "string",
"enum": [
"fr",
"en"
]
},
"title": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"$schema": {
"type": "string"
}
},
"required": [
"lang",
"title"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@ -0,0 +1 @@
export default new Map();

View File

@ -0,0 +1,15 @@
export default new Map([
["src/content/fragments/fr/buttons.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fbuttons.mdx&astroContentModuleFlag=true")],
["src/content/fragments/fr/image-full.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fimage-full.mdx&astroContentModuleFlag=true")],
["src/content/fragments/fr/super-cookies.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fsuper-cookies.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/after-effects-expressions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fafter-effects-expressions.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/sci-hub-blocage.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fsci-hub-blocage.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/the-day-I-jamd.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fthe-day-I-jamd.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/video-compression.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fvideo-compression.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/image-full.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fimage-full.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/super-cookies.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fsuper-cookies.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/configuration-serveur.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fconfiguration-serveur.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/sci-hub-blocage.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fsci-hub-blocage.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/the-day-I-jamd.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fthe-day-I-jamd.mdx&astroContentModuleFlag=true")]]);

206
.astro/content.d.ts vendored Normal file
View File

@ -0,0 +1,206 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
components: import('astro').MDXInstance<{}>['components'];
}>;
}
}
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"HPsections": Record<string, {
id: string;
body?: string;
collection: "HPsections";
data: InferEntrySchema<"HPsections">;
rendered?: RenderedContent;
filePath?: string;
}>;
"articles": Record<string, {
id: string;
body?: string;
collection: "articles";
data: InferEntrySchema<"articles">;
rendered?: RenderedContent;
filePath?: string;
}>;
"fragments": Record<string, {
id: string;
body?: string;
collection: "fragments";
data: InferEntrySchema<"fragments">;
rendered?: RenderedContent;
filePath?: string;
}>;
"references": Record<string, {
id: string;
body?: string;
collection: "references";
data: InferEntrySchema<"references">;
rendered?: RenderedContent;
filePath?: string;
}>;
"veille": Record<string, {
id: string;
body?: string;
collection: "veille";
data: InferEntrySchema<"veille">;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
export type ContentConfig = typeof import("../src/content.config.js");
}

1
.astro/data-store.json Normal file

File diff suppressed because one or more lines are too long

5
.astro/settings.json Normal file
View File

@ -0,0 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1737382034437
}
}

485
.astro/types.d.ts vendored Normal file → Executable file
View File

@ -1,483 +1,2 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
interface Render {
'.md': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
export { z } from 'astro/zod';
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
// This needs to be in sync with ImageMetadata
export type ImageFunction = () => import('astro/zod').ZodObject<{
src: import('astro/zod').ZodString;
width: import('astro/zod').ZodNumber;
height: import('astro/zod').ZodNumber;
format: import('astro/zod').ZodUnion<
[
import('astro/zod').ZodLiteral<'png'>,
import('astro/zod').ZodLiteral<'jpg'>,
import('astro/zod').ZodLiteral<'jpeg'>,
import('astro/zod').ZodLiteral<'tiff'>,
import('astro/zod').ZodLiteral<'webp'>,
import('astro/zod').ZodLiteral<'gif'>,
import('astro/zod').ZodLiteral<'svg'>,
import('astro/zod').ZodLiteral<'avif'>,
]
>;
}>;
type BaseSchemaWithoutEffects =
| import('astro/zod').AnyZodObject
| import('astro/zod').ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
| import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
| import('astro/zod').ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
type BaseSchema =
| BaseSchemaWithoutEffects
| import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
export type SchemaContext = { image: ImageFunction };
type DataCollectionConfig<S extends BaseSchema> = {
type: 'data';
schema?: S | ((context: SchemaContext) => S);
};
type ContentCollectionConfig<S extends BaseSchema> = {
type?: 'content';
schema?: S | ((context: SchemaContext) => S);
};
type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionConfig<S>;
export function defineCollection<S extends BaseSchema>(
input: CollectionConfig<S>
): CollectionConfig<S>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[]
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[]
): Promise<CollectionEntry<C>[]>;
export function reference<C extends keyof AnyEntryMap>(
collection: C
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
"articles": {
"en/2022.md": {
id: "en/2022.md";
slug: "en/2022";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"en/2023.md": {
id: "en/2023.md";
slug: "en/2023";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"en/after-effects-expressions.mdx": {
id: "en/after-effects-expressions.mdx";
slug: "en/after-effects-expressions";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"en/faq.md": {
id: "en/faq.md";
slug: "en/faq";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"en/gratuiste.md": {
id: "en/gratuiste.md";
slug: "en/gratuiste";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"en/sci-hub-blocage.mdx": {
id: "en/sci-hub-blocage.mdx";
slug: "en/sci-hub-blocage";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"en/the-day-I-jamd.mdx": {
id: "en/the-day-I-jamd.mdx";
slug: "en/the-day-i-jamd";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"en/video-compression.mdx": {
id: "en/video-compression.mdx";
slug: "en/video-compression";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"fr/2022.md": {
id: "fr/2022.md";
slug: "fr/2022";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"fr/2023.md": {
id: "fr/2023.md";
slug: "fr/2023";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"fr/after-effects-expressions.md": {
id: "fr/after-effects-expressions.md";
slug: "fr/after-effects-expressions";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"fr/faq.md": {
id: "fr/faq.md";
slug: "fr/faq";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"fr/gratuiste.md": {
id: "fr/gratuiste.md";
slug: "fr/gratuiste";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
"fr/sci-hub-blocage.mdx": {
id: "fr/sci-hub-blocage.mdx";
slug: "fr/sci-hub-blocage";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"fr/the-day-I-jamd.mdx": {
id: "fr/the-day-I-jamd.mdx";
slug: "fr/the-day-i-jamd";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] };
"fr/video-compression.md": {
id: "fr/video-compression.md";
slug: "fr/video-compression";
body: string;
collection: "articles";
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] };
};
"fragments": {
"en/acme-sh-tls-cert.md": {
id: "en/acme-sh-tls-cert.md";
slug: "en/acme-sh-tls-cert";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"en/array-vs-array.md": {
id: "en/array-vs-array.md";
slug: "en/array-vs-array";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"en/buttons.md": {
id: "en/buttons.md";
slug: "en/buttons";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"en/image-full.mdx": {
id: "en/image-full.mdx";
slug: "en/image-full";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".mdx"] };
"en/nuxt-graphql-static.md": {
id: "en/nuxt-graphql-static.md";
slug: "en/nuxt-graphql-static";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"en/super-cookies.mdx": {
id: "en/super-cookies.mdx";
slug: "en/super-cookies";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".mdx"] };
"en/toulouse-fun.md": {
id: "en/toulouse-fun.md";
slug: "en/toulouse-fun";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"en/visited-links.md": {
id: "en/visited-links.md";
slug: "en/visited-links";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"fr/acme-sh-tls-cert.md": {
id: "fr/acme-sh-tls-cert.md";
slug: "fr/acme-sh-tls-cert";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"fr/array-vs-array.md": {
id: "fr/array-vs-array.md";
slug: "fr/array-vs-array";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"fr/buttons.mdx": {
id: "fr/buttons.mdx";
slug: "fr/buttons";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".mdx"] };
"fr/image-full.mdx": {
id: "fr/image-full.mdx";
slug: "fr/image-full";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".mdx"] };
"fr/nuxt-graphql-static.md": {
id: "fr/nuxt-graphql-static.md";
slug: "fr/nuxt-graphql-static";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"fr/super-cookies.mdx": {
id: "fr/super-cookies.mdx";
slug: "fr/super-cookies";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".mdx"] };
"fr/toulouse-fun.md": {
id: "fr/toulouse-fun.md";
slug: "fr/toulouse-fun";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
"fr/visited-links.md": {
id: "fr/visited-links.md";
slug: "fr/visited-links";
body: string;
collection: "fragments";
data: InferEntrySchema<"fragments">
} & { render(): Render[".md"] };
};
"references": {
"en/3w.md": {
id: "en/3w.md";
slug: "en/3w";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"en/natureo.md": {
id: "en/natureo.md";
slug: "en/natureo";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"en/parole-expression.md": {
id: "en/parole-expression.md";
slug: "en/parole-expression";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"en/rose-primaire.md": {
id: "en/rose-primaire.md";
slug: "en/rose-primaire";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"fr/3w.md": {
id: "fr/3w.md";
slug: "fr/3w";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"fr/natureo.md": {
id: "fr/natureo.md";
slug: "fr/natureo";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"fr/parole-expression.md": {
id: "fr/parole-expression.md";
slug: "fr/parole-expression";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
"fr/rose-primaire.md": {
id: "fr/rose-primaire.md";
slug: "fr/rose-primaire";
body: string;
collection: "references";
data: InferEntrySchema<"references">
} & { render(): Render[".md"] };
};
};
type DataEntryMap = {
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ContentConfig = typeof import("../src/content/config");
}
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

4
.gitignore vendored Normal file → Executable file
View File

@ -1,5 +1,6 @@
# build output
dist/
.astro/
# dependencies
node_modules/
@ -17,3 +18,6 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
# astro
astro_tmp_pages_*

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
**/*.d.ts
eslintrc-auto-import.mjs

34
.prettierrc.mjs Normal file
View File

@ -0,0 +1,34 @@
/** @type {import("prettier").Config} */
const config = {
arrowParens: 'always',
bracketSameLine: false,
bracketSpacing: true,
embeddedLanguageFormatting: 'auto',
endOfLine: 'lf',
htmlWhitespaceSensitivity: 'css',
insertPragma: false,
jsxSingleQuote: true,
printWidth: 80,
proseWrap: 'preserve',
quoteProps: 'as-needed',
requirePragma: false,
semi: false,
singleAttributePerLine: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'none',
useTabs: true,
vueIndentScriptAndStyle: false,
plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-astro'],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro'
}
}
]
}
export default config

0
.vscode/extensions.json vendored Normal file → Executable file
View File

11
.vscode/launch.json vendored
View File

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

21
.vscode/settings.json vendored Normal file → Executable file
View File

@ -1,17 +1,8 @@
{
"cssvar.files": [
"./node_modules/open-props/open-props.min.css",
// if you have an alternative path to where your styles are located
// you can add it in this array of files
"assets/styles/variables.css"
],
// Do not ignore node_modules css files, which is ignored by default
"cssvar.ignore": [],
// add support for autocomplete in JS or JS like files
"cssvar.extensions": [
"css", "jsx", "tsx"
],
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.insertSpaces": false,
"editor.detectIndentation": false,
"editor.tabSize": 2,
"astro.content-intellisense": true
}

0
README.md Normal file → Executable file
View File

53
astro.config.mjs Normal file → Executable file
View File

@ -1,33 +1,40 @@
import { defineConfig } from "astro/config";
import { defineConfig } from 'astro/config'
// https://github.com/alexandre-fernandez/astro-i18n
import i18n from "astro-i18n";
import mdx from '@astrojs/mdx'
import sitemap from '@astrojs/sitemap'
import rehypeExternalLinks from 'rehype-external-links'
// https://astro.build/config
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import expressiveCode from 'astro-expressive-code'
// https://astro.build/config
export default defineConfig({
site: "https://www.nardu.in",
site: 'https://www.nardu.in',
build: {
format: 'directory'
},
image: {
domains: ["assets.nardu.in"],
remotePatterns: [{ protocol: "https" }],
domains: ['assets.nardu.in'],
remotePatterns: [{ protocol: 'https' }]
},
markdown: {
syntaxHighlight: "prism",
rehypePlugins: [[rehypeExternalLinks, { rel: ['noopener noreferer'] }]]
},
integrations: [
i18n(),
mdx(),
sitemap({
i18n: {
defaultLocale: "fr", // All urls that don't contain `en`
locales: {
fr: "fr-FR", // The `defaultLocale` value must present in `locales` keys
en: "en-US",
},
},
expressiveCode({
theme: 'one-dark-pro',
plugins: [pluginLineNumbers()],
defaultProps: {
// Disable line numbers by default
showLineNumbers: false,
// But enable line numbers for certain languages
overridesByLang: {
'css,html,js,ts,vue': {
showLineNumbers: true
}
}
}
}),
],
});
mdx(),
sitemap()
]
})

View File

@ -1,20 +0,0 @@
import { defineAstroI18nConfig } from "astro-i18n";
export default defineAstroI18nConfig({
defaultLangCode: "fr",
supportedLangCodes: ["en"],
showDefaultLangCode: false,
trailingSlash: "never",
translations: {
fr: "src/i18n/fr.json",
en: "src/i18n/en.json",
},
routeTranslations: {
en: {
"sci-hub-blocage": "sci-hub-unblock",
fragments: "snippets",
"plan-du-site": "sitemap",
references: "work",
},
},
});

BIN
bun.lockb Executable file

Binary file not shown.

33
package.json Normal file → Executable file
View File

@ -1,27 +1,30 @@
{
"name": "@example/minimal",
"name": "nardude",
"type": "module",
"version": "0.0.1",
"version": "3.0.0",
"private": true,
"scripts": {
"dev": "astro-i18n sync && astro dev",
"start": "astro dev",
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"i18n:install": "astro-i18n install",
"i18n:sync": "astro-i18n sync"
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^1.1.0",
"@astrojs/rss": "^3.0.0",
"@astrojs/sitemap": "^3.0.0",
"astro": "3.1.1",
"astro-i18n": "1.8.1",
"sharp": "^0.32.6"
"@astrojs/mdx": "4.0.7",
"@astrojs/rss": "4.0.11",
"@astrojs/sitemap": "^3.2.1",
"@astrojs/ts-plugin": "^1.10.4",
"@expressive-code/plugin-line-numbers": "^0.40.1",
"astro": "5.1.8",
"astro-expressive-code": "^0.40.1",
"rehype-external-links": "^3.0.0",
"sharp": "^0.33.5"
},
"devDependencies": {
"autoprefixer": "^10.4.13",
"postcss": "^8.4.20"
"autoprefixer": "^10.4.20",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-organize-imports": "^4.1.0"
}
}

File diff suppressed because it is too large Load Diff

0
postcss.config.cjs Normal file → Executable file
View File

0
public/assets/images/home/about.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 582 B

0
public/assets/images/home/icon-desktop.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

0
public/assets/images/home/icon-heart.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

0
public/assets/images/home/icon-methodo.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

0
public/assets/images/home/icon-mobile.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

0
public/assets/images/home/icon-philo.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

0
public/assets/images/home/methodo-1.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

0
public/assets/images/home/methodo.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 953 B

After

Width:  |  Height:  |  Size: 953 B

0
public/assets/images/home/offre-1.1.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 821 B

After

Width:  |  Height:  |  Size: 821 B

0
public/assets/images/home/offre-1.2.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

0
public/assets/images/home/offre-1.3.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

0
public/assets/svg/anchor.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 751 B

0
public/assets/svg/arrow-right-white.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

0
public/assets/svg/arrow-right.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

0
public/favicon.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 260 B

48
public/robots.txt Normal file → Executable file
View File

@ -1,2 +1,48 @@
User-agent: AI2Bot
User-agent: Ai2Bot-Dolma
User-agent: Amazonbot
User-agent: anthropic-ai
User-agent: Applebot
User-agent: Applebot-Extended
User-agent: Bytespider
User-agent: CCBot
User-agent: ChatGPT-User
User-agent: Claude-Web
User-agent: ClaudeBot
User-agent: cohere-ai
User-agent: cohere-training-data-crawler
User-agent: Crawlspace
User-agent: Diffbot
User-agent: DuckAssistBot
User-agent: FacebookBot
User-agent: FriendlyCrawler
User-agent: Google-Extended
User-agent: GoogleOther
User-agent: GoogleOther-Image
User-agent: GoogleOther-Video
User-agent: GPTBot
User-agent: iaskspider/2.0
User-agent: ICC-Crawler
User-agent: ImagesiftBot
User-agent: img2dataset
User-agent: ISSCyberRiskCrawler
User-agent: Kangaroo Bot
User-agent: Meta-ExternalAgent
User-agent: Meta-ExternalFetcher
User-agent: OAI-SearchBot
User-agent: omgili
User-agent: omgilibot
User-agent: PanguBot
User-agent: PerplexityBot
User-agent: PetalBot
User-agent: Scrapy
User-agent: SemrushBot
User-agent: Sidetrade indexer bot
User-agent: Timpibot
User-agent: VelenPublicWebCrawler
User-agent: Webzio-Extended
User-agent: YouBot
Disallow: /
User-agent: *
Allow: /
Disallow:
Sitemap: https://www.nardu.in/sitemap-index.xml

3
public/security.txt Normal file
View File

@ -0,0 +1,3 @@
Contact: mailto:security@nardu.in
Expires: 2027-01-01T11:00:00.000Z
Preferred-Languages: en, fr

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 582 B

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

View File

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

View File

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 671 B

View File

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

View File

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 953 B

After

Width:  |  Height:  |  Size: 953 B

View File

Before

Width:  |  Height:  |  Size: 821 B

After

Width:  |  Height:  |  Size: 821 B

0
src/images/oui.jpg → src/assets/images/oui.jpg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

29
src/components/AstroImage.astro Normal file → Executable file
View File

@ -1,44 +1,45 @@
---
import { Image, getImage } from "astro:assets";
import { Image, getImage } from 'astro:assets'
const { src, alt, width, height, ...attrs } = Astro.props;
const { src, alt, width, height, ...attrs } = Astro.props
// if h/w attributes are declared, use them. If not, use from the source file
const imgHeight = height ? height : src.height;
const imgWidth = width ? width : src.width;
const imgHeight = height ? height : src.height
const imgWidth = width ? width : src.width
// compute avif and webp format in order to use inside a <picture> element
const imgAvif = await getImage({
src: src,
format: "avif",
format: 'avif',
width: Number(imgWidth),
height: Number(imgHeight),
});
height: Number(imgHeight)
})
const imgWebp = await getImage({
src: src,
format: "webp",
format: 'webp',
// need Number() because sharp wants an integer and not a string for w/h
width: Number(imgWidth),
height: Number(imgHeight),
});
height: Number(imgHeight)
})
---
<picture>
<source
srcset={imgAvif.src}
sizes={`(max-inline-size: ${imgWidth}px) 100vw, ${imgHeight}px`}
type="image/avif"
type='image/avif'
/>
<source
srcset={imgWebp.src}
sizes={`(max-inline-size: ${imgWidth}px) 100vw, ${imgHeight}px`}
type="image/webp"
type='image/webp'
/>
<Image
src={src}
width={Number(imgWidth)}
height={Number(imgHeight)}
format="jpg"
alt={alt ? alt : ""}
format='jpg'
alt={alt ? alt : ''}
{...attrs}
/>
</picture>

161
src/components/CardEditorial.astro Normal file → Executable file
View File

@ -1,20 +1,19 @@
---
import ListTags from "./ListTags.astro";
import { l, t } from "astro-i18n";
import ListTags from './ListTags.astro'
const { item, routeName } = Astro.props;
const { item, routeName } = Astro.props
// no link on references cards
const isReference = routeName === t("references.slug");
const isReference = routeName === 'references'
---
<div class:list={["card", { "card--link": !isReference }]}>
<div class='card'>
<h3>
{
!isReference ? (
<a
class="clean-link card__link"
href={`${l(`/${routeName}`)}/${item.data.permalink}`}
class='clean-link card__link'
href={`/${routeName}/${item.data.slug}`}
>
{item.data.title}
</a>
@ -27,15 +26,15 @@ const isReference = routeName === t("references.slug");
<ListTags list={item.data.tags} />
{
isReference && (
<a href={item.data.url} rel="noopener noreferer">
{t("references.cta")}
<span class="sr-only"> {item.data.title}</span>
<a href={item.data.url} rel='noopener noreferer'>
Consulter le site
<span class='sr-only'> {item.data.title}</span>
</a>
)
}
</div>
<style define:vars={{ permalink: item.data.permalink }}>
<style define:vars={{ slug: item.data.slug }}>
.card {
padding: var(--space-s-m) var(--space-xs-s);
position: relative;
@ -45,58 +44,75 @@ const isReference = routeName === t("references.slug");
box-shadow: var(--shadow-elevation-medium);
background-color: white;
}
/*
* to be replaced with .card:has(a)
* when firefox supports it
*/
.card--link:hover {
box-shadow: var(--shadow-elevation-high);
}
.card--link:focus-within {
box-shadow: var(--shadow-elevation-high);
}
.card--link::after {
content: "";
position: absolute;
inline-size: 30px;
block-size: 30px;
top: var(--space-m);
right: var(--space-s);
opacity: 0;
background-image: url("/assets/svg/arrow-right.svg");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
transform: translateX(1rem);
}
.card--link:hover::after,
.card--link:focus-within::after {
transform: translateX(0);
opacity: 1;
}
.card--link:hover h3 a {
text-decoration: underline;
}
.card--link:hover h3 a,
.card--link:focus-within h3 a {
color: var(--color-brique);
}
.card--link::before {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
block-size: 100%;
inline-size: 2px;
transform: scaleY(0);
transform-origin: bottom;
background-color: var(--color-brique);
}
.card--link:hover::before,
.card--link:focus-within::before {
transform: scaleY(1);
transform-origin: top;
/* selects card that do not have an external link */
.card:not(:has([rel='noopener noreferer'])) {
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
block-size: 100%;
inline-size: 2px;
transform: scaleY(0);
transform-origin: bottom;
background-color: var(--color-brique);
}
&::after {
content: '';
position: absolute;
inline-size: 30px;
block-size: 30px;
top: var(--space-m);
right: var(--space-s);
opacity: 0;
background-image: url('/assets/svg/arrow-right.svg');
background-repeat: no-repeat;
background-position: center;
background-size: contain;
transform: translateX(1rem);
}
&:hover,
&:focus-within {
box-shadow: var(--shadow-elevation-high);
&::before {
transform: scaleY(1);
transform-origin: top;
}
&::after {
transform: translateX(0);
opacity: 1;
transition-duration: 0.6s;
transition-timing-function: var(--timing-bounce);
}
h3 a {
text-decoration: underline;
color: var(--color-brique);
}
}
@media (prefers-reduced-motion: no-preference) {
& {
transition: box-shadow 0.2s ease;
}
&::before {
transition: transform 0.4s var(--timing-out-expo);
}
&::after {
transition-property: opacity, transform;
transition-duration: 0.3s;
transition-timing-function: ease-in-out;
}
h3 a {
transition: color ease 0.2s;
}
}
}
h3 {
@ -109,27 +125,10 @@ const isReference = routeName === t("references.slug");
color: var(--color-blue);
}
.card__link::after {
content: "";
content: '';
position: absolute;
inset: 0;
}
@media (prefers-reduced-motion: no-preference) {
.card {
transition: box-shadow 0.2s ease;
}
.card::before {
transition: transform 0.2s ease-in-out;
}
.card::after {
transition: opacity ease 0.2s, transform ease 0.2s;
}
.card--link {
view-transition-name: var(--permalink);
}
h3 a {
transition: color ease 0.2s;
}
}
.card h4 {
margin-block-start: var(--space-2xs);

29
src/components/EditorialContent.astro Normal file → Executable file
View File

@ -1,34 +1,31 @@
---
// import { renderContent } from "astro-i18n";
import MetaDate from "./MetaDate.astro";
import TOC from "./TOC.astro";
import { render } from 'astro:content'
const { content } = Astro.props;
const { Content, headings } = await content.render();
// const { html, headings } = await renderContent(Astro, content);
import MetaDate from './MetaDate.astro'
import TOC from './TOC.astro'
const { content } = Astro.props
const { Content, headings } = await render(content)
const toc = headings.map((heading) => {
return heading;
});
import "../styles/vendor/one-dark-pro.css";
return heading
})
---
<div class="sidebar region">
<div class='sidebar region'>
<TOC toc={toc} />
<article class="flow editorial">
<article class='flow editorial'>
<h1>{content.data.title}</h1>
<p class="h3">{content.data.subtitle}</p>
<p class='h3'>{content.data.subtitle}</p>
<MetaDate item={content.data} />
<!-- <div class="flow content" set:html={html}> -->
<Content />
</article>
</div>
<style define:vars={{ permalink: content.data.permalink }}>
<style define:vars={{ slug: content.data.slug }}>
@media (prefers-reduced-motion: no-preference) {
article {
view-transition-name: var(--permalink);
view-transition-name: var(--slug);
}
}
.sidebar {

49
src/components/Footer.astro Normal file → Executable file
View File

@ -1,24 +1,30 @@
---
import { l, t } from "astro-i18n";
import { Picture } from 'astro:assets'
import SocialRel from '../components/SocialRel.astro'
import fight from '../assets/images/fight-fascism.webp'
---
<footer class="footer" role="contentinfo">
<section class="flow">
<p>Nicolas Arduin</p>
<p>{t("tagline")}</p>
<ul class="flow" role="list">
<li>
<a href="mailto:contact@nardu.in" title={t("contact.email")}
>contact@nardu.in</a
>
</li>
<li>
<a href="tel:+33749464239" title={t("contact.tel")}>+337 49 46 42 39</a>
</li>
<li><a href={l("/veille")}>{t("veille.titre")}</a></li>
<li><a href={l("/plan-du-site")}>{t("sitemap")}</a></li>
<li><a href={`${l("/")}rss.xml`}>RSS</a></li>
<footer class='footer wrapper flow' role='contentinfo'>
<section class='info'>
<div>
<p class='h4'>Nicolas Arduin</p>
<p>Développeur web spécialisé en accessibilité.</p>
</div>
<ul class='flow' role='list'>
<li><a href='/veille'>Veille</a></li>
<li><a href='/plan-du-site'>Plan du site</a></li>
<li><a href='/rss.xml'>RSS</a></li>
</ul>
<SocialRel />
</section>
<section class='supports'>
<a
rel='noopener noreferer'
href='https://fightfascism.glitch.me/'
class='img-link'
>
<Picture src={fight} formats={['avif', 'webp']} alt='Fight fascism' />
</a>
</section>
</footer>
@ -37,4 +43,13 @@ import { l, t } from "astro-i18n";
font-weight: 500;
color: var(--color-blue);
}
.info,
.supports {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
gap: var(--space-s);
text-align: left;
}
</style>

38
src/components/Head.astro Normal file → Executable file
View File

@ -1,17 +1,31 @@
---
import { t } from "astro-i18n";
const { pageTitle } = Astro.props;
const { pageTitle } = Astro.props
---
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{pageTitle} - Nicolas Arduin</title>
<meta name="description" content={t("seo.meta.description")} />
<meta name="robots" content="index,follow." />
<meta name="author" content="Nicolas Arduin" />
<meta name="subject" content="Développement de sites web, accessibilité." />
<meta name="view-transition" content="same-origin" />
<meta charset='utf-8' />
<link rel='icon' type='image/svg+xml' href='/favicon.svg' />
<link
rel='alternate'
type='application/rss+xml'
title='Nicolas Arduin'
href={new URL('rss.xml', Astro.site)}
/>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>{pageTitle} | Nicolas Arduin</title>
<meta
name='description'
content='Développeur web spécialisé en accessibilité numérique à Toulouse. Création de sites web sur mesure, mise en conformité RGAA, maintenance, etc.'
/>
<meta name='robots' content='index,follow.' />
<meta name='author' content='Nicolas Arduin' />
<meta name='subject' content='Développement de sites web, accessibilité.' />
<meta name='fediverse:creator' content='@narduin@mastodon.tetaneutral.net' />
<meta name='theme-color' content='#2e02cf' />
<meta property='og:title' content={`${pageTitle} | Nicolas Arduin`} />
<meta
property='og:description'
content='Développeur web spécialisé en accessibilité numérique à Toulouse. Création de sites web sur mesure, mise en conformité RGAA, maintenance, etc.'
/>
</head>

14
src/components/Header.astro Normal file → Executable file
View File

@ -1,15 +1,11 @@
---
import { t, l } from "astro-i18n";
import Navigation from "../components/Navigation.astro";
import Navigation from '../components/Navigation.astro'
---
<header role="banner">
<a href="#skip-content" class="skip-link"> {t("header.skipLink")}</a>
<div class="container">
<a href={l("/")} class="logo clean-link" aria-label={t("header.homeLink")}
>nardu.in</a
>
<header role='banner'>
<a href='#skip-content' class='skip-link'>Accéder au contenu</a>
<div class='container'>
<a href='/' class='logo clean-link'>nardu.in</a>
<Navigation />
</div>
</header>

View File

@ -1,39 +0,0 @@
---
import { l, astroI18n } from "astro-i18n";
// get all the locales available on the website and remove the one currently in use
const availableLocales = astroI18n.langCodes.filter(
(locale) => locale !== astroI18n.langCode
);
// current path
const currentRoute = Astro.url.pathname;
function localeName(locale) {
let localeName = "";
switch (locale) {
case "fr":
localeName = "Français";
break;
case "en":
localeName = "English";
break;
}
return localeName;
}
---
<ul role="list">
{
// create a list of available alternative locale
availableLocales.map((locale) => (
<li>
<a
href={l(currentRoute as any, {}, locale as any)}
class="clean-link nice-link"
>
{localeName(locale)}
</a>
</li>
))
}
</ul>

8
src/components/ListCards.astro Normal file → Executable file
View File

@ -1,13 +1,13 @@
---
const { list, routeName } = Astro.props;
const { list, routeName } = Astro.props
import CardEditorial from "./CardEditorial.astro";
import CardEditorial from './CardEditorial.astro'
---
<ul class="list" role="list">
<ul class='list' role='list'>
{
list.map((item) => (
<li class="list__item">
<li class='list__item'>
<CardEditorial item={item} routeName={routeName} />
</li>
))

7
src/components/ListTags.astro Normal file → Executable file
View File

@ -1,12 +1,13 @@
---
const { list } = Astro.props;
const { list } = Astro.props
---
<ul class="tags-list" role="list">
<ul class='tags-list' role='list'>
{
list.map((tag, index) => (
<li>
{tag}{index + 1 < list.length && <span>, </span>}
{tag}
{index + 1 < list.length && <span>, </span>}
</li>
))
}

57
src/components/MetaDate.astro Normal file → Executable file
View File

@ -1,26 +1,47 @@
---
import { t, astroI18n } from "astro-i18n";
astroI18n.init(Astro);
import type { ArticleFrontmatter, FragmentFrontmatter } from '../content.config'
const { item } = Astro.props;
type Props = {
item: ArticleFrontmatter | FragmentFrontmatter
}
function formatDate(date) {
const options = { year: "numeric", month: "long", day: "numeric" };
return new Date(date).toLocaleDateString(astroI18n.langCode, options);
const { item } = Astro.props
const locale: 'fr' | 'en' = item.lang
let ui = {
published: 'Publié le',
updated: 'Mis à jour le'
}
switch (locale) {
case 'en':
ui = {
published: 'Published on',
updated: 'Last updated on'
}
break
}
function formatDate(date: Date) {
return new Date(date).toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
// Needed because browser will transform the value to a readable english date in 'datetime' if not forced
// see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time#attr-datetime for valid formats
function rawDate(date) {
return new Date(date).toISOString();
function rawDate(date: Date) {
return new Date(date).toISOString()
}
---
<div class="meta">
<div class='meta'>
{
!!item.createdAt && (
<p class="meta__date">
{t("meta.publication")}&nbsp;:
<p class='meta__date'>
{ui.published}&nbsp;:
<time datetime={rawDate(item.createdAt)}>
{formatDate(item.createdAt)}
</time>
@ -28,11 +49,11 @@ function rawDate(date) {
)
}
{
formatDate(item.createdAt) != formatDate(item.updatedAt) &&
!!item.createdAt &&
!!item.updatedAt && (
<p class="meta__date">
{t("meta.modification")}&nbsp;:
!!item.createdAt &&
!!item.updatedAt &&
formatDate(item.createdAt) != formatDate(item.updatedAt) && (
<p class='meta__date'>
{ui.updated}&nbsp;:
<time datetime={rawDate(item.updatedAt)}>
{formatDate(item.updatedAt)}
</time>
@ -41,8 +62,8 @@ function rawDate(date) {
}
{
!item.createdAt && !!item.updatedAt && (
<p class="meta__date">
{t("meta.modification")}&nbsp;:
<p class='meta__date'>
{ui.updated}&nbsp;:
<time datetime={rawDate(item.updatedAt)}>
{formatDate(item.updatedAt)}
</time>

39
src/components/Navigation.astro Normal file → Executable file
View File

@ -1,37 +1,24 @@
---
import { t, l } from "astro-i18n";
import LanguageSwitcher from "./LangSwitcher.astro";
---
<nav role="navigation" aria-label={t("header.mainNav")}>
<ul class="main-nav" role="list">
<nav role='navigation' aria-label='Menu principal'>
<ul class='main-nav' role='list'>
<li>
<a href={l("/articles")} class="clean-link nice-link"
>{t("article.titre")}</a
>
<span aria-hidden="true">&middot;</span>
<a href='/articles' class='clean-link nice-link'>Articles&nbsp;</a>
<span aria-hidden='true'>&middot;</span>
</li>
<li>
<a href={l("/fragments")} class="clean-link nice-link"
>{t("fragments.titre")}</a
>
<span aria-hidden="true">&middot;</span>
<a href='/fragments' class='clean-link nice-link'>Fragments&nbsp;</a>
<span aria-hidden='true'>&middot;</span>
</li>
<li>
<a href={l("/references")} class="clean-link nice-link"
>{t("references.titre")}</a
>
<span aria-hidden="true">&middot;</span>
<a href='/references' class='clean-link nice-link'>Références&nbsp;</a>
<span aria-hidden='true'>&middot;</span>
</li>
<li>
<a
href="mailto:contact@nardu.in"
class="clean-link nice-link"
title={t("contact.email")}>{t("contact.title")}</a
><span aria-hidden="true">&nbsp;&#x7C;</span>
</li>
<li>
<LanguageSwitcher />
href='mailto:contact@nardu.in'
class='clean-link nice-link'
title='Envoyez-moi un mail (ouverture du logiciel automatique).'
>contact</a
>
</li>
</ul>
</nav>

24
src/components/QuickAccessCard.astro Normal file → Executable file
View File

@ -1,19 +1,19 @@
---
const { item } = Astro.props;
const { item } = Astro.props
---
<div class="card">
<div class="card-container">
<div class="card__illustration">
<div class='card'>
<div class='card-container'>
<div class='card__illustration'>
<img
src={item.quickImage}
width="150"
height="150"
alt=""
aria-hidden="true"
width='150'
height='150'
alt=''
aria-hidden='true'
/>
</div>
<a href={`#${item.id}`} class="card__link clean-link">{item.quickTitle}</a>
<a href={`#${item.uid}`} class='card__link clean-link'>{item.quickTitle}</a>
</div>
</div>
@ -52,17 +52,17 @@ const { item } = Astro.props;
text-decoration: none;
}
.card__link::after {
content: "";
content: '';
position: absolute;
inset: 0;
}
@media (prefers-reduced-motion: no-preference) {
.card {
transition: all ease 0.2s;
transition: all var(--timing-bounce) 0.4s;
}
.card:hover {
transform: translateY(-10px);
box-shadow: var(--shadow-elevation-high);
box-shadow: 0 4px 0 0 var(--accent-color);
}
.card:focus-within {
transform: translateY(-10px);

View File

@ -0,0 +1,12 @@
<ul class='flow' role='list'>
<li><a href='mailto:contact@nardu.in' rel='me'>contact@nardu.in</a></li>
<li>
<a href='tel:+33749464239' rel='me'>+337 49 46 42 39</a>
</li>
<li><a href='https://github.com/narduin' rel='me'>@narduin sur Github</a></li>
<li>
<a rel='me' href='https://mastodon.tetaneutral.net/@narduin'
>@narduin sur Mastodon</a
>
</li>
</ul>

21
src/components/TOC.astro Normal file → Executable file
View File

@ -1,13 +1,12 @@
---
import { t } from "astro-i18n";
const { toc } = Astro.props;
const { toc } = Astro.props
---
<aside class="table-of-content">
<details open="true">
<summary>{t("index.toc")}</summary>
<nav role="navigation" aria-label={t("index.toc")}>
<ol class="table-of-content__list" role="list">
<aside class='table-of-content'>
<details open='true'>
<summary id='toc-title'>Table des matières</summary>
<nav role='navigation' aria-labelledby='toc-title'>
<ol class='table-of-content__list' role='list'>
{
// loop over the toc
toc.map(
@ -17,14 +16,14 @@ const { toc } = Astro.props;
) =>
heading.depth === 2 ? (
<li>
<a href={`#${heading.slug}`} class="toc-2">
<a href={`#${heading.slug}`} class='toc-2'>
{heading.text}
</a>
</li> // if h3, set as inner ol > li
) : heading.depth === 3 ? (
<ol role="list">
<ol role='list'>
<li>
<a href={`#${heading.slug}`} class="toc-3">
<a href={`#${heading.slug}`} class='toc-3'>
{heading.text}
</a>
</li>
@ -70,7 +69,7 @@ const { toc } = Astro.props;
}
.table-of-content__list a::before {
content: "";
content: '';
position: absolute;
top: 50%;
left: 0;

133
src/content.config.ts Executable file
View File

@ -0,0 +1,133 @@
import { glob } from 'astro/loaders'
import type { CollectionEntry } from 'astro:content'
import { defineCollection, z } from 'astro:content'
// TYPES
const articleSchema = z.object({
title: z.string(),
subtitle: z.string(),
lang: z.enum(['fr', 'en']),
tags: z.array(z.string()), // An array of strings
type: z.string(),
slug: z.string(),
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false
})
const fragmentSchema = z.object({
title: z.string(),
subtitle: z.string(),
lang: z.enum(['fr', 'en']),
tags: z.array(z.string()), // An array of strings
type: z.string(),
slug: z.string(),
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false
})
const referenceSchema = z.object({
title: z.string(),
subtitle: z.string(),
url: z.string(),
lang: z.enum(['fr', 'en']),
slug: z.string(),
tags: z.array(z.string()), // An array of strings
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false
})
const HPsectionSchema = z.object({
type: z.string(),
lang: z.enum(['fr', 'en']),
uid: z.string().optional(),
image: z.string().optional(),
order: z.number(),
quickTitle: z.string().optional(),
quickImage: z.string().optional(),
reference: z.string().optional()
})
const VeilleSchema = z.object({
lang: z.enum(['fr', 'en']),
title: z.string(),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional()
})
// COLLECTIONS
const articles = defineCollection({
loader: glob({
pattern: '**/[^_]*.{md,mdx}',
base: './src/content/articles'
}),
schema: articleSchema
})
const fragments = defineCollection({
loader: glob({
pattern: '**/[^_]*.{md,mdx}',
base: './src/content/fragments'
}),
schema: fragmentSchema
})
const references = defineCollection({
loader: glob({
pattern: '**/[^_]*.{md,mdx}',
base: './src/content/references'
}),
schema: referenceSchema
})
// DATA
const HPsections = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/data/HP' }),
schema: HPsectionSchema
})
const veille = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/data/veille' }),
schema: VeilleSchema
})
export const collections = {
// Don't forget 'quotes' for collection names containing dashes
articles,
fragments,
references,
HPsections,
veille
}
// TYPES from the schemas
export type ArticleFrontmatter = z.infer<typeof articleSchema>
export type FragmentFrontmatter = z.infer<typeof fragmentSchema>
export type ReferenceFrontmatter = z.infer<typeof referenceSchema>
export type HPsectionsFrontmatter = z.infer<typeof HPsectionSchema>
export type VeilleFrontmatter = z.infer<typeof VeilleSchema>
// full entry type including data, body, slug etc.
export type ArticleEntry = CollectionEntry<'articles'>
export type FragmentEntry = CollectionEntry<'fragments'>
export type ReferenceEntry = CollectionEntry<'references'>
export type HPSectionEntry = CollectionEntry<'HPsections'>

6
src/content/articles/en/2022.md Normal file → Executable file
View File

@ -2,11 +2,11 @@
title: Nico v2.0
subtitle: 2022 update of many things.
lang: en
permalink: "2022"
slug: 'en-2022'
excerpt: Changes in my services, the website and myself.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2022-06-08T14:24:06.000Z"
createdAt: '2022-06-08T14:24:06.000Z'
---
After two years of full-time freelancing, I took a step back from my activity. I especially questioned my positioning and the services I was offering.

8
src/content/articles/en/2023.md Normal file → Executable file
View File

@ -2,12 +2,12 @@
title: Nico v2.5
subtitle: Update 2023.
lang: en
permalink: "2023"
slug: 'en-2023'
excerpt: New changes.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2023-02-03T17:41:00.000Z"
updatedAt: "2023-05-17T17:41:00.000Z"
createdAt: '2023-02-03T17:41:00.000Z'
updatedAt: '2023-05-17T17:41:00.000Z'
---
This article will be updated when I have something new to share during the year 2023.

View File

@ -0,0 +1,18 @@
---
title: Nico v3.0
subtitle: This website no longer has an english version..
lang: en
slug: en-2025
excerpt: This website no longer has an english version.
tags: ['Freelance']
type: articles
createdAt: '2025-01-02T17:41:00.000Z'
---
## So long <code style="font-size: var(--size-2)">lang="en"</code>
I have maintained an english version of my website for some years but **no more…** The effort needed to make a fully multilingual website with Astro is just too much work for my small personal website.
**All previous content will remain online** ([articles](/articles/#en-articles) and [snippets](/fragments/#en-fragments)) but the interface and internal pages will only be in french.
I do not have any metrics on who visits my website but I don't think it will be missed. If you do miss it, don't hesitate to send me an email!

40
src/content/articles/en/after-effects-expressions.mdx Normal file → Executable file
View File

@ -2,15 +2,15 @@
title: After Effects Expressions
subtitle: Animation on steroïds.
lang: en
permalink: "after-effects-expressions"
slug: 'en-after-effects-expressions'
excerpt: Expressions in After Effects have always been blurry for me. I know they exist, I know they're powerful, I know it could save a lot of time and clean complex keyframe filled compositions but… They are hard to learn!
tags: ["Design"]
tags: ['Design']
type: articles
createdAt: "2019-04-24T09:00:00.000Z"
createdAt: '2019-04-24T09:00:00.000Z'
code: true
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
## An ever lasting battle
@ -22,10 +22,10 @@ So the last time I had to do a complex animation, **I took the damn time!**
Everyone uses expressions whether they know it or not. Most of the time it's a rather transparent process for the animator. For example: when parenting a property to another one, After Effects creates an expression for us.
<AstroImage
src="https://assets.nardu.in/basic_expression_d81b12f1ac.jpeg"
width="728"
height="80"
alt="Parenting the position of the form to a null creates an expression."
src='https://assets.nardu.in/basic_expression_d81b12f1ac.jpeg'
width='728'
height='80'
alt='Parenting the position of the form to a null creates an expression.'
/>
Over the last updates, Adobe has made an effort to make expressions more accessible to everyone. For example, the expression box is now resembling a code editor thanks to code-coloring and auto-completion features. After Effects expression feel like JavaScript with custom functions.
@ -33,10 +33,10 @@ Over the last updates, Adobe has made an effort to make expressions more accessi
Those custom functions can be called through a menu once you enabled the expressions on a property. It offers organized shortcut and proper syntax to all of AE native functions and a bunch of JavaScript standard ones.
<AstroImage
src="https://assets.nardu.in/shortcut_39cc19d383.jpeg"
width="728"
height="322"
alt="Alt + Click the stopwatch to access the shortcuts."
src='https://assets.nardu.in/shortcut_39cc19d383.jpeg'
width='728'
height='322'
alt='Alt + Click the stopwatch to access the shortcuts.'
/>
## So I need to learn javascript to do motion design now?
@ -72,14 +72,14 @@ Let's say you need to animate a rather simple counter from 0% to 100%.
- On the effect menu add expression options > slider control
- Alt + Click the Source text property
<video width="728" loop controls preload="metadata">
<source src="https://assets.nardu.in/alt-counter.mp4" type="video/mp4" />
<video width='728' loop controls preload='metadata'>
<source src='https://assets.nardu.in/alt-counter.mp4' type='video/mp4' />
</video>
- Parent the source text to the slider control through the pickwhip.
<video width="462" loop controls preload="metadata">
<source src="https://assets.nardu.in/pickwhip.mp4" type="video/mp4" />
<video width='462' loop controls preload='metadata'>
<source src='https://assets.nardu.in/pickwhip.mp4' type='video/mp4' />
</video>
You should get something like this in the expressions panel:
@ -91,8 +91,8 @@ effect("Slider Control")("Slider")
- Set two keyframes on the slider from 0 to 100. The text should update accordingly.
- By default, After Effects does not round numbers. In the expression panel, wrap your expression with the `Math.round()` function.
<video width="728" loop controls preload="metadata">
<source src="https://assets.nardu.in/round.mp4" type="video/mp4" />
<video width='728' loop controls preload='metadata'>
<source src='https://assets.nardu.in/round.mp4' type='video/mp4' />
</video>
```javascript
@ -107,8 +107,8 @@ Still in the expression panel, we're going to add a string containing the % glyp
Math.round(effect("Slider Control")("Slider")) + '%'
```
<video width="538" loop controls preload="metadata">
<source src="https://assets.nardu.in/percent.mp4" type="video/mp4" />
<video width='538' loop controls preload='metadata'>
<source src='https://assets.nardu.in/percent.mp4' type='video/mp4' />
</video>
**And there we go!** A fully functioning and customizable counter using a slider controller and expressions.

6
src/content/articles/en/faq.md Normal file → Executable file
View File

@ -2,12 +2,12 @@
title: Accessibility and sobriety
subtitle: Translation in progress, stay tuned ;)
lang: en
permalink: "faq"
slug: 'en-faq'
draft: true
excerpt: Why, how et and especially what.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2022-06-22T15:34:45.000Z"
createdAt: '2022-06-22T15:34:45.000Z'
---
[Go back to available articles](/en/articles)

6
src/content/articles/en/gratuiste.md Normal file → Executable file
View File

@ -2,12 +2,12 @@
title: Gratuiste
subtitle: Translation in progress, stay tuned ;)
lang: en
permalink: "gratuiste"
slug: 'en-gratuiste'
draft: true
excerpt: Translation in progress, stay tuned ;)
tags: ["Design", "Freelance"]
tags: ['Design', 'Freelance']
type: articles
createdAt: "2017-05-27T07:47:36.000Z"
createdAt: '2017-05-27T07:47:36.000Z'
---
[Go back to available articles](/en/articles)

60
src/content/articles/en/sci-hub-blocage.mdx Normal file → Executable file
View File

@ -1,19 +1,19 @@
---
title: "Access blocked Sci-hub"
subtitle: "The science of sharing."
title: 'Access blocked Sci-hub'
subtitle: 'The science of sharing.'
lang: en
permalink: "sci-hub-unblock"
key: "scihub"
excerpt: "In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway."
tags: ["Internet", "Science"]
slug: 'en-sci-hub-unblock'
key: 'scihub'
excerpt: 'In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway.'
tags: ['Internet', 'Science']
type: articles
createdAt: "2019-03-31T07:47:36.000Z"
updatedAt: "2022-12-27T12:08:00.000Z"
createdAt: '2019-03-31T07:47:36.000Z'
updatedAt: '2022-12-27T12:08:00.000Z'
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
The current sci-hub address is: <a href="https://sci-hub.se" rel="noreferer noopener">sci-hub.se</a>
The current sci-hub address is: <a href="https://www.sci-hub.st/" rel="noreferer noopener">sci-hub.st</a>
## What is this about?
@ -46,10 +46,10 @@ Go to:
From there, you can add DNS servers by clicking the + icon. Click ok and apply the new settings. You might need to restart your computer for the changes to work.
<AstroImage
src="https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg"
width="728"
height="1060"
alt="MacOS network and DNS settings"
src='https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg'
width='728'
height='1060'
alt='MacOS network and DNS settings'
/>
### Windows (10)
@ -67,31 +67,31 @@ Go to:
From there, you can add DNS servers. Click save. You might need to restart your computer for the changes to work.
<AstroImage
src="https://assets.nardu.in/sci-hub-settings.jpg"
width="728"
height="319"
alt="Windows system settings"
src='https://assets.nardu.in/sci-hub-settings.jpg'
width='728'
height='319'
alt='Windows system settings'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-network.jpg"
width="728"
height="803"
alt="Windows network settings"
src='https://assets.nardu.in/sci-hub-network.jpg'
width='728'
height='803'
alt='Windows network settings'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-adapter.jpg"
width="728"
height="327"
alt="Windows network connections settings"
src='https://assets.nardu.in/sci-hub-adapter.jpg'
width='728'
height='327'
alt='Windows network connections settings'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-adapter-settings.jpg"
width="728"
height="434"
alt="Windows network adapter settings"
src='https://assets.nardu.in/sci-hub-adapter-settings.jpg'
width='728'
height='434'
alt='Windows network adapter settings'
/>
## Which DNS?

26
src/content/articles/en/the-day-I-jamd.mdx Normal file → Executable file
View File

@ -2,15 +2,15 @@
title: The day I Jamd
subtitle: A story of unusual tools and fun gambles.
lang: en
permalink: "the-day-I-jamd"
slug: 'en-the-day-I-jamd'
excerpt: Ooh, yeah! All right! Were jammin
tags: ["Dev", "Jamstack"]
tags: ['Dev', 'Jamstack']
type: articles
createdAt: "2020-10-08T09:00:00.000Z"
updatedAt: "2022-12-27T15:40:06.000Z"
createdAt: '2020-10-08T09:00:00.000Z'
updatedAt: '2022-12-27T15:40:06.000Z'
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
## The not so easy choice
@ -31,10 +31,10 @@ Boy did they exceed my expectations! With almost no optimization on the static s
### wordpress
<AstroImage
src="https://assets.nardu.in/wordpress_8ee6f54b98.jpeg"
width="728"
height="412"
alt="Performance score of 53/100 on Wordpress."
src='https://assets.nardu.in/wordpress_8ee6f54b98.jpeg'
width='728'
height='412'
alt='Performance score of 53/100 on Wordpress.'
/>
Despite a lot of efforts I could not do better. Im no expert in caching, do not use CDN and relied on plugins to achieve a lot of stuff (php not being my strongest skill 😬).
@ -42,10 +42,10 @@ Despite a lot of efforts I could not do better. Im no expert in caching, do n
### 11ty + strapi
<AstroImage
src="https://assets.nardu.in/static_2c0d9f1eb8.jpeg"
width="728"
height="412"
alt="Performance score of 97/100 on jamstack."
src='https://assets.nardu.in/static_2c0d9f1eb8.jpeg'
width='728'
height='412'
alt='Performance score of 97/100 on jamstack.'
/>
Almost **zero** special configuration (I was too anxious to test) and I reached an awesome score. I know lighthouse scores are not everything but hey, **53 vs 97**… Ill take it!

42
src/content/articles/en/video-compression.mdx Normal file → Executable file
View File

@ -2,15 +2,15 @@
title: Video compression
subtitle: Encode like you mean it.
lang: en
permalink: "video-compression"
slug: 'en-video-compression'
excerpt: How to gain precious weight when encoding videos.
tags: ["Design"]
tags: ['Design']
type: articles
createdAt: "2021-05-05T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
createdAt: '2021-05-05T09:00:00.000Z'
updatedAt: '2022-06-08T14:24:06.000Z'
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
## Let's play.
@ -27,9 +27,9 @@ With a good connection, users will not see the difference. But if we go down tha
Let's say we exported a 1920x1080 video from Premiere Pro with these basic settings:
<AstroImage
src="https://assets.nardu.in/video-premiere-1.jpeg"
width="673"
height="800"
src='https://assets.nardu.in/video-premiere-1.jpeg'
width='673'
height='800'
/>
It's gorgeous, it's Full HD, it's 1:30 minute of excellent editing but it's 50mb… What a shame.
@ -52,9 +52,9 @@ While it's not as nice as Premiere Pro, it has way more exporting capabilities.
1. Keep MPEG-4 as the format
<AstroImage
src="https://assets.nardu.in/video-handbrake-1.jpeg"
width="728"
height="337"
src='https://assets.nardu.in/video-handbrake-1.jpeg'
width='728'
height='337'
/>
### Video screen
@ -65,9 +65,9 @@ While it's not as nice as Premiere Pro, it has way more exporting capabilities.
1. Choose the type of video you are encoding (film, animation…)
<AstroImage
src="https://assets.nardu.in/video-handbrake-2.jpeg"
width="728"
height="337"
src='https://assets.nardu.in/video-handbrake-2.jpeg'
width='728'
height='337'
/>
### Audio screen
@ -80,9 +80,9 @@ If you have an audio channel, these settings are great and will not influence th
1. Bitrate 192 to 256 (your choice)
<AstroImage
src="https://assets.nardu.in/video-handbrake-3.jpeg"
width="728"
height="337"
src='https://assets.nardu.in/video-handbrake-3.jpeg'
width='728'
height='337'
/>
### Export!
@ -105,10 +105,10 @@ Webm is an html video format and VP9 is its latest codec.
Using Handbrake and webm/VP9, we can achieve really great compression without losing too much quality (or none at all depending on the settings). I was able to divide by 4 the size of a video using these presets:
<AstroImage
src="https://assets.nardu.in/video-handbrake-4.jpg"
width="728"
height="313"
alt=""
src='https://assets.nardu.in/video-handbrake-4.jpg'
width='728'
height='313'
alt=''
/>
The only down side is that it takes some time to encode. It will depend on the video length and your computing power.

8
src/content/articles/fr/2022.md Normal file → Executable file
View File

@ -2,13 +2,15 @@
title: Nico v2.0
subtitle: Mise à jour 2022 de plein de trucs.
lang: fr
permalink: "2022"
slug: '2022'
excerpt: Évolution des services, du site et de moi-même.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2022-06-08T14:24:06.000Z"
createdAt: '2022-06-08T14:24:06.000Z'
---
<a href='/articles/en-2022/' lang='en'>This content exists in english.</a>
Après deux ans de freelance à temps plein, jai pris du recul sur mon activité. Jai surtout questionné mon positionnement et les prestations que je proposais.
## Les services

10
src/content/articles/fr/2023.md Normal file → Executable file
View File

@ -2,14 +2,16 @@
title: Nico v2.5
subtitle: Mise à jour 2023.
lang: fr
permalink: "2023"
slug: '2023'
excerpt: Suite des évolutions.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2023-02-03T17:41:00.000Z"
updatedAt: "2023-05-17T17:41:00.000Z"
createdAt: '2023-02-03T17:41:00.000Z'
updatedAt: '2023-05-17T17:41:00.000Z'
---
<a href='/articles/en-2023/' lang='en'>This content exists in english.</a>
Cet article sera mis à jour lorsque j'aurai des nouveautés à partager au cours de l'année 2023.
## Le site

6
src/content/articles/fr/after-effects-expressions.md Normal file → Executable file
View File

@ -3,11 +3,11 @@ title: After Effects Expressions
subtitle: En cours de traduction, revenez bientôt ;)
lang: fr
draft: true
permalink: "after-effects-expressions"
slug: 'after-effects-expressions'
excerpt: En cours de traduction, revenez bientôt ;)
tags: ["Design"]
tags: ['Design']
type: articles
createdAt: "2019-04-24T09:00:00.000Z"
createdAt: '2019-04-24T09:00:00.000Z'
---
[Retour aux articles](/articles)

View File

@ -0,0 +1,277 @@
---
title: 'Paramétrer un serveur pour héberger des trucs'
subtitle: 'Guide personnel.'
lang: fr
slug: 'configuration-serveur'
excerpt: Envie de mettre un site en ligne&nbsp;? D'héberger vos propres outils plutôt que de payer des abonnements&nbsp;? Bah lezgongue
tags: ['Dev', 'Backend']
type: articles
createdAt: '2025-01-28T22:20:00.000Z'
---
import { Picture } from 'astro:assets'
import coolifySettings from '@assets/images/articles/configuration-serveur/coolify-admin-settings.jpg'
import coolifyNewRessource from '@assets/images/articles/configuration-serveur/coolify-new-ressource.jpg'
import coolifyConfRessource from '@assets/images/articles/configuration-serveur/coolify-conf-ressource.jpg'
## Mon mémo perso.
Cet article est l'extension d'un fichier «&nbsp;pense-bête&nbsp;» que j'utilise depuis toujours quand je crée un nouveau serveur. Généralement un <abbr lang="en" title="Virtual Private Server">VPS</abbr>, généralement sous debian ou fedora.
Si vous voulez tester des trucs, voici [un lien de parrainage](https://hetzner.cloud/?ref=Jl7yPFuoBGDM) chez l'hébergeur allemand [Hetzner](https://hetzner.cloud) pour récupérer 20€ de crédit (et 10€ pour moi si vous y restez). [Conditions de l'offre.](https://www.hetzner.com/legal/referrals)
> Notez bien que je ne suis ni administrateur système ni expert en sécurité.
## Conf serveur
### Actions immédiates
Se connecter en root via ssh puis…
#### Paramétrer les locales
Parfois il manque la configuration des [locales](https://www.tecmint.com/set-system-locales-in-linux/) et ça cause des erreurs.
```bash {"Debian":4} {"Fedora":6}
# affiche la conf actuelle
locale
+
nano /etc/default/locale
+
nano /etc/locale.conf
# compléter ces lignes si besoin
LANG=en_US.UTF-8
LANGUAGE=en_US.UTF-8
LC_ALL=en_US.UTF-8
```
#### Mettre à jour le système
```bash {"Debian":1} {"Fedora":4}
+
apt update
apt dist-upgrade
+
dnf check-update
dnf upgrade
```
#### Nouvel utilisateur
- Ajouter et renseigner un nouvel utilisateur.
- Ajouter l'utilisateur aux "sudoers".
- Ajouter sa clé ssh locale à l'utilisateur distant.
```bash {"Debian":3} {"Fedora":5} {"En local":8} {"Sur le serveur":13}
adduser USERNAME
+
usermod -aG sudo USERNAME
+
usermod -aG wheel USERNAME
+
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "mail@domain.tld"
# afficher puis copier la clé publique
cat ~/.ssh/id_ed25519.pub
+
su USERNAME
mkdir ~/.ssh
# coller la clé publique dans ce fichier
nano authorized_keys
```
#### bash alias
Ajoute un alias `ll` pour un `ls` plus explicite.
```bash title="~/.bashrc"
alias ll='ls -lah'
```
### SSH
configure [sshd_config](https://infosec.mozilla.org/guidelines/openssh)
```ssh-config title="/etc/ssh/sshd_config"
# Changer le port est recommandé mais pas obligatoire
Port 10485
# Désactive la connexion par mot de passe
AuthenticationMethods publickey
# Désactive la connexion via root
PermitRootLogin No
# modifier/adapter ces options si besoin
LoginGraceTime 120
StrictModes yes
PubkeyAuthentication yes
AuthorizedKeysFile /home/%u/.ssh/authorized_keys
PermitEmptyPasswords no
ChallengeResponseAuthentication no
X11Forwarding no
UseDNS no
MaxStartups 10:30:60
PermitTunnel no
```
Tester maintenant dans un autre terminal si la connexion ssh avec le nouvel utilisateur fonctionne.
Si oui&nbsp;: **redémarrer&nbsp;!**
### Pare-feu
- Installer
- Paramétrer
- Activer
#### Debian
[ufw](https://www.codeflow.site/fr/article/how-to-set-up-a-firewall-with-ufw-on-debian-10)
```bash
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh # ou ufw allow PORT
ufw allow http # ufw allow 80
ufw allow https # ufw allow 443
ufw enable
```
#### Fedora
[Firewalld](https://docs.fedoraproject.org/en-US/quick-docs/firewalld/#_viewing_the_current_status_of_firewalld)
```bash
dnf install firewalld
systemctl unmask firewalld
systemctl start firewalld
systemctl enable firewalld
firewall-cmd --zone=public --add-service=ssh
firewall-cmd --zone=public --add-service=http
firewall-cmd --zone=public --add-service=https
firewall-cmd --runtime-to-permanent
firewall-cmd --reload
```
### Crowdsec
[Crowdsec](https://doc.crowdsec.net/docs/getting_started/install_crowdsec/) est un système de sécurité qui détecte et bloque les connexions malveillantes.
- Installer crowdsec.
- Installer un "<span lang="en">bouncer</span>".
- Activer des [scenarios](https://doc.crowdsec.net/docs/next/cscli/cscli_scenarios_install) (facultatif).
```bash {"Debian":3} {"Fedora":6}
curl -s https://install.crowdsec.net | sudo sh
+
apt install crowdsec
apt install crowdsec-firewall-bouncer-iptables
+
dnf install crowdsec
dnf install crowdsec-firewall-bouncer-nftables
```
## Conf web
### Serveur web
J'utilisais généralement [OpenLiteSpeed](https://openlitespeed.org/) pour avoir une interface graphique, mais j'ai changé pour [Coolify](https://coolify.io) depuis quelques années.
#### Coolify
[Documentation.](https://coolify.io/docs/installation)
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash
```
> Crowdsec et Coolify utilisent tous les deux le port 8080 par défaut. Il faut le changer pour un des deux (plus facile à [faire pour Crowdsec.](https://support.crowdsec.net/hc/en-gb/articles/10831013001234--Security-Engine-How-to-change-the-default-port))
<details class="flow">
<summary>Procédure détaillée</summary>
Il faut éditer deux fichiers:
```yaml title="/etc/crowdsec/config.yaml" "8080"
api:
server:
listen_uri: 127.0.0.1:8080
```
```yaml title="/etc/crowdsec/local_api_credentials.yaml" "8080"
url: http://127.0.0.1:8080
```
Puis charger la nouvelle configuration&nbsp;:
```bash
service crowdsec reload
```
</details>
##### Configuration
Une fois installé (suivre les instructions du script), on peut paramétrer Coolify avec un nom de domaine, généralement un sous-domaine du style `coolify.domaine.tld`
<Picture
src={coolifySettings}
formats={['avif', 'webp']}
alt="Instance's domain field inside settings"
/>
##### Ajouter une ressource
Dans coolify, les «&nbsp;ressources&nbsp;» correspondent à un site ou service web&nbsp;:
- site statique
- webapp
- base de données
- image docker
- etc.
Afin d'héberger un site ou une webapp «&nbsp;maison&nbsp;», il faudra avant tout que le code soit disponible sur une forge git. Soit en public soit en ayant paramétré une clé de déploiement au préalable.
<Picture src={coolifyNewRessource} formats={['avif', 'webp']} alt='' />
##### Configurer la ressource
Coolify va essayer de déterminer des [options par défaut](https://github.com/coollabsio/coolify-examples/) selon le dépôt git. Il ne restera plus qu'à ajuster les paramètres de la ressource&nbsp;:
- url du projet
- commande d'installation des paquets
- commande de construction du projet
- commande de démarrage
- variables d'environnement
- <span lang='en'>webhooks</span>
- etc.
<Picture src={coolifyConfRessource} formats={['avif', 'webp']} alt='' />
#### OpenLiteSpeed
Plus vraiment utilisé pour de nouveaux projets et moins évident à prendre en main. Mais puissant malgré tout&nbsp;!
[Documentation.](https://docs.openlitespeed.org/installation/repo/)
```bash
wget -O - https://repo.litespeed.sh | sudo bash
apt-get install openlitespeed
```
Récupérer le mot de passe admin `cat /usr/local/lsws/adminpasswd`
### Outils
Selon les besoins du projet&nbsp;:
- git
- [nvm](https://github.com/nvm-sh/nvm#install--update-script)
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)/[pnpm](https://pnpm.io/installation)/[bun](https://bun.sh/)
- [acme.sh](https://github.com/acmesh-official/acme.sh) ([tuto complet](/fragments/acme-sh-tls-cert/))

6
src/content/articles/fr/faq.md Normal file → Executable file
View File

@ -2,11 +2,11 @@
title: Accessibilité, sobriété et F.A.Q.
subtitle: Explications sur ma vision et mon fonctionnement.
lang: fr
permalink: "faq"
slug: 'faq'
excerpt: Pourquoi, comment et surtout quèsaco.
tags: ["Freelance"]
tags: ['Freelance']
type: articles
createdAt: "2022-06-22T15:34:45.000Z"
createdAt: '2022-06-22T15:34:45.000Z'
---
## l'Accessibilité

14
src/content/articles/fr/gratuiste.md Normal file → Executable file
View File

@ -1,13 +1,13 @@
---
title: "Gratuiste"
subtitle: "Ou le travail gratuit."
title: 'Gratuiste'
subtitle: 'Ou le travail gratuit.'
lang: fr
permalink: "gratuiste"
excerpt: "Jai cherché un moyen de mettre mes compétences au service dautrui et je pense avoir trouvé: je vais travailler gratuitement pour des associations."
tags: ["Graphisme", "Freelance"]
slug: 'gratuiste'
excerpt: 'Jai cherché un moyen de mettre mes compétences au service dautrui et je pense avoir trouvé: je vais travailler gratuitement pour des associations.'
tags: ['Graphisme', 'Freelance']
type: articles
createdAt: "2017-05-27T07:47:36.000Z"
updatedAt: "2022-12-27T15:36:06.000Z"
createdAt: '2017-05-27T07:47:36.000Z'
updatedAt: '2022-12-27T15:36:06.000Z'
---
## Gratuiste, quest-ce que cest ?

60
src/content/articles/fr/sci-hub-blocage.mdx Normal file → Executable file
View File

@ -1,18 +1,20 @@
---
title: "Sci-hub bloqué, comment contourner"
subtitle: "La science du partage."
title: 'Sci-hub bloqué, comment contourner'
subtitle: 'La science du partage.'
lang: fr
permalink: "sci-hub-blocage"
excerpt: "Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place."
tags: ["Internet", "Science"]
slug: 'sci-hub-blocage'
excerpt: 'Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place.'
tags: ['Internet', 'Science']
type: articles
createdAt: "2019-03-31T07:47:36.000Z"
updatedAt: "2022-12-27T12:08:00.000Z"
createdAt: '2019-03-31T07:47:36.000Z'
updatedAt: '2022-12-27T12:08:00.000Z'
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
L'adresse actuelle de sci-hub est&nbsp;: [sci-hub.se](https://sci-hub.se)
<a href='/articles/en-sci-hub-unblock/' lang='en'>This content exists in english.</a>
L'adresse actuelle de sci-hub est&nbsp;: <a href="https://www.sci-hub.st/" rel="noreferer noopener">sci-hub.st</a>
## Résumé de la situation
@ -49,10 +51,10 @@ Ouvrez&nbsp;:
De là, vous pouvez ajouter des serveurs DNS en cliquant sur l'icône +. Cliquez sur ok et appliquez les nouveaux paramètres. Vous devrez peut-être redémarrer votre ordinateur pour que les changements fonctionnent.
<AstroImage
src="https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg"
width="728"
height="1060"
alt="MacOS réglages réseau et DNS"
src='https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg'
width='728'
height='1060'
alt='MacOS réglages réseau et DNS'
/>
### Sur Windows (ici 10)
@ -68,31 +70,31 @@ Ouvrez&nbsp;:
1. Utiliser ladresse de serveur DNS suivante
<AstroImage
src="https://assets.nardu.in/sci-hub-settings.jpg"
width="728"
height="319"
alt="Réglages windows"
src='https://assets.nardu.in/sci-hub-settings.jpg'
width='728'
height='319'
alt='Réglages windows'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-network.jpg"
width="728"
height="803"
alt="Windows réglages réseaux"
src='https://assets.nardu.in/sci-hub-network.jpg'
width='728'
height='803'
alt='Windows réglages réseaux'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-adapter.jpg"
width="728"
height="327"
alt="Windows régalges connections réseaux"
src='https://assets.nardu.in/sci-hub-adapter.jpg'
width='728'
height='327'
alt='Windows régalges connections réseaux'
/>
<AstroImage
src="https://assets.nardu.in/sci-hub-adapter-settings.jpg"
width="728"
height="434"
alt="Windows options adaptateur réseau"
src='https://assets.nardu.in/sci-hub-adapter-settings.jpg'
width='728'
height='434'
alt='Windows options adaptateur réseau'
/>
## Quels serveurs DNS utiliser&nbsp;?

28
src/content/articles/fr/the-day-I-jamd.mdx Normal file → Executable file
View File

@ -2,15 +2,17 @@
title: The day I Jamd
subtitle: Des paris, des outils et du fun.
lang: fr
permalink: "the-day-I-jamd"
slug: 'the-day-I-jamd'
excerpt: Ooh, yeah! All right! Were jammin
tags: ["Dev", "Jamstack"]
tags: ['Dev', 'Jamstack']
type: articles
createdAt: "2020-10-08T07:47:36.000Z"
updatedAt: "2022-12-27T15:40:06.000Z"
createdAt: '2020-10-08T07:47:36.000Z'
updatedAt: '2022-12-27T15:40:06.000Z'
---
import AstroImage from "../../../components/AstroImage.astro";
<a href='/articles/en-the-day-I-jamd/' lang='en'>This content exists in english.</a>
import AstroImage from '../../../components/AstroImage.astro'
## La solution de non facilité
@ -31,10 +33,10 @@ Jen suis resté pantois&nbsp;! Quasiment sans optimisation du côté statique
### wordpress
<AstroImage
src="https://assets.nardu.in/wordpress_8ee6f54b98.jpeg"
width="728"
height="412"
alt="Score de performance de 53/100 sur Wordpress."
src='https://assets.nardu.in/wordpress_8ee6f54b98.jpeg'
width='728'
height='412'
alt='Score de performance de 53/100 sur Wordpress.'
/>
Malgré beaucoup defforts, je nai pas pu faire mieux. Je ne suis pas un expert en cache, je nutilise pas de CDN et je me suis appuyé sur des plugins pour réaliser beaucoup de choses (php nétant pas ma spécialité 😬).
@ -42,10 +44,10 @@ Malgré beaucoup defforts, je nai pas pu faire mieux. Je ne suis pas un ex
### 11ty + strapi
<AstroImage
src="https://assets.nardu.in/static_2c0d9f1eb8.jpeg"
width="728"
height="412"
alt="Score de performance de 97/100 en Jamstack."
src='https://assets.nardu.in/static_2c0d9f1eb8.jpeg'
width='728'
height='412'
alt='Score de performance de 97/100 en Jamstack.'
/>
Presque **zéro** configuration spéciale (jétais trop impatient de tester) et jai atteint un score impressionnant. Je sais que les scores lighthouse ne font pas tout mais **53 contre 97**… Ça me va&nbsp;!

8
src/content/articles/fr/video-compression.md Normal file → Executable file
View File

@ -3,12 +3,12 @@ title: Compression vidéo
subtitle: En cours de traduction, revenez bientôt ;)
lang: fr
draft: true
permalink: "video-compression"
slug: 'video-compression'
excerpt: Pas encore traduit
tags: ["Design"]
tags: ['Design']
type: articles
createdAt: "2021-05-05T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
createdAt: '2021-05-05T09:00:00.000Z'
updatedAt: '2022-06-08T14:24:06.000Z'
---
[Retour aux articles](/articles)

View File

@ -1,65 +0,0 @@
import { z, defineCollection } from "astro:content";
const articles = defineCollection({
schema: z.object({
title: z.string(),
subtitle: z.string(),
lang: z.enum(["fr", "en"]),
tags: z.array(z.string()), // An array of strings
type: z.string(),
permalink: z.string(),
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false,
}),
});
const fragments = defineCollection({
schema: z.object({
title: z.string(),
subtitle: z.string(),
lang: z.enum(["fr", "en"]),
tags: z.array(z.string()), // An array of strings
type: z.string(),
permalink: z.string(),
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false,
}),
});
const references = defineCollection({
schema: z.object({
title: z.string(),
subtitle: z.string(),
url: z.string(),
lang: z.enum(["fr", "en"]),
permalink: z.string(),
tags: z.array(z.string()), // An array of strings
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
draft: z.boolean().optional() || false,
}),
});
export const collections = {
// Don't forget 'quotes' for collection names containing dashes
articles,
fragments,
references,
};

12
src/content/fragments/en/acme-sh-tls-cert.md Normal file → Executable file
View File

@ -2,10 +2,10 @@
title: Strong TLS certificates with acme.sh
subtitle: 384-bit of https
lang: en
permalink: "acme-sh-tls-cert"
createdAt: "2022-06-08T14:24:06.000Z"
slug: 'en-acme-sh-tls-cert'
createdAt: '2022-06-08T14:24:06.000Z'
excerpt: Real cert have curves.
tags: ["security"]
tags: ['security']
type: snippets
---
@ -89,7 +89,7 @@ Limit TLS version to 1.2 and 1.3 (or just 1.3 as there is only a [5% compatibili
Use the [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/) header.
```
```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
@ -101,12 +101,12 @@ I've based my initial choices of ciphers on [this list](https://tls.imirhil.fr/c
I then asked [Aeris](https://twitter.com/aeris22), the creator of [tls.imirhil.fr](https://tls.imirhil.fr), about it and he advised me to use the following:
```
```cypher
ECDHE+AES:ECDHE+CHACHA20
```
In order to achieve a perfect score, we can be a little more restrictive with:
```
```cypher
ECDHE+AES256:ECDHE+CHACHA20
```

34
src/content/fragments/en/array-vs-array.md Normal file → Executable file
View File

@ -2,10 +2,10 @@
title: Filter an array against another array
subtitle: Array vs Array.
lang: en
permalink: "array-vs-array"
createdAt: "2022-06-08T14:24:06.000Z"
slug: 'en-array-vs-array'
createdAt: '2022-06-08T14:24:06.000Z'
excerpt: My peak javascript
tags: ["nuxt.js"]
tags: ['nuxt.js']
type: snippets
---
@ -20,24 +20,24 @@ Here is an example:
```javascript
const skills = [
{
name: "Be awesome",
user: "Jean",
name: 'Be awesome',
user: 'Jean'
},
{
name: "Great jokes",
user: "Jacques",
name: 'Great jokes',
user: 'Jacques'
},
{
name: "Heavy sleeper",
user: "Jean",
name: 'Heavy sleeper',
user: 'Jean'
},
{
name: "Heavy sleeper",
user: "Beatriz",
},
];
name: 'Heavy sleeper',
user: 'Beatriz'
}
]
const selectedUsers = ["Jean", "Beatriz"];
const selectedUsers = ['Jean', 'Beatriz']
```
I thought it would be pretty easy but I guess I'm not familiar enough with javascript 😬
@ -54,9 +54,9 @@ After a bit more trials and errors, this is the code I ended up using in a `comp
// index.vue
const filteredSkills = selectedUsers.map((user) => {
return skills.filter((skill) => {
return skill.user === user;
});
});
return skill.user === user
})
})
```
I used `map()` in order to loop on the second array and used its string value to only include the corresponding skills with `filter()`.

12
src/content/fragments/en/buttons.md Normal file → Executable file
View File

@ -2,13 +2,13 @@
title: Buttons hover
subtitle: Simple, but nice.
lang: en
permalink: "buttons"
slug: 'en-buttons'
draft: true
excerpt: Easy to grab and use hover effects.
tags: ["CSS"]
tags: ['CSS']
code: true
type: snippets
createdAt: "2020-10-08T09:00:00.000Z"
createdAt: '2020-10-08T09:00:00.000Z'
---
## General rules
@ -51,7 +51,7 @@ All the buttons use these styles as a “reset”:
background-color: hotpink;
}
.btn-icon::before {
content: url("~assets/svg/arrow-right-white.svg");
content: url('~assets/svg/arrow-right-white.svg');
position: absolute;
width: 20px;
top: 50%;
@ -103,7 +103,7 @@ All the buttons use these styles as a “reset”:
}
.btn-rideau::before,
.btn-rideau::after {
content: "";
content: '';
position: absolute;
height: 100%;
width: 100%;
@ -174,7 +174,7 @@ All the buttons use these styles as a “reset”:
background-color: transparent;
}
.btn-scale::after {
content: "";
content: '';
position: absolute;
top: 0;
left: 0;

8
src/content/fragments/en/image-full.mdx Normal file → Executable file
View File

@ -3,10 +3,10 @@ title: Full width image
subtitle: Translation in progress, stay tuned ;)
lang: en
draft: true
permalink: "image-full"
createdAt: "2020-09-15T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
tags: ["CSS"]
slug: 'en-image-full'
createdAt: '2020-09-15T09:00:00.000Z'
updatedAt: '2022-06-08T14:24:06.000Z'
tags: ['CSS']
type: snippets
---

74
src/content/fragments/en/nuxt-graphql-static.md Normal file → Executable file
View File

@ -2,18 +2,18 @@
title: Static website and GraphQL queries with Nuxt.js
subtitle: Graphql client is king.
lang: en
permalink: "nuxt-graphql-static"
createdAt: "2022-06-08T14:24:06.000Z"
updatedAt: "2022-09-08T13:43:33.000Z"
slug: 'en-nuxt-graphql-static'
createdAt: '2022-06-08T14:24:06.000Z'
updatedAt: '2022-09-08T13:43:33.000Z'
excerpt: When the most used gql module doesn't work…
tags: ["nuxt.js"]
tags: ['nuxt.js']
type: snippets
---
## The problem
I encountered a nasty bug while using static generation with Nuxt and [nuxt apollo](https://github.com/nuxt-community/apollo-module "Dépôt github du module nuxt apollo (new tab)") client.
I found [the issue](https://github.com/nuxt-community/apollo-module/issues/339 "Github issue on nuxt/apollo repository (new tab)") already reported on github.
I encountered a nasty bug while using static generation with Nuxt and [nuxt apollo](https://github.com/nuxt-community/apollo-module 'Dépôt github du module nuxt apollo (new tab)') client.
I found [the issue](https://github.com/nuxt-community/apollo-module/issues/339 'Github issue on nuxt/apollo repository (new tab)') already reported on github.
It seems the module doesn't handle static generation correctly with `nuxt generate`.
@ -70,49 +70,47 @@ query {
#### Inside a page
```javascript
```vue
// index.vue
<script>
import homepageQuery from '~/graphql/queries/singles/homepage'
export default {
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
},
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
}
}
</script>
```
#### Inside a component
It is safer to wait until `fetch` has received a response before displaying anything. You can use `$fetchState` to be sure ([documentation](https://nuxtjs.org/docs/2.x/components-glossary/pages-fetch "Documentation on the fetch hook (new tab)")).
It is safer to wait until `fetch` has received a response before displaying anything. You can use `$fetchState` to be sure ([documentation](https://nuxtjs.org/docs/2.x/components-glossary/pages-fetch 'Documentation on the fetch hook (new tab)')).
```javascript
```vue
// Header.vue
<template>
<header v-if="!$fetchState.pending">
</header>
<header v-if="!$fetchState.pending"></header>
</template>
<script>
import headerQuery from '~/graphql/queries/singles/header'
export default {
data() {
return {
data: {},
}
},
async fetch() {
try {
const data = await this.$graphql.default.request(headerQuery)
this.data = data
} catch (error) {
console.error(JSON.stringify(error, undefined, 2))
}
},
data() {
return {
data: {}
}
},
async fetch() {
try {
const data = await this.$graphql.default.request(headerQuery)
this.data = data
} catch (error) {
console.error(JSON.stringify(error, undefined, 2))
}
}
}
</script>
```
@ -121,20 +119,20 @@ export default {
To pass options to the request, for example for a multilingual version with [nuxt/i18n](https://i18n.nuxtjs.org/) and/or a url parameter in a dynamic page:
```javascript
```vue
// _slug.vue
<script>
import articleQuery from '~/graphql/queries/articles'
export default {
async asyncData({ $graphql, app, params }) {
const locale = app.i18n.localeProperties.iso
const data = await $graphql.default.request(articleQuery, {
code: locale,
permalink: params.slug,
})
return { data }
},
async asyncData({ $graphql, app, params }) {
const locale = app.i18n.localeProperties.iso
const data = await $graphql.default.request(articleQuery, {
code: locale,
slug: params.slug
})
return { data }
}
}
</script>
```

24
src/content/fragments/en/super-cookies.mdx Normal file → Executable file
View File

@ -2,14 +2,14 @@
title: The best cookies
subtitle: Consentless biscuits.
lang: en
permalink: "super-cookies"
createdAt: "2022-06-08T14:24:06.000Z"
slug: 'en-super-cookies'
createdAt: '2022-06-08T14:24:06.000Z'
excerpt: It's a real recipe, not a joke about annoying files.
tags: ["food"]
tags: ['food']
type: snippets
---
import AstroImage from "../../../components/AstroImage.astro";
import AstroImage from '../../../components/AstroImage.astro'
## Vegetalian version
@ -74,14 +74,14 @@ Chocolate can be replaced by anything like nuts, raisins, legos... (don't eat le
## Figures
<AstroImage
src="https://assets.nardu.in/cookies-fig-1.jpg"
width="753"
height="1248"
alt="All ingredients mixed together to form a brown paste."
src='https://assets.nardu.in/cookies-fig-1.jpg'
width='753'
height='1248'
alt='All ingredients mixed together to form a brown paste.'
/>
<AstroImage
src="https://assets.nardu.in/cookies-fig-2.jpg"
width="753"
height="1248"
alt="The cookies are round and soft after baking."
src='https://assets.nardu.in/cookies-fig-2.jpg'
width='753'
height='1248'
alt='The cookies are round and soft after baking.'
/>

6
src/content/fragments/en/toulouse-fun.md Normal file → Executable file
View File

@ -2,11 +2,11 @@
title: Toulouse yourself
subtitle: Only the bestest
lang: en
permalink: "toulouse-fun"
slug: 'en-toulouse-fun'
excerpt: Gonna have to trust me on this ¯\_(ツ)_/¯
tags: ["lifestyle"]
tags: ['lifestyle']
type: snippets
createdAt: "2022-06-22T15:34:45.000Z"
createdAt: '2022-06-22T15:34:45.000Z'
---
Here is my list of great places to go to when in Toulouse. There are of course a lot of other great places to go to, these are just my all time favourite.

6
src/content/fragments/en/visited-links.md Normal file → Executable file
View File

@ -3,11 +3,11 @@ title: Visited links and visuel aid
subtitle: Translation in progress, stay tuned ;)
lang: en
draft: true
permalink: "visited-links"
slug: 'en-visited-links'
excerpt: Petit hack CSS
tags: ["Front-end"]
tags: ['Front-end']
type: snippets
createdAt: "2023-06-06T18:34:00.000Z"
createdAt: '2023-06-06T18:34:00.000Z'
---
[Go back to available snippets](/en/snippets)

12
src/content/fragments/fr/acme-sh-tls-cert.md Normal file → Executable file
View File

@ -2,10 +2,10 @@
title: Certificates TLS robustes avec acme.sh
subtitle: 384-bit de https
lang: fr
permalink: "acme-sh-tls-cert"
createdAt: "2022-06-08T14:24:06.000Z"
slug: 'acme-sh-tls-cert'
createdAt: '2022-06-08T14:24:06.000Z'
excerpt: La sécurité avec des courbes.
tags: ["sécurité"]
tags: ['sécurité']
type: fragments
---
@ -89,7 +89,7 @@ Limiter la version TLS à 1.2 et 1.3 (voire uniquement 1.3 vu [la différence de
Utiliser le header [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/).
```
```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
@ -101,12 +101,12 @@ J'avais basé ma première suite sur [cette liste](https://tls.imirhil.fr/cipher
J'ai ensuite demandé à [Aeris](https://twitter.com/aeris22), le créateur de [tls.imirhil.fr](https://tls.imirhil.fr), ses conseils sur cette suite. Il m'a recommandé d'utiliser&nbsp;:
```
```cypher
ECDHE+AES:ECDHE+CHACHA20
```
Afin d'atteindre un score de 100/100, il est possible de restreindre un peu plus la suite comme ceci&nbsp;:
```
```cypher
ECDHE+AES256:ECDHE+CHACHA20
```

Some files were not shown because too many files have changed in this diff Show More