diff --git a/package.json b/package.json index f82fc11..5f90b08 100755 --- a/package.json +++ b/package.json @@ -63,6 +63,8 @@ "shiki": "^1.7.0", "smol-toml": "^1.3.0", "svelte-inview": "^4.0.2", + "tailwind-merge": "^2.5.4", "ts-pattern": "^5.2.0" - } + }, + "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4149ce..7db5e0a 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: svelte-inview: specifier: ^4.0.2 version: 4.0.2(svelte@4.2.18) + tailwind-merge: + specifier: ^2.5.4 + version: 2.5.4 ts-pattern: specifier: ^5.2.0 version: 5.2.0 @@ -1541,6 +1544,9 @@ packages: resolution: {integrity: sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==} engines: {node: '>=16'} + tailwind-merge@2.5.4: + resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==} + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -3036,6 +3042,8 @@ snapshots: magic-string: 0.30.5 periscopic: 3.1.0 + tailwind-merge@2.5.4: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.4): dependencies: tailwindcss: 3.4.4 diff --git a/scripts/generate-blurred-images.mjs b/scripts/generate-blurred-images.mjs index a990d63..70a36da 100644 --- a/scripts/generate-blurred-images.mjs +++ b/scripts/generate-blurred-images.mjs @@ -2,7 +2,6 @@ import { globby } from 'globby' import { spawnSync } from 'node:child_process' -import { getFileNameWithoutExtension } from '../src/lib/Helper.mjs' // This script should be run from the root of the application const root = new URL('..', import.meta.url) @@ -89,3 +88,8 @@ function exec(command) { throw new Error(error) } } + +/** Get the filename of a filepath without its extension */ +export function getFileNameWithoutExtension(filePath) { + return filePath.split('/').at(-1)?.replace(/\..*$/, '') +} diff --git a/src/content/plugins.toml b/src/content/plugins.toml index e02bfc0..566f5ca 100644 --- a/src/content/plugins.toml +++ b/src/content/plugins.toml @@ -1,13 +1,15 @@ +############################################# +# To submit a plugin add a new entry to this file. +############################################# + +# Structure: # - name: Name of the plugin # - tagline: Very concise description of the plugin -# - url: Link to the Github repository -# - logo: Relative link to the logo placed in the `/static/plugins-data/logos/` directory (without the `/static/` though) +# - url: Link to the Git repository/website +# - logo (optional): Relative link to the logo placed in the `/static/plugins-data/logos/` directory (without the `/static/` though) # - tags: Tags for the plugin. Capitalized -# - featured: Whether the plugin is featured at the top. A maximum of 4 is shown -# - weight: Determines the sort order. A higher weight comes first. - -# Please only use darkmode images/videos. -# Do not use yellow and yellowish colors, unless nesssecary. +# - featured (optional): Whether the plugin is featured at the top. A maximum of 4 are shown +# - weight (optional): Determines the sort order. A higher weight comes first. [[plugins]] diff --git a/src/lib/Helper.mjs b/src/lib/Helper.ts similarity index 79% rename from src/lib/Helper.mjs rename to src/lib/Helper.ts index 6088866..d01f147 100755 --- a/src/lib/Helper.mjs +++ b/src/lib/Helper.ts @@ -17,6 +17,9 @@ import { import { inview } from 'svelte-inview' import { pick } from 'remeda' import { writable } from 'svelte/store' +import type { ClassValue } from 'clsx' +import clsx from 'clsx' +import { twMerge } from 'tailwind-merge' /** * Fade: The initial opacity from 0 to 1. @@ -24,12 +27,18 @@ import { writable } from 'svelte/store' * Zoom: The scale from 0 to 1. * * Slide: Slide in in pixels. - * - * @param {{fade?: number, zoom?: number, slide?: number, duration?: number, delay?: number, threshold?: number}} options - * @param { HTMLElement } node - * @returns */ -export function animateIn(node, options = {}) { +export function animateIn( + node: HTMLElement, + options: { + fade?: number + zoom?: number + slide?: number + duration?: number + delay?: number + threshold?: number + } = {} +) { // Do nothing on mobile if (getIsMobile()) return { destroy: () => undefined } @@ -53,9 +62,10 @@ export function animateIn(node, options = {}) { }) .join(';') + // @ts-ignore node.style = style - let timeoutId + let timeoutId: NodeJS.Timeout node.addEventListener('inview_enter', callback) @@ -85,14 +95,14 @@ export function animateIn(node, options = {}) { * @param {number} given * @returns */ -export function lerp(start, end, given) { +export function lerp(start: number, end: number, given: number) { return (1 - given) * start + given * end } /** * Taken from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser/11381730#11381730 */ -export function getIsMobile() { +export function getIsMobile(): boolean { let check = false ;(function (a) { if ( @@ -104,23 +114,28 @@ export function getIsMobile() { ) ) check = true + // @ts-expect-error })(navigator.userAgent || navigator.vendor || window.opera) return check } /** Get the `generated_` for the provided path **/ -export function getGeneratedPath(path, extension = 'webp') { +export function getGeneratedPath(path: string, extension: string = 'webp') { const directory = path.substring(0, path.lastIndexOf('/')) const filename = getFileNameWithoutExtension(path) return `${directory}/generated_${filename}.${extension}` } /** Get a random item from an array */ -export function getRandom(array) { - return array.at(Math.floor(Math.random() * array.length)) +export function getRandom(array: T[]): T { + return array.at(Math.floor(Math.random() * array.length))! } -export function formatDate(date, dateStyle = 'long', locales = 'en') { +export function formatDate( + date: string, + dateStyle: 'full' | 'long' | 'medium' | 'short' = 'long', + locales = 'en' +) { const dateToFormat = new Date(date) const dateFormatter = new Intl.DateTimeFormat(locales, { dateStyle }) @@ -128,13 +143,7 @@ export function formatDate(date, dateStyle = 'long', locales = 'en') { return dateFormatter.format(dateToFormat) } -/** - * - * @param {string} text - * @param {number} maxLenght - * @returns - */ -export function trimText(text, maxLenght) { +export function trimText(text: string, maxLenght: number) { if (text.length < maxLenght - 1) return text const lastSpace = text.slice(0, maxLenght).lastIndexOf(' ') @@ -143,8 +152,8 @@ export function trimText(text, maxLenght) { } /** Get the filename of a filepath without its extension */ -export function getFileNameWithoutExtension(filePath) { - return filePath.split('/').at(-1).replace(/\..*$/, '') +export function getFileNameWithoutExtension(filePath: string) { + return filePath.split('/').at(-1)?.replace(/\..*$/, '') } /** @@ -180,10 +189,8 @@ export function createThresholdStream({ clicksTarget = 69, clicksEachMs = 400, f /** * Tell the browser to preload an image - * - * @param {string} src */ -export function preloadImage(src) { +export function preloadImage(src: string) { return new Promise((resolve, reject) => { const image = new Image() image.src = src @@ -195,29 +202,27 @@ export function preloadImage(src) { /** * A writable store but as as an observable. * Observables are much nicher than regular stores. - * @template T - * @param {T} init - * @returns {Observable & { update: (updater: (state: T) => T) => void}} */ -export function writableObservable(init) { +export function writableObservable( + init: T +): Observable & { update: (updater: (state: T) => T) => void } { const { update, subscribe } = writable(init) const observable = new Observable((subscriber) => { const unsubscribe = subscribe((value) => subscriber.next(value)) return unsubscribe }) + // @ts-ignore observable.update = update + // @ts-ignore return observable } -/** - * Convert a store to an observable - * @template T - * @param {import('svelte/store').Readable} store - * @returns {Observable} - */ -export function convertStoreToObservable(store) { +/** Convert a store to an observable */ +export function convertStoreToObservable( + store: import('svelte/store').Readable +): Observable { return new Observable((subscriber) => { return store.subscribe((value) => subscriber.next(value)) }) @@ -225,10 +230,11 @@ export function convertStoreToObservable(store) { /** * Checks if two rectangles are intersecting - * @param {{size: number, coordinates: [x: number, y: number]}} rect1 - * @param {{size: number, coordinates: [x: number, y: number]} rect2 */ -export function isIntersecting(rect1, rect2) { +export function isIntersecting( + rect1: { size: number; coordinates: [x: number, y: number] }, + rect2: { size: number; coordinates: [x: number, y: number] } +) { return !( rect1.coordinates[0] + rect1.size < rect2.coordinates[0] || rect2.coordinates[0] + rect2.size < rect1.coordinates[0] || @@ -236,3 +242,11 @@ export function isIntersecting(rect1, rect2) { rect2.coordinates[1] + rect2.size < rect1.coordinates[1] ) } + +/** + * Merges class names using clsx and tailwind-merge. + * @returns The merged class name string. + */ +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)) +} diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 5eef4d8..3cdd622 100755 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -1,12 +1,13 @@ -