Compare commits
7 Commits
1ff33bfd64
...
d54aabd621
Author | SHA1 | Date | |
---|---|---|---|
d54aabd621 | |||
24d558e0f5 | |||
bc36bdec90 | |||
1b54860f93 | |||
37f9b36b2f | |||
4b447369c2 | |||
cf1147204c |
178
.drone.yml
178
.drone.yml
@ -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
|
41
.gitea/workflows/deploy.yaml
Normal file
41
.gitea/workflows/deploy.yaml
Normal 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
1
.gitignore
vendored
@ -3,3 +3,4 @@ node_modules
|
||||
.cache
|
||||
/content/.vuepress/dist/*
|
||||
*.md
|
||||
/.yarn/
|
||||
|
3
.yarnrc.yml
Normal file
3
.yarnrc.yml
Normal file
@ -0,0 +1,3 @@
|
||||
enableMessageNames: false
|
||||
|
||||
nodeLinker: node-modules
|
@ -4,9 +4,8 @@ import ListRepositories from './components/GitHub/ListRepositories.vue';
|
||||
import FetchRepositories from './components/GitHub/FetchRepositories.vue';
|
||||
import GithubRepository from './components/GitHub/GithubRepository.vue';
|
||||
import ApiLoader from './components/ApiLoader.vue';
|
||||
import Loader from './components/Loader.vue';
|
||||
import Cache from './components/Cache.vue';
|
||||
import Error from './components/Error.vue';
|
||||
import LoaderAnimation from './components/LoaderAnimation.vue';
|
||||
import FetchError from './components/FetchError.vue';
|
||||
import Icon from './components/Icon.vue';
|
||||
|
||||
export default defineClientConfig({
|
||||
@ -16,9 +15,8 @@ export default defineClientConfig({
|
||||
app.component('FetchRepositories', FetchRepositories);
|
||||
app.component('GithubRepository', GithubRepository);
|
||||
app.component('ApiLoader', ApiLoader);
|
||||
app.component('Loader', Loader);
|
||||
app.component('Cache', Cache);
|
||||
app.component('Error', Error);
|
||||
app.component('LoaderAnimation', LoaderAnimation);
|
||||
app.component('FetchError', FetchError);
|
||||
app.component('Icon', Icon);
|
||||
},
|
||||
setup() {},
|
||||
|
@ -1,31 +1,22 @@
|
||||
<template>
|
||||
<Cache
|
||||
:name="props.cacheName"
|
||||
:callback="fetchData"
|
||||
:already-known-data="alreadyKnownData"
|
||||
@cached="processCachedData"
|
||||
/>
|
||||
<slot v-if="loading" name="loader">
|
||||
<Loader />
|
||||
<LoaderAnimation />
|
||||
</slot>
|
||||
<slot v-else-if="error" name="error">
|
||||
<Error :url="props.url" />
|
||||
<FetchError :url="props.url" />
|
||||
</slot>
|
||||
<slot v-else> </slot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Cache from './Cache.vue';
|
||||
import Loader from './Loader.vue';
|
||||
import Error from './Error.vue';
|
||||
import LoaderAnimation from './LoaderAnimation.vue';
|
||||
import FetchError from './FetchError.vue';
|
||||
|
||||
import { Ref, ref } from 'vue';
|
||||
import { Observable, catchError, switchMap, throwError } from 'rxjs';
|
||||
import { fromFetch } from 'rxjs/fetch';
|
||||
import { useFetchAndCache } from '../composables/fetchAndCache';
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
default: false,
|
||||
default: '',
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
@ -35,31 +26,11 @@ const props = defineProps({
|
||||
},
|
||||
alreadyKnownData: Object,
|
||||
});
|
||||
const emits = defineEmits(['dataLoaded', 'dataError', 'loading']);
|
||||
|
||||
const error: Ref<Error> = ref(null);
|
||||
const loading: Ref<boolean> = ref(true);
|
||||
const emits = defineEmits(['loaded', 'error', 'loading']);
|
||||
|
||||
const fetchData = (): Observable<any> => {
|
||||
return fromFetch(props.url).pipe(
|
||||
switchMap((response: Response) => response.json()),
|
||||
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;
|
||||
},
|
||||
});
|
||||
};
|
||||
const { loading, error } = useFetchAndCache(props.url, {
|
||||
emits: emits,
|
||||
cacheName: props.cacheName,
|
||||
});
|
||||
</script>
|
||||
|
@ -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>
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<ApiLoader :url="fetchUrl" @dataLoaded="filterRepos" cache-name="repos" />
|
||||
<ApiLoader :url="fetchUrl" @loaded="filterRepos" cache-name="repos" />
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { GithubRepo } from '../../composables/github';
|
||||
import { PropType, ref } from 'vue';
|
||||
import { GithubRepo } from '../../types/github';
|
||||
const props = defineProps({
|
||||
sortBy: {
|
||||
default: 'none',
|
||||
@ -23,27 +23,24 @@ const props = defineProps({
|
||||
type: Number,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['dataLoaded']);
|
||||
|
||||
const emits = defineEmits(['loaded']);
|
||||
const fetchUrl = `https://api.github.com/users/${props.user}/repos?per_page=100`;
|
||||
const repos = ref<GithubRepo[]>([]);
|
||||
|
||||
const filterRepos = (response: GithubRepo[]) => {
|
||||
emits(
|
||||
'dataLoaded',
|
||||
response
|
||||
.sort((a, b) => {
|
||||
if (props.sortBy === 'stars') {
|
||||
return b.stargazers_count - a.stargazers_count;
|
||||
}
|
||||
if (props.sortBy === 'pushed_at') {
|
||||
const dateA = new Date(a.pushed_at);
|
||||
const dateB = new Date(b.pushed_at);
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
}
|
||||
return b.forks_count - a.forks_count;
|
||||
})
|
||||
.slice(0, +props.limit)
|
||||
);
|
||||
repos.value = response
|
||||
.sort((a, b) => {
|
||||
if (props.sortBy === 'stars') {
|
||||
return b.stargazers_count - a.stargazers_count;
|
||||
}
|
||||
if (props.sortBy === 'pushed_at') {
|
||||
const dateA = new Date(a.pushed_at);
|
||||
const dateB = new Date(b.pushed_at);
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
}
|
||||
return b.forks_count - a.forks_count;
|
||||
})
|
||||
.slice(0, +props.limit);
|
||||
emits('loaded', repos.value);
|
||||
};
|
||||
</script>
|
||||
|
@ -6,23 +6,23 @@
|
||||
:cache-name="repoName()"
|
||||
:url="fetchUrl"
|
||||
: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>
|
||||
<p>
|
||||
{{ repository.description }}
|
||||
{{ repository?.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-row flex-start gap-1rem stats">
|
||||
<div class="stars">
|
||||
<Icon name="star" /> {{ repository.stargazers_count }}
|
||||
<Icon name="star" /> {{ repository?.stargazers_count }}
|
||||
</div>
|
||||
<div class="forks">
|
||||
<Icon name="fork" /> {{ repository.forks_count }}
|
||||
<Icon name="fork" /> {{ repository?.forks_count }}
|
||||
</div>
|
||||
<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>
|
||||
</ApiLoader>
|
||||
@ -32,7 +32,7 @@
|
||||
<script setup lang="ts">
|
||||
import ApiLoader from '../ApiLoader.vue';
|
||||
|
||||
import { GithubRepo } from '../../composables/github';
|
||||
import { GithubRepo } from '../../types/github';
|
||||
import { PropType, Ref, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
@ -45,7 +45,7 @@ const repoName = (): string => {
|
||||
};
|
||||
|
||||
const fetchUrl = `https://api.github.com/repos/${repoName()}`;
|
||||
const repository: Ref<GithubRepo> = ref(null);
|
||||
const repository: Ref<GithubRepo | null> = ref(null);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@ -78,5 +78,12 @@ const repository: Ref<GithubRepo> = ref(null);
|
||||
gap: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -5,7 +5,7 @@
|
||||
:sort-by="props.sortBy"
|
||||
:user="props.user"
|
||||
:limit="props.limit"
|
||||
@data-loaded="(response: GithubRepo[]) => (repos = response)"
|
||||
@loaded="(response: GithubRepo[]) => (repos = response)"
|
||||
>
|
||||
<GithubRepository
|
||||
:data="repo"
|
||||
@ -22,7 +22,7 @@ import FetchRepositories from './FetchRepositories.vue';
|
||||
import GithubRepository from './GithubRepository.vue';
|
||||
|
||||
import { PropType, Ref, ref } from 'vue';
|
||||
import { GithubRepo } from '../../composables/github';
|
||||
import { GithubRepo } from '../../types/github';
|
||||
|
||||
const props = defineProps({
|
||||
sortBy: {
|
||||
|
62
content/.vuepress/composables/cache.ts
Normal file
62
content/.vuepress/composables/cache.ts
Normal 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 };
|
||||
};
|
72
content/.vuepress/composables/fetchAndCache.ts
Normal file
72
content/.vuepress/composables/fetchAndCache.ts
Normal 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 };
|
||||
};
|
@ -1,24 +1,46 @@
|
||||
import { defineUserConfig, defaultTheme } from 'vuepress';
|
||||
import { removeHtmlExtensionPlugin } from 'vuepress-plugin-remove-html-extension';
|
||||
import head from './head';
|
||||
import locales from './locales';
|
||||
import { defaultTheme } from '@vuepress/theme-default';
|
||||
import { viteBundler } from '@vuepress/bundler-vite';
|
||||
import { defineUserConfig } from 'vuepress';
|
||||
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';
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
export default defineUserConfig({
|
||||
lang: 'fr-FR',
|
||||
title: 'Lucien Cartier-Tilet',
|
||||
description: 'Site web personnel de Lucien Cartier-Tilet',
|
||||
head: head,
|
||||
bundler: viteBundler({}),
|
||||
markdown: {
|
||||
html: true,
|
||||
linkify: 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,
|
||||
theme: defaultTheme({
|
||||
contributors: false,
|
||||
locales: themeLocales,
|
||||
repo: 'https://labs.phundrak.com/phundrak/phundrak.com',
|
||||
themePlugins: {
|
||||
copyCode: false,
|
||||
prismjs: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { HeadAttrsConfig } from 'vuepress';
|
||||
|
||||
interface SimplifiedHeader {
|
||||
tag: string;
|
||||
content: [any];
|
||||
content: HeadAttrsConfig[];
|
||||
}
|
||||
|
||||
const simplifiedHead = [
|
||||
const simplifiedHead: SimplifiedHeader[] = [
|
||||
{
|
||||
tag: 'meta',
|
||||
content: [
|
||||
@ -35,6 +37,10 @@ const simplifiedHead = [
|
||||
name: 'twitter:creator',
|
||||
content: '@phundrak',
|
||||
},
|
||||
{
|
||||
name: 'build-status',
|
||||
content: `value: ${process.env.NODE_ENV}`,
|
||||
},
|
||||
{ name: 'msapplication-TileColor', content: '#3b4252' },
|
||||
{ name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' },
|
||||
{ name: 'theme-color', content: '#3b4252' },
|
||||
@ -117,12 +123,16 @@ const simplifiedHead = [
|
||||
},
|
||||
];
|
||||
|
||||
let head = [];
|
||||
const headBuilder = [];
|
||||
simplifiedHead.forEach((tag) => {
|
||||
tag.content.forEach((element: any) => {
|
||||
head.push([tag.tag, element]);
|
||||
tag.content.forEach((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;
|
||||
|
@ -1,4 +1,6 @@
|
||||
const locales = {
|
||||
import { SearchProLocaleConfig } from 'vuepress-plugin-search-pro';
|
||||
|
||||
export const locales = {
|
||||
'/': {
|
||||
lang: 'fr-FR',
|
||||
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...',
|
||||
},
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ me.
|
||||
- [[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,
|
||||
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 there’s a chance I’ll consider your message as spam)
|
||||
|
||||
** Other Websites
|
||||
|
@ -18,8 +18,8 @@ websites to function properly or function properly or more
|
||||
efficiently.
|
||||
|
||||
This website uses some functional cookies in order to remember your
|
||||
preferences, such as your preferred language or its color theme. These
|
||||
cookies are not and cannot be used to track you.
|
||||
preferences, such as your preferred language or its colour theme.
|
||||
These cookies are not and cannot be used to track you.
|
||||
|
||||
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
|
||||
@ -27,8 +27,8 @@ record traffic to the site.
|
||||
|
||||
*** How can I control cookies on my computer?
|
||||
If you don't want Cloudflare to record your browsing activity on my
|
||||
website, a good adblocker should do the trick. I personally recommend
|
||||
[[https://ublockorigin.com/][uBlock Origin]], one of the most effective adblockers I know of if not
|
||||
website, a good ad blocker should do the trick. I personally recommend
|
||||
[[https://ublockorigin.com/][uBlock Origin]], one of the most effective ad blockers I know of if not
|
||||
the most effective one.
|
||||
|
||||
You can also manually delete cookies from your browser, but given the
|
||||
@ -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
|
||||
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 won’t 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?
|
||||
There’s 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?
|
||||
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
|
||||
might add some user tracking, however don’t worry, Matomo (the service
|
||||
I would use) would only track you on this website and this website
|
||||
only. Matomo respects the privacy of a website’s users.
|
||||
|
||||
The date of the last update of this web page can be found at its very
|
||||
beginning.
|
||||
website behaves, or if I notice errors on this page (such as typos).
|
||||
|
||||
** I have other questions
|
||||
And I have the answers! I’ll be more thang happy to chat with you by
|
||||
And I have the answers! I’ll be more than happy to chat with you by
|
||||
email, feel free to send me one at [[mailto:lucien@phundrak.com][lucien@phundrak.com]].
|
||||
|
||||
#+include: other-links
|
||||
|
@ -15,7 +15,7 @@ sociaux où vous pouvez me suivre.
|
||||
- {{{icon(writefreely)}}} *Writefreely* :
|
||||
- [[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
|
||||
- {{{icon(discord)}}} *Discord* : =Phundrak#0001= (dites-moi que vous venez
|
||||
- {{{icon(discord)}}} *Discord* : =@phundrak= (dites-moi que vous venez
|
||||
d’ici, autrement il est possible que je considère le message comme
|
||||
du pourriel)
|
||||
|
||||
|
@ -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/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: istorias corta (a
|
||||
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)
|
||||
|
||||
** Other Websites
|
||||
|
@ -16,19 +16,15 @@ es an con tota estrema comun. Los capasi esa locas ueb a funsiona
|
||||
coreta o plu eficas.
|
||||
|
||||
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
|
||||
Matomo par sabe lo ce lo usores de esa loca ueb fa asi, ma Matomo
|
||||
trasa lo sola sur mea locas ueb.
|
||||
la lingua o la motif ce te ia eleje.
|
||||
|
||||
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
|
||||
me loca ueb o no.
|
||||
|
||||
*** Como me pote controla la cucis sur mea computa?
|
||||
Si te no vole ce Cloudflare o Matomo recorda cucis, un bon
|
||||
anti-comersial como [[https://ublockorigin.com/][uBlock Origin]] ta pote
|
||||
te proteje (es la plu eficas ce me conose). Matomo ance respecta la
|
||||
demanda “no trasa me” de surfadores.
|
||||
Si te no vole ce Cloudflare recorda cucis, un bon anti-comersial como
|
||||
[[https://ublockorigin.com/][uBlock Origin]] ta pote te proteje (es la plu eficas ce me conose).
|
||||
|
||||
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,
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
ajunta esa comersiales, o me ia dementi, o me es secuestrada e lo es
|
||||
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
|
||||
cambias de funsiona de mea loca ueb, o si me trova eras. Te pote trove
|
||||
la ultima refresci de esa pajina a supra.
|
||||
cambias de funsiona de mea loca ueb, o si me trova eras.
|
||||
|
||||
*** Me ave otra demandas
|
||||
** Me ave otra demandas
|
||||
Te pote scrive me un eposta a la adirije de eposta
|
||||
[[mailto:lucien@phundrak.com][lucien@phundrak.com]].
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
** Où est hébergé le site ?
|
||||
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,
|
||||
=labs.phundrak.com= et =mail.phundrak.com=, sont hébergé sur d’autres
|
||||
=labs.phundrak.com= et =mail.phundrak.com=, sont hébergés sur d’autres
|
||||
serveurs loués à Scaleway et à OVH France respectivement, et les
|
||||
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.
|
||||
|
||||
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
|
||||
enregistrer le trafic sur le site.
|
||||
|
||||
@ -33,7 +33,7 @@ activités, un bon anti-pub devrait faire l’affaire. Je recommande
|
||||
personnellement [[https://ublockorigin.com/][uBlock Origin]], l’un des bloqueurs de pub les plus
|
||||
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
|
||||
sera sans doute plus rapide pour vous de chercher sur DuckDuckGo,
|
||||
Qwant ou Startpage comment faire pour votre navigateur actuel (si vous
|
||||
@ -44,15 +44,29 @@ voudrez éviter Google).
|
||||
Il existe d’autres méthodes plus subtiles qui permettent de traquer
|
||||
quelqu’un sur internet, ou même via des mails ou tout contenu web
|
||||
rendu à l’écran, comme pixels espions (des images extrêmement
|
||||
petites). Il est également possible de stocker des cookies Flash ou
|
||||
des objets locaux partagés.
|
||||
petites). Il est de plus possible de stocker des cookies Flash ou des
|
||||
objets locaux partagés.
|
||||
|
||||
Ce site n’en utilise absolument pas.
|
||||
*** Mais, est-ce qu’il y a du tracking sur ce site ?
|
||||
Oui, mais en préservant intégralement votre vie privée. J’utilise ma
|
||||
propre instance de [[https://umami.is][Umami]] qui est un service d’analyse 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, l’information est envoyée qu’une
|
||||
visite s’est produite, avec quelques informations supplémentaires,
|
||||
mais rien qui permet d’identifier 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
|
||||
s’offrent à vous :
|
||||
- Activez l’option 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 qu’il y a de la pub ciblée sur ce site ?
|
||||
Il n’y a tout simplement aucune pub sur ce site. Si vous en voyez,
|
||||
vous avez sans doute un virus installé sur votre ordinateur. Si ça
|
||||
vient en effet de mon site web, cela veut dire qu’il a été hacké. Si
|
||||
vient en effet de mon site web, cela veut dire qu’il a été piraté. Si
|
||||
vous voyez sur son dépôt de code que c’est bien moi qui ai rajouté ces
|
||||
pubs, cela veut dire que j’ai perdu tout mon sens moral, ou bien que
|
||||
j’ai été kidnappé et il s’agit d’un appel au secours.
|
||||
@ -60,11 +74,7 @@ j’ai été kidnappé et il s’agit d’un appel au secours.
|
||||
** Est-ce que cette page est souvent mise à jour ?
|
||||
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
|
||||
sur la page. Il se peut aussi que j’ajoute un jour un tracking des
|
||||
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.
|
||||
sur la page.
|
||||
|
||||
** J’ai d’autres questions
|
||||
Et je serai heureux d’y répondre par mail. Vous pouvez me contacter
|
||||
|
5295
package-lock.json
generated
Normal file
5295
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -8,23 +8,26 @@
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"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",
|
||||
"git-cliff": "^1.1.2",
|
||||
"vuepress": "2.0.0-beta.61"
|
||||
"git-cliff": "^1.4.0",
|
||||
"vuepress": "2.0.0-rc.13",
|
||||
"vuepress-plugin-search-pro": "^2.0.0-rc.43"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vuepress dev content",
|
||||
"build": "vuepress build content"
|
||||
},
|
||||
"dependencies": {
|
||||
"less": "^4.1.3",
|
||||
"nord": "^0.2.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"vuepress-plugin-remove-html-extension": "^0.1.0"
|
||||
"less": "^4.2.0",
|
||||
"nord": "^0.2.1"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.3.0"
|
||||
}
|
||||
|
6
shell.nix
Normal file
6
shell.nix
Normal file
@ -0,0 +1,6 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
nodejs_22
|
||||
];
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user