Compare commits

..

7 Commits

Author SHA1 Message Date
d54aabd621
refactor: rework API loader and caching
All checks were successful
deploy / deploy (push) Successful in 2m24s
This commit removes dependency on rxjs.

It also implements better composables to handle data fetching from
remote APIs and caching these values more transparently.

This commit also switches from yarn to npm

It also switches to the official Umami plugin
2024-06-20 09:27:59 +02:00
24d558e0f5
test
All checks were successful
deploy / deploy (push) Successful in 2m51s
2024-02-26 07:38:38 +01:00
bc36bdec90
feat(umami): switch to dedicated Vuepress plugin
All checks were successful
deploy / deploy (push) Successful in 3m24s
2024-02-26 06:51:54 +01:00
1b54860f93
docs(find-me): Update Discord handle
Update content/en/find-me.org
Update content/find-me.org
Update content/lfn/find-me.org
2024-02-26 06:51:27 +01:00
37f9b36b2f
feat,docs: add Umami to website, update privacy pages
All checks were successful
deploy / deploy (push) Successful in 2m1s
2024-01-27 18:25:44 +01:00
4b447369c2
chore: switch from Drone to Gitea Actions
All checks were successful
deploy / deploy (push) Successful in 2m8s
2024-01-27 16:55:30 +01:00
cf1147204c
chore: update Vuepress, add search bar 2024-01-27 16:15:57 +01:00
29 changed files with 5687 additions and 3185 deletions

View File

@ -1,178 +0,0 @@
---
kind: pipeline
type: docker
name: CD
steps:
- name: restore cache node
image: drillster/drone-volume-cache
volumes:
- name: main-website-node
path: /cache/phundrak.com/node
settings:
restore: true
mount:
- ./node_modules
- name: restore cache emacs
image: drillster/drone-volume-cache
volumes:
- name: main-website-emacs
path: /cache/phundrak.com/emacs
settings:
restore: true
mount:
- /var/emacs
- name: generate emacs
image: silex/emacs:master-alpine
commands:
- mkdir -p /var/emacs
- apk update && apk add git
- emacs --init-directory=/var/emacs --script export.el
depends_on:
- "restore cache emacs"
- name: generate node
image: node:19-alpine
commands:
- yarn install
- yarn build
depends_on:
- "restore cache node"
- "generate emacs"
- name: rebuild cache emacs
image: drillster/drone-volume-cache
volumes:
- name: conlang-emacs
path: /cache/conlang/emacs
settings:
rebuild: true
mount:
- /var/emacs
depends_on:
- "generate emacs"
- name: rebuild cache node
image: drillster/drone-volume-cache
volumes:
- name: main-website-node
path: /cache/phundrak.com/node
settings:
rebuild: true
mount:
- ./node_modules
depends_on:
- "generate node"
- name: deploy web stable
image: appleboy/drone-scp
settings:
host:
from_secret: ssh_host
target:
from_secret: ssh_target
source: content/.vuepress/dist/*
strip_components: 3
username:
from_secret: ssh_username
password:
from_secret: ssh_password
port:
from_secret: ssh_port
depends_on:
- "generate node"
when:
branch:
- main
event:
exclude:
- pull_request
- name: deploy gemini
image: appleboy/drone-scp
settings:
host:
from_secret: ssh_host
target:
from_secret: ssh_target_gemini
source: gemini/*
strip_components: 1
username:
from_secret: ssh_username
password:
from_secret: ssh_password
port:
from_secret: ssh_port
depends_on:
- "generate emacs"
when:
branch:
- main
event:
exclude:
- pull_request
- name: purge cache stable
image: jetrails/drone-cloudflare-caching
settings:
api_token:
from_secret: cloudflare_cache_api
zone_identifier:
from_secret: phundrak_com_zone_id
action: purge_files
list:
- https://beta.phundrak.com
depends_on:
- "deploy web stable"
- "deploy gemini"
when:
branch:
- main
event:
exclude:
- pull_request
- name: deploy web devel
image: appleboy/drone-scp
settings:
host:
from_secret: ssh_host
target:
from_secret: ssh_target_devel
source: content/.vuepress/dist/*
strip_components: 3
username:
from_secret: ssh_username
password:
from_secret: ssh_password
port:
from_secret: ssh_port
depends_on:
- "generate node"
when:
branch:
- devel
event:
exclude:
- pull_request
- name: purge cache devel
image: jetrails/drone-cloudflare-caching
settings:
api_token:
from_secret: cloudflare_cache_api
zone_identifier:
from_secret: phundrak_com_zone_id
action: purge_files
list:
- https://alpha.phundrak.com
depends_on:
- "deploy web devel"
when:
branch:
- devel
event:
exclude:
- pull_request

1
.envrc Normal file
View File

@ -0,0 +1 @@
use nix

View File

@ -0,0 +1,41 @@
name: deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- uses: purcell/setup-emacs@master
with:
version: 29.1
- name: "Export org to md"
run: emacs -Q --script export.el
- run: npm run build
- name: "Deploy on the Web"
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: content/.vuepress/dist/*
target: ${{ secrets.DESTPATH }}
strip_components: 3
- name: "Deploy on Gemini"
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: gemini/*
target: ${{ secrets.DESTPATH_GMI }}
strip_components: 1

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ node_modules
.cache .cache
/content/.vuepress/dist/* /content/.vuepress/dist/*
*.md *.md
/.yarn/

3
.yarnrc.yml Normal file
View File

@ -0,0 +1,3 @@
enableMessageNames: false
nodeLinker: node-modules

View File

@ -4,9 +4,8 @@ import ListRepositories from './components/GitHub/ListRepositories.vue';
import FetchRepositories from './components/GitHub/FetchRepositories.vue'; import FetchRepositories from './components/GitHub/FetchRepositories.vue';
import GithubRepository from './components/GitHub/GithubRepository.vue'; import GithubRepository from './components/GitHub/GithubRepository.vue';
import ApiLoader from './components/ApiLoader.vue'; import ApiLoader from './components/ApiLoader.vue';
import Loader from './components/Loader.vue'; import LoaderAnimation from './components/LoaderAnimation.vue';
import Cache from './components/Cache.vue'; import FetchError from './components/FetchError.vue';
import Error from './components/Error.vue';
import Icon from './components/Icon.vue'; import Icon from './components/Icon.vue';
export default defineClientConfig({ export default defineClientConfig({
@ -16,9 +15,8 @@ export default defineClientConfig({
app.component('FetchRepositories', FetchRepositories); app.component('FetchRepositories', FetchRepositories);
app.component('GithubRepository', GithubRepository); app.component('GithubRepository', GithubRepository);
app.component('ApiLoader', ApiLoader); app.component('ApiLoader', ApiLoader);
app.component('Loader', Loader); app.component('LoaderAnimation', LoaderAnimation);
app.component('Cache', Cache); app.component('FetchError', FetchError);
app.component('Error', Error);
app.component('Icon', Icon); app.component('Icon', Icon);
}, },
setup() {}, setup() {},

View File

@ -1,31 +1,22 @@
<template> <template>
<Cache
:name="props.cacheName"
:callback="fetchData"
:already-known-data="alreadyKnownData"
@cached="processCachedData"
/>
<slot v-if="loading" name="loader"> <slot v-if="loading" name="loader">
<Loader /> <LoaderAnimation />
</slot> </slot>
<slot v-else-if="error" name="error"> <slot v-else-if="error" name="error">
<Error :url="props.url" /> <FetchError :url="props.url" />
</slot> </slot>
<slot v-else> </slot> <slot v-else> </slot>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Cache from './Cache.vue'; import LoaderAnimation from './LoaderAnimation.vue';
import Loader from './Loader.vue'; import FetchError from './FetchError.vue';
import Error from './Error.vue';
import { Ref, ref } from 'vue'; import { useFetchAndCache } from '../composables/fetchAndCache';
import { Observable, catchError, switchMap, throwError } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
const props = defineProps({ const props = defineProps({
url: { url: {
default: false, default: '',
required: true, required: true,
type: String, type: String,
}, },
@ -35,31 +26,11 @@ const props = defineProps({
}, },
alreadyKnownData: Object, alreadyKnownData: Object,
}); });
const emits = defineEmits(['dataLoaded', 'dataError', 'loading']);
const error: Ref<Error> = ref(null); const emits = defineEmits(['loaded', 'error', 'loading']);
const loading: Ref<boolean> = ref(true);
const fetchData = (): Observable<any> => { const { loading, error } = useFetchAndCache(props.url, {
return fromFetch(props.url).pipe( emits: emits,
switchMap((response: Response) => response.json()), cacheName: props.cacheName,
catchError((errorResponse: Error) => {
error.value = errorResponse;
return Error;
})
);
};
const processCachedData = (data: Observable<any>) => {
data.subscribe({
next: (response: any) => {
loading.value = false;
emits('dataLoaded', response);
},
error: (responseError: Error) => {
loading.value = false;
error.value = responseError;
},
}); });
};
</script> </script>

View File

@ -1,67 +0,0 @@
<template>
<slot />
</template>
<script setup lang="ts">
import { Observable, of, tap } from 'rxjs';
const props = defineProps({
name: {
required: true,
type: String,
},
callback: {
required: true,
type: Function,
},
lifetime: {
default: 1000 * 60 * 60, // one hour
required: false,
type: Number,
},
alreadyKnownData: {
default: null,
type: Object,
},
});
const emits = defineEmits(['cached']);
const isDataOutdated = (name: string): boolean => {
const lastUpdated: number = +localStorage.getItem(name + '-timestamp');
const elapsedTime: number = Date.now() - lastUpdated;
return elapsedTime > props.lifetime;
};
const storeInCache = (
callback: Function,
data: any,
name: string,
): Observable<any> => {
let response: Observable<any> = data ? of(data) : callback();
return response.pipe(
tap((response) => {
localStorage.setItem(name, JSON.stringify(response));
localStorage.setItem(name + '-timestamp', `${Date.now()}`);
}),
);
};
if (isDataOutdated(props.name)) {
emits(
'cached',
storeInCache(props.callback, props.alreadyKnownData, props.name),
);
} else {
let data = localStorage.getItem(props.name);
try {
emits('cached', of(JSON.parse(data)));
} catch (err) {
console.error(`Could not parse data found in cache: ${err}`);
emits(
'cached',
storeInCache(props.callback, props.alreadyKnownData, props.name),
);
}
}
</script>

View File

@ -1,11 +1,11 @@
<template> <template>
<ApiLoader :url="fetchUrl" @dataLoaded="filterRepos" cache-name="repos" /> <ApiLoader :url="fetchUrl" @loaded="filterRepos" cache-name="repos" />
<slot /> <slot />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from 'vue'; import { PropType, ref } from 'vue';
import { GithubRepo } from '../../composables/github'; import { GithubRepo } from '../../types/github';
const props = defineProps({ const props = defineProps({
sortBy: { sortBy: {
default: 'none', default: 'none',
@ -23,15 +23,12 @@ const props = defineProps({
type: Number, type: Number,
}, },
}); });
const emits = defineEmits(['loaded']);
const emits = defineEmits(['dataLoaded']);
const fetchUrl = `https://api.github.com/users/${props.user}/repos?per_page=100`; const fetchUrl = `https://api.github.com/users/${props.user}/repos?per_page=100`;
const repos = ref<GithubRepo[]>([]);
const filterRepos = (response: GithubRepo[]) => { const filterRepos = (response: GithubRepo[]) => {
emits( repos.value = response
'dataLoaded',
response
.sort((a, b) => { .sort((a, b) => {
if (props.sortBy === 'stars') { if (props.sortBy === 'stars') {
return b.stargazers_count - a.stargazers_count; return b.stargazers_count - a.stargazers_count;
@ -43,7 +40,7 @@ const filterRepos = (response: GithubRepo[]) => {
} }
return b.forks_count - a.forks_count; return b.forks_count - a.forks_count;
}) })
.slice(0, +props.limit) .slice(0, +props.limit);
); emits('loaded', repos.value);
}; };
</script> </script>

View File

@ -6,23 +6,23 @@
:cache-name="repoName()" :cache-name="repoName()"
:url="fetchUrl" :url="fetchUrl"
:already-known-data="props.data" :already-known-data="props.data"
@data-loaded="(repo: GithubRepo) => (repository = repo)" @loaded="(repo: GithubRepo) => (repository = repo)"
> >
<h3>{{ repository.name }}</h3> <h3>{{ repository?.name }}</h3>
<div> <div>
<p> <p>
{{ repository.description }} {{ repository?.description }}
</p> </p>
</div> </div>
<div class="flex-row flex-start gap-1rem stats"> <div class="flex-row flex-start gap-1rem stats">
<div class="stars"> <div class="stars">
<Icon name="star" /> {{ repository.stargazers_count }} <Icon name="star" /> {{ repository?.stargazers_count }}
</div> </div>
<div class="forks"> <div class="forks">
<Icon name="fork" /> {{ repository.forks_count }} <Icon name="fork" /> {{ repository?.forks_count }}
</div> </div>
<div class="link"> <div class="link">
<a :href="repository.html_url"><i class="icon phunic-link" /></a> <a :href="repository?.html_url"><i class="icon phunic-link" /></a>
</div> </div>
</div> </div>
</ApiLoader> </ApiLoader>
@ -32,7 +32,7 @@
<script setup lang="ts"> <script setup lang="ts">
import ApiLoader from '../ApiLoader.vue'; import ApiLoader from '../ApiLoader.vue';
import { GithubRepo } from '../../composables/github'; import { GithubRepo } from '../../types/github';
import { PropType, Ref, ref } from 'vue'; import { PropType, Ref, ref } from 'vue';
const props = defineProps({ const props = defineProps({
@ -45,7 +45,7 @@ const repoName = (): string => {
}; };
const fetchUrl = `https://api.github.com/repos/${repoName()}`; const fetchUrl = `https://api.github.com/repos/${repoName()}`;
const repository: Ref<GithubRepo> = ref(null); const repository: Ref<GithubRepo | null> = ref(null);
</script> </script>
<style lang="less"> <style lang="less">
@ -78,5 +78,12 @@ const repository: Ref<GithubRepo> = ref(null);
gap: 0.3rem; gap: 0.3rem;
} }
} }
.link {
a {
display: flex;
align-items: center;
}
}
} }
</style> </style>

View File

@ -5,7 +5,7 @@
:sort-by="props.sortBy" :sort-by="props.sortBy"
:user="props.user" :user="props.user"
:limit="props.limit" :limit="props.limit"
@data-loaded="(response: GithubRepo[]) => (repos = response)" @loaded="(response: GithubRepo[]) => (repos = response)"
> >
<GithubRepository <GithubRepository
:data="repo" :data="repo"
@ -22,7 +22,7 @@ import FetchRepositories from './FetchRepositories.vue';
import GithubRepository from './GithubRepository.vue'; import GithubRepository from './GithubRepository.vue';
import { PropType, Ref, ref } from 'vue'; import { PropType, Ref, ref } from 'vue';
import { GithubRepo } from '../../composables/github'; import { GithubRepo } from '../../types/github';
const props = defineProps({ const props = defineProps({
sortBy: { sortBy: {

View File

@ -0,0 +1,62 @@
import { Ref, computed, ref, watchEffect } from 'vue';
interface CacheOptions {
lifetime?: number;
timestampSuffix?: string;
forceUpdate?: boolean;
}
/**
* Cache data in local storage.
*
* The cache is updated if:
* - cache data does not exist
* - cached data is outdated and `data` is not null
* - or `options.forceUpdate` is true, regardless of the value of `data`
*
* Otherwise, data is retrieved from cache.
*
* @param {string} name Name of the cached value in local storage
* @param {Ref<T>} data Data to cache
* @param {CacheOptions} options Tweaks to the behaviour of the function
*/
export const useCache = <T>(
name: string,
data: Ref<T>,
options: CacheOptions,
) => {
const error = ref<string>(null);
const timestampName = name + (options?.timestampSuffix || '-timestamp');
const lifetime = options?.lifetime || 1000 * 60 * 60; // one hour in milliseconds
const lastUpdated: number = +localStorage.getItem(timestampName);
const cacheAge: number = Date.now() - lastUpdated;
const isDataOutdated = computed(() => {
return cacheAge > lifetime;
});
const shouldUpdate = computed(
() => options?.forceUpdate || (isDataOutdated.value && data.value != null),
);
const setData = () => {
console.log('Setting data in cache with name', name);
localStorage.setItem(name, JSON.stringify(data.value));
localStorage.setItem(timestampName, `${Date.now()}`);
};
const getData = () => {
console.log('Getting data from cache with name', name);
const cached = localStorage.getItem(name);
console.log('Value from storage:', cached);
try {
data.value = JSON.parse(cached);
} catch (err) {
console.error('Failed to parse cached data:', err);
data.value = null;
error.value = err;
}
};
getData();
watchEffect(() => (shouldUpdate.value ? setData() : getData()));
return { error, isDataOutdated };
};

View File

@ -0,0 +1,72 @@
import { ref, Ref } from 'vue';
import { useCache } from './cache';
type FetchAndCacheEmitter = (
event: 'loaded' | 'error' | 'loading',
...args: any[]
) => void;
interface UseFetchAndCacheOptions {
cacheLifetime?: number;
cacheName?: string;
emits?: FetchAndCacheEmitter;
}
const dummyEmits = (
_event: 'loaded' | 'error' | 'loading',
..._args: any[]
) => {};
export const useFetchAndCache = <T, E>(
url: string,
options?: UseFetchAndCacheOptions,
) => {
const data = ref<T | null>(null) as Ref<T>;
const error = ref<E | null>(null) as Ref<E>;
const loading = ref<boolean>(true);
const cacheLifetime: number = options?.cacheLifetime || 1000 * 60 * 60; // one hour
const cacheName: string = options?.cacheName || url;
const { isDataOutdated: isCacheOutdated, error: cacheError } = useCache(
cacheName,
data,
{
lifetime: cacheLifetime,
},
);
const emits: FetchAndCacheEmitter = options?.emits || dummyEmits;
const loaded = () => {
loading.value = false;
emits('loaded', data.value);
};
const fetchData = () => {
loading.value = true;
emits('loading');
console.log('Fetching from URL', url);
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<T>;
})
.then((responseData) => {
data.value = responseData;
loaded();
})
.catch((e) => {
console.warn('Caught error!', e);
error.value = e;
emits('error', e);
})
.finally(() => (loading.value = false));
};
if (isCacheOutdated.value || cacheError.value != null) {
fetchData();
} else {
loaded();
}
return { data, loading, error };
};

View File

@ -1,24 +1,46 @@
import { defineUserConfig, defaultTheme } from 'vuepress'; import { defaultTheme } from '@vuepress/theme-default';
import { removeHtmlExtensionPlugin } from 'vuepress-plugin-remove-html-extension'; import { viteBundler } from '@vuepress/bundler-vite';
import head from './head'; import { defineUserConfig } from 'vuepress';
import locales from './locales'; import { searchProPlugin } from 'vuepress-plugin-search-pro';
import { umamiAnalyticsPlugin } from '@vuepress/plugin-umami-analytics';
import { head } from './head';
import { locales, searchLocales } from './locales';
import { themeLocales } from './themeLocales'; import { themeLocales } from './themeLocales';
const isProd = process.env.NODE_ENV === 'production';
export default defineUserConfig({ export default defineUserConfig({
lang: 'fr-FR', lang: 'fr-FR',
title: 'Lucien Cartier-Tilet', title: 'Lucien Cartier-Tilet',
description: 'Site web personnel de Lucien Cartier-Tilet', description: 'Site web personnel de Lucien Cartier-Tilet',
head: head, head: head,
bundler: viteBundler({}),
markdown: { markdown: {
html: true, html: true,
linkify: true, linkify: true,
typographer: true, typographer: true,
}, },
plugins: [removeHtmlExtensionPlugin()], plugins: [
searchProPlugin({
indexContent: true,
locales: searchLocales,
}),
isProd
? umamiAnalyticsPlugin({
id: '67166941-8c83-4a19-bc8c-139e44b7f7aa',
link: 'https://umami.phundrak.com/script.js',
})
: [],
],
locales: locales, locales: locales,
theme: defaultTheme({ theme: defaultTheme({
contributors: false, contributors: false,
locales: themeLocales, locales: themeLocales,
repo: 'https://labs.phundrak.com/phundrak/phundrak.com', repo: 'https://labs.phundrak.com/phundrak/phundrak.com',
themePlugins: {
copyCode: false,
prismjs: false,
},
}), }),
}); });

View File

@ -1,9 +1,11 @@
import { HeadAttrsConfig } from 'vuepress';
interface SimplifiedHeader { interface SimplifiedHeader {
tag: string; tag: string;
content: [any]; content: HeadAttrsConfig[];
} }
const simplifiedHead = [ const simplifiedHead: SimplifiedHeader[] = [
{ {
tag: 'meta', tag: 'meta',
content: [ content: [
@ -35,6 +37,10 @@ const simplifiedHead = [
name: 'twitter:creator', name: 'twitter:creator',
content: '@phundrak', content: '@phundrak',
}, },
{
name: 'build-status',
content: `value: ${process.env.NODE_ENV}`,
},
{ name: 'msapplication-TileColor', content: '#3b4252' }, { name: 'msapplication-TileColor', content: '#3b4252' },
{ name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' }, { name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' },
{ name: 'theme-color', content: '#3b4252' }, { name: 'theme-color', content: '#3b4252' },
@ -117,12 +123,16 @@ const simplifiedHead = [
}, },
]; ];
let head = []; const headBuilder = [];
simplifiedHead.forEach((tag) => { simplifiedHead.forEach((tag) => {
tag.content.forEach((element: any) => { tag.content.forEach((element) => {
head.push([tag.tag, element]); headBuilder.push([tag.tag, element]);
}); });
}); });
head.push(['a', { rel: 'me', href: 'https://emacs.ch/@phundrak' }, 'Mastodon']); headBuilder.push([
'a',
{ rel: 'me', href: 'https://emacs.ch/@phundrak' },
'Mastodon',
]);
export default head; export const head = headBuilder;

View File

@ -1,4 +1,6 @@
const locales = { import { SearchProLocaleConfig } from 'vuepress-plugin-search-pro';
export const locales = {
'/': { '/': {
lang: 'fr-FR', lang: 'fr-FR',
title: 'Lucien Cartier-Tilet', title: 'Lucien Cartier-Tilet',
@ -16,4 +18,37 @@ const locales = {
}, },
}; };
export default locales; export const searchLocales: SearchProLocaleConfig = {
'/fr/': {
cancel: 'Annuler',
placeholder: 'Rechercher',
search: 'Rechercher',
searching: 'Recherche',
defaultTitle: 'Documentation',
select: 'sélectionner',
navigate: 'naviguer',
autocomplete: 'auto-complétion',
exit: 'fermer',
queryHistory: 'Historique de recherche',
resultHistory: 'Historique des résultats',
emptyHistory: "Vider l'historique de recherche",
emptyResult: 'Aucun résultat trouvé',
loading: 'Chargement des index de recherche...',
},
'/lfn/': {
cancel: 'Cansela',
placeholder: 'Xerca',
search: 'Xerca',
searching: 'Xercante',
defaultTitle: 'Documentos',
select: 'eleje',
navigate: 'naviga',
autocomplete: 'auto-completi',
exit: 'sorti',
queryHistory: 'Historia de xerca',
resultHistory: 'Historia de resultas',
emptyHistory: 'Historia vacua',
emptyResult: 'Resultas vacua',
loading: 'Cargante la indise de xerca...',
},
};

View File

@ -16,7 +16,7 @@ me.
- [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] : blog alternative - [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] : blog alternative
- [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: short stories, - [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: short stories,
mainly in French for now mainly in French for now
- {{{icon(discord)}}} Discord :: =Phundrak#0001= (tell me you come from here, - {{{icon(discord)}}} Discord :: =@phundrak= (tell me you come from here,
otherwise theres a chance Ill consider your message as spam) otherwise theres a chance Ill consider your message as spam)
** Other Websites ** Other Websites

View File

@ -18,8 +18,8 @@ websites to function properly or function properly or more
efficiently. efficiently.
This website uses some functional cookies in order to remember your This website uses some functional cookies in order to remember your
preferences, such as your preferred language or its color theme. These preferences, such as your preferred language or its colour theme.
cookies are not and cannot be used to track you. These cookies are not and cannot be used to track you.
However, as this site is protected by Cloudflare, they may also host However, as this site is protected by Cloudflare, they may also host
some cookies to remember, for example, that your browser is safe or to some cookies to remember, for example, that your browser is safe or to
@ -43,7 +43,19 @@ internet, or even via emails or any web content rendered on the
screen, such as web beacons (minuscule, invisible images). It is also screen, such as web beacons (minuscule, invisible images). It is also
possible to store Flash cookies or local shared objects. possible to store Flash cookies or local shared objects.
This site does not use them at all. *** But is there any tracking at all on this website?
Well, there is, but it absolutely respects your privacy. I use my own
instance of [[https://umami.is][Umami]] which is an analytics service that is fully GDPR and
CCPA compliant. In short, when you visit a web page, some data get
sent to my service, but nothing that can identify you. If you come
back an hour later, I wont have any indication that you are the same
person.
If you still worry about your privacy, you have two options:
- Activate the Do Not Track setting of your browser (which Umami will
honour)
- Block the domain =umami.phundrak.com= in uBlock Origin (the only
ad blocker I will ever trust)
** Is there targeted advertisement on this website? ** Is there targeted advertisement on this website?
Theres no advertisement to begin with, and never will be. If you see Theres no advertisement to begin with, and never will be. If you see
@ -55,16 +67,10 @@ is a cry for help.
** How often is this page updated? ** How often is this page updated?
It is updated from time to time to reflect any changes in how my It is updated from time to time to reflect any changes in how my
website behaves, or if I notice errors on this page (such as typos). I website behaves, or if I notice errors on this page (such as typos).
might add some user tracking, however dont worry, Matomo (the service
I would use) would only track you on this website and this website
only. Matomo respects the privacy of a websites users.
The date of the last update of this web page can be found at its very
beginning.
** I have other questions ** I have other questions
And I have the answers! Ill be more thang happy to chat with you by And I have the answers! Ill be more than happy to chat with you by
email, feel free to send me one at [[mailto:lucien@phundrak.com][lucien@phundrak.com]]. email, feel free to send me one at [[mailto:lucien@phundrak.com][lucien@phundrak.com]].
#+include: other-links #+include: other-links

View File

@ -15,7 +15,7 @@ sociaux où vous pouvez me suivre.
- {{{icon(writefreely)}}} *Writefreely* : - {{{icon(writefreely)}}} *Writefreely* :
- [[https://write.phundrak.com/phundrak][*@phundrak@write.phundrak.com*]] : billets personnels - [[https://write.phundrak.com/phundrak][*@phundrak@write.phundrak.com*]] : billets personnels
- [[https://write.phundrak.com/phundraks-short-stories][*@phundraks-short-stories@write.phundrak.com*]] : histoires courtes - [[https://write.phundrak.com/phundraks-short-stories][*@phundraks-short-stories@write.phundrak.com*]] : histoires courtes
- {{{icon(discord)}}} *Discord* : =Phundrak#0001= (dites-moi que vous venez - {{{icon(discord)}}} *Discord* : =@phundrak= (dites-moi que vous venez
dici, autrement il est possible que je considère le message comme dici, autrement il est possible que je considère le message comme
du pourriel) du pourriel)

View File

@ -13,7 +13,7 @@ On pote trova me sur multe loca ueb e redes sosial do on pote segue me.
- [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] :: revistas personal - [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] :: revistas personal
- [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: istorias corta (a - [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: istorias corta (a
multe veses en Frans) multe veses en Frans)
- {{{icon(discord)}}} Discord :: =Phundrak#0001= (dise me ce tu veni de asi, - {{{icon(discord)}}} Discord :: =@phundrak= (dise me ce tu veni de asi,
si no, me pote pensa ce tua mesaje es spam) si no, me pote pensa ce tua mesaje es spam)
** Other Websites ** Other Websites

View File

@ -16,19 +16,15 @@ es an con tota estrema comun. Los capasi esa locas ueb a funsiona
coreta o plu eficas. coreta o plu eficas.
Esa loca ueb usa cucis con la ojeto de recorda tua prefere, como ance Esa loca ueb usa cucis con la ojeto de recorda tua prefere, como ance
la lingua o la motif ce te ia eleje. Lo ance usa cucis de mi personal la lingua o la motif ce te ia eleje.
Matomo par sabe lo ce lo usores de esa loca ueb fa asi, ma Matomo
trasa lo sola sur mea locas ueb.
An tal, esa loca ueb es protejeda par Cloudflare, esa compania ance An tal, esa loca ueb es protejeda par Cloudflare, esa compania ance
pote ospita alga cucis afin lo recorda si tu surfador es un risca par pote ospita alga cucis afin lo recorda si tu surfador es un risca par
me loca ueb o no. me loca ueb o no.
*** Como me pote controla la cucis sur mea computa? *** Como me pote controla la cucis sur mea computa?
Si te no vole ce Cloudflare o Matomo recorda cucis, un bon Si te no vole ce Cloudflare recorda cucis, un bon anti-comersial como
anti-comersial como [[https://ublockorigin.com/][uBlock Origin]] ta pote [[https://ublockorigin.com/][uBlock Origin]] ta pote te proteje (es la plu eficas ce me conose).
te proteje (es la plu eficas ce me conose). Matomo ance respecta la
demanda “no trasa me” de surfadores.
Te pote ance supresa con mano la cucis de tua surfador, ma me no pote Te pote ance supresa con mano la cucis de tua surfador, ma me no pote
te dise como, lo ave tro ce esiste. Ma te pote xerca sur DuckDuckGo, te dise como, lo ave tro ce esiste. Ma te pote xerca sur DuckDuckGo,
@ -40,21 +36,32 @@ Lo esista otra metodos plu sutil afin de trasa usores sur la interede,
o an con epostas o cada contenida ueb, como pixeles spia (imajes o an con epostas o cada contenida ueb, como pixeles spia (imajes
estrema peti), cucis Flash o ojetos local compartida. estrema peti), cucis Flash o ojetos local compartida.
Ma esa loca ueb no usa lo. *** Ma, lo ave metodos de trasa asi?
Si, ma la lo conserva tua vida privata. Me usa mea propre varia de
[[https://umami.is][Umami]] ce es un servi de analise de datos ce respeta completa la GDPR e
la CCPA. En noncomplicada parolas, cuando te come a un paje, la
informa es enviada ce alga ia visita lo, con alga otra informas. Ma on
no pote sabe ci ia visita la loca ueb, e si te va visita lo a otra
ves, on no pote sabe ce te ia veni ja.
*** Esa loca ueb usa comersiales intendeda? Si an tal te ansiosa consernante tua vida privata, tu avec du
posibles:
- Ativi la parametre Do Not Track de tua surfador (Umami respeta lo)
- Bloci la domina ueb =umami.phundrak.com= en uBlock Origin (la sola
blocinte de comersiales ce me fida)
** Esa loca ueb usa comersiales intendeda?
Lo no ave no comersiales. Si te lo vide asi, te ave un virus sur tua Lo no ave no comersiales. Si te lo vide asi, te ave un virus sur tua
computa o surfador. Si esa comersiales vera ta veni de mea loca ueb, computa o surfador. Si esa comersiales vera ta veni de mea loca ueb,
lo es cracida. Si te pote vide en la arciveria git ce lo es me ce ia lo es cracida. Si te pote vide en la arciveria git ce lo es me ce ia
ajunta esa comersiales, o me ia dementi, o me es secuestrada e lo es ajunta esa comersiales, o me ia dementi, o me es secuestrada e lo es
un sinial de crise. un sinial de crise.
*** Ave esa pajina frecuente refrescis? ** Ave esa pajina frecuente refrescis?
Me dona esa pajina un refresci aora e alora cuando lo debe a mostra Me dona esa pajina un refresci aora e alora cuando lo debe a mostra
cambias de funsiona de mea loca ueb, o si me trova eras. Te pote trove cambias de funsiona de mea loca ueb, o si me trova eras.
la ultima refresci de esa pajina a supra.
*** Me ave otra demandas ** Me ave otra demandas
Te pote scrive me un eposta a la adirije de eposta Te pote scrive me un eposta a la adirije de eposta
[[mailto:lucien@phundrak.com][lucien@phundrak.com]]. [[mailto:lucien@phundrak.com][lucien@phundrak.com]].

View File

@ -5,7 +5,7 @@
** Où est hébergé le site? ** Où est hébergé le site?
Ce site est hébergé sur mon serveur personnel, situé dans la ville de Ce site est hébergé sur mon serveur personnel, situé dans la ville de
Bron en France, comme la majorité de mes sites. Deux autres sites, Bron en France, comme la majorité de mes sites. Deux autres sites,
=labs.phundrak.com= et =mail.phundrak.com=, sont hébergé sur dautres =labs.phundrak.com= et =mail.phundrak.com=, sont hébergés sur dautres
serveurs loués à Scaleway et à OVH France respectivement, et les serveurs loués à Scaleway et à OVH France respectivement, et les
serveurs se situent également en France. serveurs se situent également en France.
@ -23,7 +23,7 @@ Ces cookies ne sont pas et ne peuvent pas être utilisés pour vous
traquer. traquer.
Cependant, ce site étant protégé par Cloudflare, ce dernier pourra Cependant, ce site étant protégé par Cloudflare, ce dernier pourra
également héberger quelques cookies afin par exemple de se souvenir pareillement héberger quelques cookies afin par exemple de se souvenir
que votre navigateur ne présente pas de risque ou bien pour que votre navigateur ne présente pas de risque ou bien pour
enregistrer le trafic sur le site. enregistrer le trafic sur le site.
@ -33,7 +33,7 @@ activités, un bon anti-pub devrait faire laffaire. Je recommande
personnellement [[https://ublockorigin.com/][uBlock Origin]], lun des bloqueurs de pub les plus personnellement [[https://ublockorigin.com/][uBlock Origin]], lun des bloqueurs de pub les plus
efficaces que je connaisse. efficaces que je connaisse.
Vous pouvez également supprimer manuellement les cookies de votre Vous pouvez par ailleurs supprimer manuellement les cookies de votre
navigateur, mais étant donné le nombre de navigateurs existants, il navigateur, mais étant donné le nombre de navigateurs existants, il
sera sans doute plus rapide pour vous de chercher sur DuckDuckGo, sera sans doute plus rapide pour vous de chercher sur DuckDuckGo,
Qwant ou Startpage comment faire pour votre navigateur actuel (si vous Qwant ou Startpage comment faire pour votre navigateur actuel (si vous
@ -44,15 +44,29 @@ voudrez éviter Google).
Il existe dautres méthodes plus subtiles qui permettent de traquer Il existe dautres méthodes plus subtiles qui permettent de traquer
quelquun sur internet, ou même via des mails ou tout contenu web quelquun sur internet, ou même via des mails ou tout contenu web
rendu à lécran, comme pixels espions (des images extrêmement rendu à lécran, comme pixels espions (des images extrêmement
petites). Il est également possible de stocker des cookies Flash ou petites). Il est de plus possible de stocker des cookies Flash ou des
des objets locaux partagés. objets locaux partagés.
Ce site nen utilise absolument pas. *** Mais, est-ce quil y a du tracking sur ce site?
Oui, mais en préservant intégralement votre vie privée. Jutilise ma
propre instance de [[https://umami.is][Umami]] qui est un service danalyse de données qui
respecte entièrement le RGPD et le [[https://www.oag.ca.gov/privacy/ccpa][CCPA]]. Pour résumer, lorsque vous
passez sur une nouvelle page du site, linformation est envoyée quune
visite sest produite, avec quelques informations supplémentaires,
mais rien qui permet didentifier qui que ce soit. Si vous revenez
dans une heure, je ne saurai pas que vous êtes la même personne.
Si vous vous inquiétez tout de même de votre vie privée, deux options
soffrent à vous:
- Activez loption Do Not Track de votre navigateur (Umami respecte ce
paramètre)
- Bloquez le domaine =umami.phundrak.com= dans uBlock Origin (le seul
bloqueur de pubs auquel je fais confiance)
** Est-ce quil y a de la pub ciblée sur ce site? ** Est-ce quil y a de la pub ciblée sur ce site?
Il ny a tout simplement aucune pub sur ce site. Si vous en voyez, Il ny a tout simplement aucune pub sur ce site. Si vous en voyez,
vous avez sans doute un virus installé sur votre ordinateur. Si ça vous avez sans doute un virus installé sur votre ordinateur. Si ça
vient en effet de mon site web, cela veut dire quil a été hacké. Si vient en effet de mon site web, cela veut dire quil a été piraté. Si
vous voyez sur son dépôt de code que cest bien moi qui ai rajouté ces vous voyez sur son dépôt de code que cest bien moi qui ai rajouté ces
pubs, cela veut dire que jai perdu tout mon sens moral, ou bien que pubs, cela veut dire que jai perdu tout mon sens moral, ou bien que
jai été kidnappé et il sagit dun appel au secours. jai été kidnappé et il sagit dun appel au secours.
@ -60,11 +74,7 @@ jai été kidnappé et il sagit dun appel au secours.
** Est-ce que cette page est souvent mise à jour? ** Est-ce que cette page est souvent mise à jour?
Je peux la mettre à jour de temps en temps afin de refléter des Je peux la mettre à jour de temps en temps afin de refléter des
changements de fonctionnement du site, ou si je remarque une erreur changements de fonctionnement du site, ou si je remarque une erreur
sur la page. Il se peut aussi que jajoute un jour un tracking des sur la page.
utilisateurs sur mon site via Matomo, un service de tracking
respectant la vie privée des utilisateurs et qui est tout à fait
bloquable. La date de la dernière mise à jour de cette page peut être
trouvée à son tout début.
** Jai dautres questions ** Jai dautres questions
Et je serai heureux dy répondre par mail. Vous pouvez me contacter Et je serai heureux dy répondre par mail. Vous pouvez me contacter

5295
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,23 +8,26 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.13",
"@vuepress/plugin-umami-analytics": "^2.0.0-rc.36",
"@vuepress/theme-default": "^2.0.0-rc.36",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"git-cliff": "^1.1.2", "git-cliff": "^1.4.0",
"vuepress": "2.0.0-beta.61" "vuepress": "2.0.0-rc.13",
"vuepress-plugin-search-pro": "^2.0.0-rc.43"
}, },
"scripts": { "scripts": {
"dev": "vuepress dev content", "dev": "vuepress dev content",
"build": "vuepress build content" "build": "vuepress build content"
}, },
"dependencies": { "dependencies": {
"less": "^4.1.3", "less": "^4.2.0",
"nord": "^0.2.1", "nord": "^0.2.1"
"rxjs": "^7.8.1",
"vuepress-plugin-remove-html-extension": "^0.1.0"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
"path": "./node_modules/cz-conventional-changelog" "path": "./node_modules/cz-conventional-changelog"
} }
} },
"packageManager": "yarn@4.3.0"
} }

6
shell.nix Normal file
View File

@ -0,0 +1,6 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs; [
nodejs_22
];
}

2800
yarn.lock

File diff suppressed because it is too large Load Diff