mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +01:00
migrate to biome (#944)
* migrate to biome * cleanup * fix * use tsx * script tweaks * fix test runs * path tweaks
This commit is contained in:
parent
d11617c2b2
commit
05ad0470c2
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"stylelint-config-standard",
|
|
||||||
"stylelint-config-css-modules",
|
|
||||||
"stylelint-prettier/recommended"
|
|
||||||
],
|
|
||||||
"plugins": ["stylelint-prettier"],
|
|
||||||
"syntax": "css",
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": true,
|
|
||||||
"property-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreProperties": ["composes", "compose-with"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"at-rule-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreAtRules": ["value", "include", "mixin", "extend"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"selector-class-pattern": null,
|
|
||||||
"keyframes-name-pattern": null,
|
|
||||||
"custom-property-pattern": null,
|
|
||||||
"selector-id-pattern": null,
|
|
||||||
"media-feature-range-notation": "prefix"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,13 @@
|
|||||||
import { defineConfig } from 'astro/config'
|
|
||||||
import { remarkLeadParagraph } from '../src/lib/remark-lead-paragraph/remark-lead-paragraph'
|
|
||||||
import { remarkToc } from '../src/lib/remark-toc/remark-toc'
|
|
||||||
import react from '@astrojs/react'
|
import react from '@astrojs/react'
|
||||||
import sitemap from '@astrojs/sitemap'
|
import sitemap from '@astrojs/sitemap'
|
||||||
|
import type { RemarkPlugins } from 'astro'
|
||||||
import expressiveCode from 'astro-expressive-code'
|
import expressiveCode from 'astro-expressive-code'
|
||||||
import redirectFrom from 'astro-redirect-from'
|
import redirectFrom from 'astro-redirect-from'
|
||||||
import config from './blog.config'
|
import { defineConfig } from 'astro/config'
|
||||||
import { getSlug } from '../src/lib/astro/getSlug'
|
import { getSlug } from '../src/lib/astro/getSlug'
|
||||||
|
import { remarkLeadParagraph } from '../src/lib/remark-lead-paragraph/remark-lead-paragraph'
|
||||||
|
import { remarkToc } from '../src/lib/remark-toc/remark-toc'
|
||||||
|
import config from './blog.config'
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@ -15,7 +16,7 @@ export default defineConfig({
|
|||||||
cacheDir: '.astro',
|
cacheDir: '.astro',
|
||||||
trailingSlash: 'always',
|
trailingSlash: 'always',
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [remarkLeadParagraph, remarkToc as any],
|
remarkPlugins: [remarkLeadParagraph, remarkToc] as unknown as RemarkPlugins,
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
// https://github.com/shikijs/shiki/blob/main/docs/themes.md
|
// https://github.com/shikijs/shiki/blob/main/docs/themes.md
|
||||||
theme: 'nord',
|
theme: 'nord',
|
||||||
|
@ -1,4 +1 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"plugins": ["prettier-plugin-astro"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.astro",
|
|
||||||
"options": { "parser": "astro" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
10
README.md
10
README.md
@ -140,7 +140,7 @@ If you want to know how, have a look at the respective components:
|
|||||||
|
|
||||||
All SVG assets under `src/images/` and from select iconset dependencies are converted to Astro & React components before building the site. Compiled components are placed under `src/images/components/` and all include the cleaned SVGs as inline HTML.
|
All SVG assets under `src/images/` and from select iconset dependencies are converted to Astro & React components before building the site. Compiled components are placed under `src/images/components/` and all include the cleaned SVGs as inline HTML.
|
||||||
|
|
||||||
All SVGs can then be imported from `@images/components` in all Astro or React components.
|
All SVGs can then be imported from `@/images/components` in all Astro or React components.
|
||||||
|
|
||||||
If you want to know how this works, have a look at the script:
|
If you want to know how this works, have a look at the script:
|
||||||
|
|
||||||
@ -177,18 +177,12 @@ npm start
|
|||||||
|
|
||||||
### 🔮 Linting
|
### 🔮 Linting
|
||||||
|
|
||||||
ESlint, Prettier, and Stylelint are setup for all linting purposes:
|
[Biome](https://biomejs.dev) is setup for all linting and formatting purposes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
To automatically format all code files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run format
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔮 Type Checking
|
### 🔮 Type Checking
|
||||||
|
|
||||||
Type checking can be invoked to check all TypeScript code, including within .astro files:
|
Type checking can be invoked to check all TypeScript code, including within .astro files:
|
||||||
|
26
biome.json
Normal file
26
biome.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"extends": ["@kremalicious/config/biome"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"include": ["*.astro"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useConst": "off",
|
||||||
|
"useImportType": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.test.ts", "*.test.tsx"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { z, type ImageFunction } from 'astro:content'
|
import { type ImageFunction, z } from 'astro:content'
|
||||||
|
|
||||||
const schemaShared = {
|
const schemaShared = {
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
|
@ -21,11 +21,7 @@ article kbd {
|
|||||||
rgb(0 0 0 / 0%)
|
rgb(0 0 0 / 0%)
|
||||||
);
|
);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
box-shadow:
|
box-shadow: 0 2px 0 #bbb, 0 3px 1px #999, 0 3px 0 #bbb, inset 0 1px 1px #fff,
|
||||||
0 2px 0 #bbb,
|
|
||||||
0 3px 1px #999,
|
|
||||||
0 3px 0 #bbb,
|
|
||||||
inset 0 1px 1px #fff,
|
|
||||||
inset 0 -1px 3px #ccc;
|
inset 0 -1px 3px #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,11 +36,8 @@ article kbd.dark {
|
|||||||
rgb(0 0 0 / 0%)
|
rgb(0 0 0 / 0%)
|
||||||
);
|
);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
box-shadow:
|
box-shadow: 0 2px 0 #000, 0 3px 1px #999, inset 0 1px 1px #aaa, inset 0 -1px
|
||||||
0 2px 0 #000,
|
3px #272727;
|
||||||
0 3px 1px #999,
|
|
||||||
inset 0 1px 1px #aaa,
|
|
||||||
inset 0 -1px 3px #272727;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article kbd.ios {
|
article kbd.ios {
|
||||||
@ -55,10 +48,8 @@ article kbd.ios {
|
|||||||
background-color: #b7b7bc;
|
background-color: #b7b7bc;
|
||||||
background-image: linear-gradient(to bottom, #efeff0, #b7b7bc);
|
background-image: linear-gradient(to bottom, #efeff0, #b7b7bc);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
box-shadow:
|
box-shadow: 0 1px 2px rgb(0 0 0 / 60%), 0 2px 3px rgb(0 0 0 / 10%), inset 0
|
||||||
0 1px 2px rgb(0 0 0 / 60%),
|
1px 0 #fff;
|
||||||
0 2px 3px rgb(0 0 0 / 10%),
|
|
||||||
inset 0 1px 0 #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article kbd.android {
|
article kbd.android {
|
||||||
@ -70,24 +61,15 @@ article kbd.android {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
background-color: #5e5e5e;
|
background-color: #5e5e5e;
|
||||||
box-shadow:
|
box-shadow: 0 2px 2px rgb(0 0 0 / 30%), 0 1px 0 #444, inset 0 1px 0 #868686;
|
||||||
0 2px 2px rgb(0 0 0 / 30%),
|
|
||||||
0 1px 0 #444,
|
|
||||||
inset 0 1px 0 #868686;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article kbd.android.dark {
|
article kbd.android.dark {
|
||||||
background: #222;
|
background: #222;
|
||||||
box-shadow:
|
box-shadow: 0 2px 2px rgb(0 0 0 / 70%), 0 1px 0 #444, inset 0 1px 0 #505050;
|
||||||
0 2px 2px rgb(0 0 0 / 70%),
|
|
||||||
0 1px 0 #444,
|
|
||||||
inset 0 1px 0 #505050;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
article kbd.android.color {
|
article kbd.android.color {
|
||||||
background: #083c5b;
|
background: #083c5b;
|
||||||
box-shadow:
|
box-shadow: 0 2px 2px rgb(0 0 0 / 70%), 0 1px 0 #444, inset 0 1px 0 #36647b;
|
||||||
0 2px 2px rgb(0 0 0 / 70%),
|
|
||||||
0 1px 0 #444,
|
|
||||||
inset 0 1px 0 #36647b;
|
|
||||||
}
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import eslint from '@eslint/js'
|
|
||||||
import tseslint from 'typescript-eslint'
|
|
||||||
import prettierRecommended from 'eslint-plugin-prettier/recommended'
|
|
||||||
import eslintPluginAstro from 'eslint-plugin-astro'
|
|
||||||
import globals from 'globals'
|
|
||||||
import gitignore from 'eslint-config-flat-gitignore'
|
|
||||||
import testingLibrary from 'eslint-plugin-testing-library'
|
|
||||||
import { fixupPluginRules } from '@eslint/compat'
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
languageOptions: {
|
|
||||||
globals: { ...globals.browser }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
gitignore(),
|
|
||||||
eslint.configs.recommended,
|
|
||||||
...tseslint.configs.recommended,
|
|
||||||
prettierRecommended,
|
|
||||||
...eslintPluginAstro.configs.recommended,
|
|
||||||
...eslintPluginAstro.configs['jsx-a11y-recommended'],
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-explicit-any': 'warn'
|
|
||||||
// "astro/no-set-html-directive": "error"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// See https://github.com/testing-library/eslint-plugin-testing-library/issues/853
|
|
||||||
// current solution from https://github.com/testing-library/eslint-plugin-testing-library/issues/899#issuecomment-2121272355
|
|
||||||
{
|
|
||||||
files: ['**/?(*.)+(spec|test).[jt]sx'],
|
|
||||||
plugins: {
|
|
||||||
'testing-library': fixupPluginRules({
|
|
||||||
rules: testingLibrary.rules
|
|
||||||
})
|
|
||||||
},
|
|
||||||
rules: testingLibrary.configs.react.rules
|
|
||||||
}
|
|
||||||
)
|
|
8166
package-lock.json
generated
8166
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
96
package.json
96
package.json
@ -17,52 +17,36 @@
|
|||||||
"test:unit": "vitest run --config './test/vitest.config.ts' --coverage",
|
"test:unit": "vitest run --config './test/vitest.config.ts' --coverage",
|
||||||
"test:unit:watch": "vitest watch --config './test/vitest.config.ts' --coverage",
|
"test:unit:watch": "vitest watch --config './test/vitest.config.ts' --coverage",
|
||||||
"test:e2e": "playwright test --config './test/playwright.config.ts'",
|
"test:e2e": "playwright test --config './test/playwright.config.ts'",
|
||||||
"lint": "run-p --silent lint:js lint:css lint:md",
|
"lint:md": "markdownlint --config '.config/markdownlint.json' --ignore-path .gitignore --dot './**/*.{md,markdown}'",
|
||||||
"lint:js": "eslint './{src,test,scripts}/**/*.{ts,tsx,astro,mjs,js,cjs}'",
|
"lint:biome": "biome check --write .",
|
||||||
"lint:css": "stylelint --config '.config/.stylelintrc.json' 'src/**/*.css'",
|
"lint": "run-p --silent lint:biome lint:md",
|
||||||
"lint:md": "markdownlint --config '.config/.markdownlint.json' --ignore-path .gitignore --dot './**/*.{md,markdown}'",
|
|
||||||
"format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx,md,json,css,astro,yml}'",
|
|
||||||
"deploy:s3": "./scripts/deploy-s3.sh",
|
"deploy:s3": "./scripts/deploy-s3.sh",
|
||||||
"new": "node --loader ts-node/esm scripts/new/index.ts",
|
"new": "tsx scripts/new/index.ts",
|
||||||
"create:icons": "node --loader ts-node/esm scripts/create-icons/index.ts",
|
"create:icons": "tsx scripts/create-icons/index.ts",
|
||||||
"create:redirects": "node --loader ts-node/esm scripts/redirect-from.ts",
|
"create:redirects": "tsx scripts/redirect-from.ts",
|
||||||
"create:symlinks": "./scripts/create-symlinks.sh",
|
"create:symlinks": "./scripts/create-symlinks.sh",
|
||||||
"move:downloads": "node --loader ts-node/esm scripts/move-downloads.ts",
|
"move:downloads": "tsx scripts/move-downloads.ts",
|
||||||
"prepare": "husky .config/husky",
|
"prepare": "husky .config/husky"
|
||||||
"lint:staged": "lint-staged"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx,astro}": [
|
"*.{js,jsx,ts,tsx,astro,css,json,md}": ["biome check --write ."],
|
||||||
"prettier --write",
|
"*.md": ["markdownlint --config '.config/markdownlint.json'"]
|
||||||
"eslint"
|
|
||||||
],
|
|
||||||
"*.css": [
|
|
||||||
"stylelint --config '.config/.stylelintrc.json' --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"**/*.json": [
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.md": [
|
|
||||||
"markdownlint --config '.config/.markdownlint.json'",
|
|
||||||
"prettier --write"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.7.0",
|
"@astrojs/check": "^0.8.3",
|
||||||
"@astrojs/react": "^3.6.0",
|
"@astrojs/react": "^3.6.0",
|
||||||
"@astrojs/rss": "^4.0.6",
|
"@astrojs/rss": "^4.0.7",
|
||||||
"@astrojs/sitemap": "^3.1.6",
|
"@astrojs/sitemap": "^3.1.6",
|
||||||
"@coingecko/cryptoformat": "^0.8.1",
|
"@coingecko/cryptoformat": "^0.8.1",
|
||||||
"@nanostores/persistent": "^0.10.1",
|
"@nanostores/persistent": "^0.10.1",
|
||||||
"@nanostores/query": "^0.3.3",
|
"@nanostores/query": "^0.3.3",
|
||||||
"@nanostores/react": "^0.7.2",
|
"@nanostores/react": "^0.7.2",
|
||||||
"@radix-ui/react-select": "^2.1.1",
|
"@radix-ui/react-select": "^2.1.1",
|
||||||
"@rainbow-me/rainbowkit": "^2.1.2",
|
"@rainbow-me/rainbowkit": "^2.1.3",
|
||||||
"@tanstack/react-query": "^5.45.1",
|
"@tanstack/react-query": "^5.45.1",
|
||||||
"astro": "4.11.0",
|
"astro": "4.12.2",
|
||||||
"astro-expressive-code": "^0.35.3",
|
"astro-expressive-code": "^0.35.3",
|
||||||
"astro-redirect-from": "^1.0.8",
|
"astro-redirect-from": "^1.1.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"dms2dec": "^1.1.0",
|
"dms2dec": "^1.1.0",
|
||||||
"fast-exif": "^2.0.1",
|
"fast-exif": "^2.0.1",
|
||||||
@ -77,32 +61,24 @@
|
|||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"viem": "^2.16.1",
|
"viem": "^2.18.2",
|
||||||
"wagmi": "^2.10.4"
|
"wagmi": "^2.12.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.1.0",
|
"@biomejs/biome": "^1.8.3",
|
||||||
"@eslint/js": "^9.5.0",
|
"@kremalicious/config": "^1.0.2",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.45.3",
|
||||||
"@testing-library/jest-dom": "^6.4.6",
|
"@testing-library/jest-dom": "^6.4.8",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@types/node": "^20.14.8",
|
"@types/node": "^20.14.8",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitest/coverage-v8": "^1.6.0",
|
"@vitest/coverage-v8": "^2.0.4",
|
||||||
"eslint": "^8.57.0",
|
"@vitest/ui": "^2.0.4",
|
||||||
"eslint-config-flat-gitignore": "^0.1.5",
|
"globby": "^14.0.2",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-plugin-astro": "^1.2.2",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
|
||||||
"eslint-plugin-react": "^7.34.0",
|
|
||||||
"eslint-plugin-testing-library": "^6.2.2",
|
|
||||||
"globals": "^15.6.0",
|
|
||||||
"hast-util-to-html": "^9.0.1",
|
"hast-util-to-html": "^9.0.1",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.1.3",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"jsdom": "^24.1.1",
|
||||||
"jsdom": "^24.1.0",
|
|
||||||
"markdownlint-cli": "^0.41.0",
|
"markdownlint-cli": "^0.41.0",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
@ -110,20 +86,13 @@
|
|||||||
"node-iptc": "^1.0.5",
|
"node-iptc": "^1.0.5",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"ora": "^8.0.1",
|
"ora": "^8.0.1",
|
||||||
"prettier": "^3.3.2",
|
|
||||||
"prettier-plugin-astro": "^0.14.0",
|
|
||||||
"sharp-ico": "^0.1.5",
|
"sharp-ico": "^0.1.5",
|
||||||
"stylelint": "^16.6.1",
|
"svgo": "^3.3.2",
|
||||||
"stylelint-config-css-modules": "^4.4.0",
|
"tsx": "^4.16.2",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"typescript": "^5.5.4",
|
||||||
"stylelint-prettier": "^5.0.0",
|
|
||||||
"svgo": "^3.2.0",
|
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"typescript": "^5.5.2",
|
|
||||||
"typescript-eslint": "^7.13.1",
|
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^2.0.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20"
|
"node": "20"
|
||||||
@ -133,10 +102,7 @@
|
|||||||
"url": "https://github.com/kremalicious/blog.git"
|
"url": "https://github.com/kremalicious/blog.git"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": ["defaults", ">0.2%"],
|
||||||
"defaults",
|
|
||||||
">0.2%"
|
|
||||||
],
|
|
||||||
"development": [
|
"development": [
|
||||||
"last 1 chrome version",
|
"last 1 chrome version",
|
||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
|
14
public/sw.js
14
public/sw.js
@ -1,14 +1,14 @@
|
|||||||
self.addEventListener('install', function () {
|
self.addEventListener('install', () => {
|
||||||
self.skipWaiting()
|
self.skipWaiting()
|
||||||
})
|
})
|
||||||
|
|
||||||
self.addEventListener('activate', function () {
|
self.addEventListener('activate', () => {
|
||||||
self.registration
|
self.registration
|
||||||
.unregister()
|
.unregister()
|
||||||
.then(function () {
|
.then(() => self.clients.matchAll())
|
||||||
return self.clients.matchAll()
|
.then((clients) => {
|
||||||
})
|
for (const client of clients) {
|
||||||
.then(function (clients) {
|
client.navigate(client.url)
|
||||||
clients.forEach((client) => client.navigate(client.url))
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
import { test } from 'vitest'
|
import { test } from 'vitest'
|
||||||
import fs from 'fs/promises'
|
|
||||||
import path from 'path'
|
|
||||||
import { generateIcons } from './index'
|
import { generateIcons } from './index'
|
||||||
|
|
||||||
const distDir = path.resolve(__dirname, 'tmp')
|
const distDir = path.resolve(__dirname, 'tmp')
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
//
|
//
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import ps from 'node:path/posix'
|
import ps from 'node:path/posix'
|
||||||
import ora from 'ora'
|
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
|
import ora from 'ora'
|
||||||
import { toInnerSvg } from './svg.ts'
|
import { toInnerSvg } from './svg.ts'
|
||||||
import { toAstroComponent } from './toAstroComponent.ts'
|
import { toAstroComponent } from './toAstroComponent.ts'
|
||||||
import { toReactComponent } from './toReactComponent.ts'
|
import { toReactComponent } from './toReactComponent.ts'
|
||||||
@ -25,24 +25,26 @@ const distDir = ps.resolve(currentDir, 'src/images/components')
|
|||||||
// Data related to each icon exported by this package.
|
// Data related to each icon exported by this package.
|
||||||
const icons = []
|
const icons = []
|
||||||
|
|
||||||
export async function generateIcons(distDir: string) {
|
export async function generateIcons(_distDir: string) {
|
||||||
const spinner = ora(
|
const spinner = ora(
|
||||||
`${chalk.bold('[create-icons]')} Create icon components`
|
`${chalk.bold('[create-icons]')} Create icon components`
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
|
const dist = _distDir || distDir
|
||||||
|
|
||||||
// clean the distribution directory
|
// clean the distribution directory
|
||||||
await fs.rm(distDir, { force: true, recursive: true })
|
await fs.rm(dist, { force: true, recursive: true })
|
||||||
await fs.mkdir(distDir, { recursive: true })
|
await fs.mkdir(dist, { recursive: true })
|
||||||
await fs.mkdir(`${distDir}/react`, { recursive: true })
|
await fs.mkdir(`${dist}/react`, { recursive: true })
|
||||||
|
|
||||||
// copy the attribute typings file
|
// copy the attribute typings file
|
||||||
await fs.copyFile(
|
await fs.copyFile(
|
||||||
ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
|
ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
|
||||||
ps.resolve(distDir, 'Props.d.ts')
|
ps.resolve(dist, 'Props.d.ts')
|
||||||
)
|
)
|
||||||
await fs.copyFile(
|
await fs.copyFile(
|
||||||
ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
|
ps.resolve(currentDir, 'scripts/create-icons/Props.d.ts'),
|
||||||
ps.resolve(`${distDir}/react`, 'Props.d.ts')
|
ps.resolve(`${dist}/react`, 'Props.d.ts')
|
||||||
)
|
)
|
||||||
|
|
||||||
// convert the SVG files into Astro & React components
|
// convert the SVG files into Astro & React components
|
||||||
@ -86,14 +88,14 @@ export async function generateIcons(distDir: string) {
|
|||||||
|
|
||||||
// write the astro component to a file
|
// write the astro component to a file
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
ps.resolve(distDir, `${baseName}.astro`),
|
ps.resolve(dist, `${baseName}.astro`),
|
||||||
toAstroComponent(innerSVG, title),
|
toAstroComponent(innerSVG, title),
|
||||||
'utf8'
|
'utf8'
|
||||||
)
|
)
|
||||||
|
|
||||||
// write the react component to a file
|
// write the react component to a file
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
ps.resolve(`${distDir}/react`, `${baseName}.tsx`),
|
ps.resolve(`${dist}/react`, `${baseName}.tsx`),
|
||||||
toReactComponent(innerSVG, title),
|
toReactComponent(innerSVG, title),
|
||||||
'utf8'
|
'utf8'
|
||||||
)
|
)
|
||||||
@ -109,11 +111,11 @@ export async function generateIcons(distDir: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write the main Astro entry `index.ts` file
|
// write the main Astro entry `index.ts` file
|
||||||
await fs.writeFile(ps.resolve(distDir, 'index.ts'), contentOfIndexJS, 'utf8')
|
await fs.writeFile(ps.resolve(dist, 'index.ts'), contentOfIndexJS, 'utf8')
|
||||||
|
|
||||||
// write the main React entry `react/index.ts` file
|
// write the main React entry `react/index.ts` file
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
ps.resolve(`${distDir}/react`, 'index.ts'),
|
ps.resolve(`${dist}/react`, 'index.ts'),
|
||||||
contentOfIndexReactJS,
|
contentOfIndexReactJS,
|
||||||
'utf8'
|
'utf8'
|
||||||
)
|
)
|
||||||
@ -121,7 +123,7 @@ export async function generateIcons(distDir: string) {
|
|||||||
spinner.succeed(
|
spinner.succeed(
|
||||||
`${chalk.bold('[create-icons]')} Generated ${
|
`${chalk.bold('[create-icons]')} Generated ${
|
||||||
icons.length
|
icons.length
|
||||||
} icons into @images/components.`
|
} icons into @/images/components.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { test, expect, vi } from 'vitest'
|
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { glob } from 'glob'
|
|
||||||
import { copyZipFiles } from './move-downloads'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
|
import { glob } from 'glob'
|
||||||
|
import type { Ora } from 'ora'
|
||||||
|
import { expect, test, vi } from 'vitest'
|
||||||
|
import { copyZipFiles } from './move-downloads'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
@ -21,12 +22,13 @@ test('copyZipFiles should copy zip files', async () => {
|
|||||||
globMock.mockReturnValue(['file1.zip', 'file2.zip'])
|
globMock.mockReturnValue(['file1.zip', 'file2.zip'])
|
||||||
|
|
||||||
const mockOra = {
|
const mockOra = {
|
||||||
|
text: '',
|
||||||
start: vi.fn(),
|
start: vi.fn(),
|
||||||
succeed: vi.fn(),
|
succeed: vi.fn(),
|
||||||
fail: vi.fn()
|
fail: vi.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
await copyZipFiles(sourceDir, destDir, mockOra as any)
|
await copyZipFiles(sourceDir, destDir, mockOra as unknown as Ora)
|
||||||
|
|
||||||
const file1 = await fs.readFile(path.join(destDir, 'file1.zip'), 'utf-8')
|
const file1 = await fs.readFile(path.join(destDir, 'file1.zip'), 'utf-8')
|
||||||
const file2 = await fs.readFile(path.join(destDir, 'file2.zip'), 'utf-8')
|
const file2 = await fs.readFile(path.join(destDir, 'file2.zip'), 'utf-8')
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
//
|
//
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import globby from 'globby'
|
|
||||||
import ora, { type Ora } from 'ora'
|
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
|
import { globby } from 'globby'
|
||||||
|
import ora, { type Ora } from 'ora'
|
||||||
|
|
||||||
const sourceFolder = './content/articles/'
|
const sourceFolder = './content/articles/'
|
||||||
const destinationFolder = './public/get/'
|
const destinationFolder = './public/get/'
|
||||||
@ -18,7 +18,7 @@ const spinner = ora(
|
|||||||
|
|
||||||
function removeFolderContents(folderPath: string) {
|
function removeFolderContents(folderPath: string) {
|
||||||
if (fs.existsSync(folderPath)) {
|
if (fs.existsSync(folderPath)) {
|
||||||
fs.readdirSync(folderPath).forEach((file) => {
|
for (const file of fs.readdirSync(folderPath)) {
|
||||||
const filePath = path.join(folderPath, file)
|
const filePath = path.join(folderPath, file)
|
||||||
if (fs.lstatSync(filePath).isDirectory()) {
|
if (fs.lstatSync(filePath).isDirectory()) {
|
||||||
removeFolderContents(filePath)
|
removeFolderContents(filePath)
|
||||||
@ -26,7 +26,7 @@ function removeFolderContents(folderPath: string) {
|
|||||||
} else {
|
} else {
|
||||||
fs.unlinkSync(filePath)
|
fs.unlinkSync(filePath)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,22 +46,21 @@ export async function copyZipFiles(
|
|||||||
// Find all files recursively in the source folder
|
// Find all files recursively in the source folder
|
||||||
const zipFiles = await globby(filesGlob, { cwd: source })
|
const zipFiles = await globby(filesGlob, { cwd: source })
|
||||||
|
|
||||||
zipFiles.forEach((zipFile: string) => {
|
for (const zipFile of zipFiles) {
|
||||||
const sourcePath = path.join(source, zipFile)
|
const sourcePath = path.join(source, zipFile)
|
||||||
const destinationPath = path.join(destination, path.basename(zipFile))
|
const destinationPath = path.join(destination, path.basename(zipFile))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Copy the file to the destination folder
|
// Copy the file to the destination folder
|
||||||
fs.copyFileSync(sourcePath, destinationPath)
|
fs.copyFileSync(sourcePath, destinationPath)
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
spinner.fail(
|
spinner.fail(
|
||||||
`${chalk.bold('[move-downloads]')} Error copying ${zipFile}: ${
|
`${chalk.bold('[move-downloads]')} Error copying ${zipFile}: ${
|
||||||
(error as Error).message
|
(error as Error).message
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
spinner.succeed(
|
spinner.succeed(
|
||||||
`${chalk.bold('[move-downloads]')} Copied ${
|
`${chalk.bold('[move-downloads]')} Copied ${
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import fs from 'node:fs/promises'
|
|
||||||
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
|
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { slugify } from '../../src/lib/slugify/slugify.js'
|
|
||||||
import type { Ora } from 'ora'
|
import type { Ora } from 'ora'
|
||||||
|
import { slugify } from '../../src/lib/slugify/slugify.js'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const templatePath = path.join(__dirname, 'new-article.md')
|
const templatePath = path.join(__dirname, 'new-article.md')
|
||||||
@ -14,7 +14,7 @@ export async function createArticlePost(
|
|||||||
title: string,
|
title: string,
|
||||||
newDate?: string
|
newDate?: string
|
||||||
) {
|
) {
|
||||||
let file
|
let file = ''
|
||||||
const date = newDate
|
const date = newDate
|
||||||
? new Date(newDate).toISOString()
|
? new Date(newDate).toISOString()
|
||||||
: new Date().toISOString()
|
: new Date().toISOString()
|
||||||
@ -44,7 +44,7 @@ export async function createArticlePost(
|
|||||||
// create post file
|
// create post file
|
||||||
await fs.appendFile(file, newContents)
|
await fs.appendFile(file, newContents)
|
||||||
spinner.succeed(`New post '${title}' created.`)
|
spinner.succeed(`New post '${title}' created.`)
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
spinner.fail((error as Error).message)
|
spinner.fail((error as Error).message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { existsSync, mkdirSync, readFileSync, promises as fs } from 'node:fs'
|
import { promises as fs, existsSync, mkdirSync, readFileSync } from 'node:fs'
|
||||||
import { slugify } from '../../src/lib/slugify/slugify.js'
|
|
||||||
import { readOutExif } from '../../src/lib/exif/readOutExif.js'
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { Ora } from 'ora'
|
import type { Ora } from 'ora'
|
||||||
|
import { readOutExif } from '../../src/lib/exif/readOutExif.js'
|
||||||
|
import { slugify } from '../../src/lib/slugify/slugify.js'
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const templatePathPhoto = path.join(__dirname, 'new-photo.md')
|
const templatePathPhoto = path.join(__dirname, 'new-photo.md')
|
||||||
@ -14,10 +14,10 @@ export async function createPhotoPost(
|
|||||||
photo: string,
|
photo: string,
|
||||||
photoTitle?: string
|
photoTitle?: string
|
||||||
) {
|
) {
|
||||||
let title
|
let title: string | undefined
|
||||||
let titleSlug
|
let titleSlug: string
|
||||||
let date
|
let date: string
|
||||||
let postPhotoFile
|
let postPhotoFile = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const templatePhoto = readFileSync(templatePathPhoto).toString()
|
const templatePhoto = readFileSync(templatePathPhoto).toString()
|
||||||
@ -25,7 +25,7 @@ export async function createPhotoPost(
|
|||||||
if (!exifData) throw new Error('No exif data found in image')
|
if (!exifData) throw new Error('No exif data found in image')
|
||||||
const { iptc, exif } = exifData
|
const { iptc, exif } = exifData
|
||||||
|
|
||||||
title = iptc?.object_name || iptc?.title || photoTitle
|
title = iptc?.object_name || iptc?.caption || photoTitle
|
||||||
if (!title)
|
if (!title)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'No title given. Add to IPTC, or use the format `npm run new photo path/to/photo.jpg "Title of post"'
|
'No title given. Add to IPTC, or use the format `npm run new photo path/to/photo.jpg "Title of post"'
|
||||||
@ -36,7 +36,7 @@ export async function createPhotoPost(
|
|||||||
date = new Date(exif?.date).toISOString()
|
date = new Date(exif?.date).toISOString()
|
||||||
const dateShort = date.slice(0, 10)
|
const dateShort = date.slice(0, 10)
|
||||||
const description = iptc?.caption
|
const description = iptc?.caption
|
||||||
const keywords = (iptc?.keywords as string[])?.join(`\n - `)
|
const keywords = (iptc?.keywords as string[])?.join('\n - ')
|
||||||
const folderName = `${dateShort}-${titleSlug}`
|
const folderName = `${dateShort}-${titleSlug}`
|
||||||
const destination = `${dest}/${folderName}`
|
const destination = `${dest}/${folderName}`
|
||||||
postPhotoFile = `${destination}/index.md`
|
postPhotoFile = `${destination}/index.md`
|
||||||
@ -68,7 +68,7 @@ export async function createPhotoPost(
|
|||||||
spinner.succeed(
|
spinner.succeed(
|
||||||
`New photo post '${title}' under '${postPhotoFile}' created.`
|
`New photo post '${title}' under '${postPhotoFile}' created.`
|
||||||
)
|
)
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
spinner.fail((error as Error).message)
|
spinner.fail((error as Error).message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { test, expect, describe, afterEach } from 'vitest'
|
|
||||||
import { createPhotoPost } from './createPhotoPost'
|
|
||||||
import { promises as fs } from 'node:fs'
|
import { promises as fs } from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import type { Ora } from 'ora'
|
import type { Ora } from 'ora'
|
||||||
|
import { afterEach, describe, expect, test } from 'vitest'
|
||||||
import { createArticlePost } from './createArticlePost'
|
import { createArticlePost } from './createArticlePost'
|
||||||
|
import { createPhotoPost } from './createPhotoPost'
|
||||||
|
|
||||||
const destFolder = path.join('.', 'test/__fixtures__/tmp')
|
const destFolder = path.join('.', 'test/__fixtures__/tmp')
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ describe('npm run new', () => {
|
|||||||
.catch(() => false))
|
.catch(() => false))
|
||||||
|
|
||||||
expect(fileExists).toBe(true)
|
expect(fileExists).toBe(true)
|
||||||
expect(spinner.text).toContain(`New photo post`)
|
expect(spinner.text).toContain('New photo post')
|
||||||
|
|
||||||
// Compare the generated index.md with the fixture new-photo.md
|
// Compare the generated index.md with the fixture new-photo.md
|
||||||
const generatedContent =
|
const generatedContent =
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import ora from 'ora'
|
import ora from 'ora'
|
||||||
import { createPhotoPost } from './createPhotoPost.js'
|
|
||||||
import { createArticlePost } from './createArticlePost.js'
|
import { createArticlePost } from './createArticlePost.js'
|
||||||
|
import { createPhotoPost } from './createPhotoPost.js'
|
||||||
|
|
||||||
const postsPath = path.join('.', 'content', 'articles')
|
const postsPath = path.join('.', 'content', 'articles')
|
||||||
const photosPath = path.join('.', 'content', 'photos')
|
const photosPath = path.join('.', 'content', 'photos')
|
||||||
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@ -3,7 +3,7 @@ interface Window {
|
|||||||
readonly [language: string]: {
|
readonly [language: string]: {
|
||||||
readonly index: lunr.Index
|
readonly index: lunr.Index
|
||||||
readonly store: {
|
readonly store: {
|
||||||
readonly [key: string]: any
|
readonly [key: string]: unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { ChevronLeft } from '@images/components'
|
import { ChevronLeft } from '@/images/components'
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { markdownToHtml } from '@lib/markdown'
|
import { getRepo } from '@/lib/github'
|
||||||
import { getRepo } from '@lib/github'
|
import { markdownToHtml } from '@/lib/markdown'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { Copy as CopyIcon } from '@images/components'
|
import { Copy as CopyIcon } from '@/images/components'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
text: string
|
text: string
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import Copy from '@components/Copy.astro'
|
import Copy from '@/components/Copy.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
address: string
|
address: string
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string
|
title: string
|
||||||
value: string
|
value: string
|
||||||
icon: any
|
icon: React.FunctionComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, value, icon } = Astro.props
|
const { title, value, icon } = Astro.props
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { describe, it } from 'vitest'
|
import { describe, it } from 'vitest'
|
||||||
import { render, screen, fireEvent } from '@testing-library/react'
|
|
||||||
import ExifMap from './ExifMap'
|
import ExifMap from './ExifMap'
|
||||||
|
|
||||||
describe('ExifMap', () => {
|
describe('ExifMap', () => {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { type ReactElement, useState, useEffect } from 'react'
|
import type { Gps } from '@/lib/exif'
|
||||||
import { Map, Marker } from 'pigeon-maps'
|
import { Marker, Map as PigeonMap } from 'pigeon-maps'
|
||||||
|
import { type ReactElement, useEffect, useState } from 'react'
|
||||||
|
|
||||||
const mapbox =
|
const mapbox =
|
||||||
(mapboxId: string) => (x: string, y: string, z: string, dpr: number) =>
|
(mapboxId: string) =>
|
||||||
|
(x: number, y: number, z: number, dpr: number | undefined) =>
|
||||||
`https://api.mapbox.com/styles/v1/mapbox/${mapboxId}/tiles/256/${z}/${x}/${y}${
|
`https://api.mapbox.com/styles/v1/mapbox/${mapboxId}/tiles/256/${z}/${x}/${y}${
|
||||||
dpr >= 2 ? '@2x' : ''
|
dpr && dpr >= 2 ? '@2x' : ''
|
||||||
}?access_token=${import.meta.env.PUBLIC_MAPBOX_ACCESS_TOKEN}`
|
}?access_token=${import.meta.env.PUBLIC_MAPBOX_ACCESS_TOKEN}`
|
||||||
|
|
||||||
const providers = {
|
const providers = {
|
||||||
@ -14,11 +16,7 @@ const providers = {
|
|||||||
|
|
||||||
type Theme = 'light' | 'dark'
|
type Theme = 'light' | 'dark'
|
||||||
|
|
||||||
export default function ExifMap({
|
export default function ExifMap({ gps }: { gps: Gps }): ReactElement {
|
||||||
gps
|
|
||||||
}: {
|
|
||||||
gps: { latitude: number; longitude: number }
|
|
||||||
}): ReactElement {
|
|
||||||
const theme = document?.documentElement?.getAttribute('data-theme') as Theme
|
const theme = document?.documentElement?.getAttribute('data-theme') as Theme
|
||||||
|
|
||||||
const [zoom, setZoom] = useState(12)
|
const [zoom, setZoom] = useState(12)
|
||||||
@ -29,6 +27,7 @@ export default function ExifMap({
|
|||||||
setProvider(() => providers[theme])
|
setProvider(() => providers[theme])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: handleThemeChange not needed in deps
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!window) return
|
if (!window) return
|
||||||
|
|
||||||
@ -41,17 +40,17 @@ export default function ExifMap({
|
|||||||
const { latitude, longitude } = gps
|
const { latitude, longitude } = gps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Map
|
<PigeonMap
|
||||||
center={[latitude, longitude]}
|
center={[latitude, longitude]}
|
||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
height={220}
|
height={220}
|
||||||
dprs={[1, 2]}
|
dprs={[1, 2]}
|
||||||
attribution={false}
|
attribution={false}
|
||||||
provider={provider as any}
|
provider={provider}
|
||||||
metaWheelZoom={true}
|
metaWheelZoom={true}
|
||||||
metaWheelZoomWarning={'META+wheel to zoom'}
|
metaWheelZoomWarning={'META+wheel to zoom'}
|
||||||
>
|
>
|
||||||
<Marker anchor={[latitude, longitude]} payload={1} onClick={zoomIn} />
|
<Marker anchor={[latitude, longitude]} payload={1} onClick={zoomIn} />
|
||||||
</Map>
|
</PigeonMap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
import type { Exif } from '@lib/exif'
|
|
||||||
import {
|
import {
|
||||||
|
Aperture,
|
||||||
Camera,
|
Camera,
|
||||||
Crosshair,
|
Crosshair,
|
||||||
Aperture,
|
Maximize,
|
||||||
Stopwatch,
|
Stopwatch,
|
||||||
Sun,
|
Sun
|
||||||
Maximize
|
} from '@/images/components'
|
||||||
} from '@images/components'
|
import type { Exif } from '@/lib/exif'
|
||||||
import ExifData from './ExifData.astro'
|
import ExifData from './ExifData.astro'
|
||||||
import styles from './index.module.css'
|
|
||||||
import ExifMap from './ExifMap.tsx'
|
import ExifMap from './ExifMap.tsx'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
exif: Exif
|
exif: Exif
|
||||||
|
@ -47,8 +47,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.map {
|
.map {
|
||||||
composes: breakout from '@layouts/Base/index.module.css';
|
composes: breakout from '@/layouts/Base/index.module.css';
|
||||||
composes: frame from '@components/Picture/index.module.css';
|
composes: frame from '@/components/Picture/index.module.css';
|
||||||
height: 220px;
|
height: 220px;
|
||||||
margin-top: calc(var(--spacer) * 2);
|
margin-top: calc(var(--spacer) * 2);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
import { Github, Jsonfeed, Mastodon, Rss } from '@/images/components'
|
||||||
import styles from './Networks.module.css'
|
import styles from './Networks.module.css'
|
||||||
import { Rss, Mastodon, Github, Jsonfeed } from '@images/components'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
links: string[]
|
links: string[]
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
import Networks from './Networks.astro'
|
import avatar from '@/images/avatar.jpg'
|
||||||
import Location from '../Location'
|
|
||||||
import avatar from '@images/avatar.jpg'
|
|
||||||
import config from '@config/blog.config'
|
import config from '@config/blog.config'
|
||||||
|
import Location from '../Location'
|
||||||
|
import Networks from './Networks.astro'
|
||||||
import styles from './Vcard.module.css'
|
import styles from './Vcard.module.css'
|
||||||
|
|
||||||
const { author, rss, jsonfeed } = config
|
const { author, rss, jsonfeed } = config
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.avatar {
|
.avatar {
|
||||||
composes: frame from '@components/Picture/index.module.css';
|
composes: frame from '@/components/Picture/index.module.css';
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
border-radius: 50% !important;
|
border-radius: 50% !important;
|
||||||
margin-bottom: calc(var(--spacer) / 3);
|
margin-bottom: calc(var(--spacer) / 3);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Github, Bitcoin } from '@images/components'
|
import { Bitcoin, Github } from '@/images/components'
|
||||||
|
import config from '@config/blog.config'
|
||||||
import Vcard from './Vcard.astro'
|
import Vcard from './Vcard.astro'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import config from '@config/blog.config'
|
|
||||||
|
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
const { name, url, github } = config.author
|
const { name, url, github } = config.author
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import Menu from '@components/Menu/index.astro'
|
import Menu from '@/components/Menu/index.astro'
|
||||||
import Search from '@features/Search/index.astro'
|
import ThemeSwitch from '@/components/ThemeSwitch/index.astro'
|
||||||
import ThemeSwitch from '@components/ThemeSwitch/index.astro'
|
import Search from '@/features/Search/index.astro'
|
||||||
import { Logo } from '@images/components'
|
import { Logo } from '@/images/components'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
.title {
|
.title {
|
||||||
display: block;
|
display: block;
|
||||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||||
font: 0/0 a;
|
font: 0 / 0 a;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type InputHTMLAttributes, type ReactElement } from 'react'
|
import type { InputHTMLAttributes, ReactElement } from 'react'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export default function Input({
|
export default function Input({
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import PostTitle from '@layouts/Post/Title.astro'
|
|
||||||
import styles from './index.module.css'
|
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import LinkActions from '@layouts/Post/LinkActions.astro'
|
import LinkActions from '@/layouts/Post/LinkActions.astro'
|
||||||
|
import PostTitle from '@/layouts/Post/Title.astro'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: CollectionEntry<'articles' | 'links'>
|
post: CollectionEntry<'articles' | 'links'>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { Icon as LoaderIcon } from '@/images/components/react/Loader'
|
||||||
import styles from './Loader.module.css'
|
import styles from './Loader.module.css'
|
||||||
import { Icon as LoaderIcon } from '@images/components/react/Loader'
|
|
||||||
|
|
||||||
export function Loader() {
|
export function Loader() {
|
||||||
// TODO: fix React props for generated SVG components for class/className
|
// TODO: fix React props for generated SVG components for class/className
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import {
|
|
||||||
describe,
|
|
||||||
it,
|
|
||||||
expect,
|
|
||||||
beforeAll,
|
|
||||||
afterAll,
|
|
||||||
vi,
|
|
||||||
type MockInstance
|
|
||||||
} from 'vitest'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
|
||||||
import * as nanostores from '@nanostores/react'
|
import * as nanostores from '@nanostores/react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import {
|
||||||
|
type MockInstance,
|
||||||
|
afterAll,
|
||||||
|
beforeAll,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
it,
|
||||||
|
vi
|
||||||
|
} from 'vitest'
|
||||||
import Location from '.'
|
import Location from '.'
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { $location } from '@/stores/location'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $location } from '@stores/location'
|
|
||||||
import { formatDistanceToNowStrict } from 'date-fns'
|
import { formatDistanceToNowStrict } from 'date-fns'
|
||||||
import { LocationItem } from './LocationItem'
|
import { LocationItem } from './LocationItem'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import Hamburger from '@components/Hamburger.astro'
|
import Hamburger from '@/components/Hamburger.astro'
|
||||||
import config from '@config/blog.config'
|
import config from '@config/blog.config'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { ChevronRight } from '@images/components'
|
import { ChevronRight } from '@/images/components'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
href: string
|
href: string
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
import { ChevronLeft, ChevronRight } from '@/images/components'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { ChevronLeft, ChevronRight } from '@images/components'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
prevPagePath?: string
|
prevPagePath?: string
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import Picture from '@components/Picture/index.astro'
|
import Picture from '@/components/Picture/index.astro'
|
||||||
|
|
||||||
type Props = { post: CollectionEntry<'photos'> }
|
type Props = { post: CollectionEntry<'photos'> }
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getImage, type ImgAttributes } from 'astro:assets'
|
import { type ImgAttributes, getImage } from 'astro:assets'
|
||||||
import styles from './index.module.css'
|
|
||||||
import type { ImageMetadata, ImageOutputFormat } from 'astro'
|
import type { ImageMetadata, ImageOutputFormat } from 'astro'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = ImgAttributes & {
|
type Props = ImgAttributes & {
|
||||||
src: ImageMetadata
|
src: ImageMetadata
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import PostTitle from '@layouts/Post/Title.astro'
|
|
||||||
import styles from './index.module.css'
|
|
||||||
import Picture from '@components/Picture/index.astro'
|
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
|
import Picture from '@/components/Picture/index.astro'
|
||||||
|
import PostTitle from '@/layouts/Post/Title.astro'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: CollectionEntry<'articles' | 'links'>
|
post: CollectionEntry<'articles' | 'links'>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
composes: frame from '@components/Picture/index.module.css';
|
composes: frame from '@/components/Picture/index.module.css';
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 95px;
|
min-height: 95px;
|
||||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAACaADAAQAAAABAAAACQAAAAAvQpmhAAAAHElEQVQYGWNgoBL4T8gcggoIGcBA0ASCCmhsBQBhFwX7u70C8QAAAABJRU5ErkJggg==');
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAACaADAAQAAAABAAAACQAAAAAvQpmhAAAAHElEQVQYGWNgoBL4T8gcggoIGcBA0ASCCmhsBQBhFwX7u70C8QAAAABJRU5ErkJggg==');
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
|
import PhotoTeaser from '@/components/PhotoTeaser.astro'
|
||||||
|
import PostTeaser from '@/components/PostTeaser/index.astro'
|
||||||
|
import { getAllPostsForSearch } from '@/lib/astro'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import PhotoTeaser from '@components/PhotoTeaser.astro'
|
|
||||||
import PostTeaser from '@components/PostTeaser/index.astro'
|
|
||||||
import { getAllPostsForSearch } from '@lib/astro'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: CollectionEntry<'articles' | 'photos' | 'links'>
|
post: CollectionEntry<'articles' | 'photos' | 'links'>
|
||||||
|
@ -3,7 +3,7 @@ type Props = {
|
|||||||
name: string
|
name: string
|
||||||
url: string
|
url: string
|
||||||
count?: number
|
count?: number
|
||||||
style?: any
|
style?: string | astroHTML.JSX.CSSProperties | null | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, url, count, style } = Astro.props
|
const { name, url, count, style } = Astro.props
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { Sun, Moon } from '@images/components'
|
import { Moon, Sun } from '@/images/components'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { test, expect, beforeAll } from 'vitest'
|
import { beforeAll, expect, test } from 'vitest'
|
||||||
|
|
||||||
function resetDocument() {
|
function resetDocument() {
|
||||||
globalThis.localStorage = {
|
globalThis.localStorage = {
|
||||||
getItem: () => undefined,
|
getItem: () => null,
|
||||||
setItem: () => {}
|
setItem: () => {}
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
@ -63,11 +63,14 @@ test('data-theme attribute is set based on system preference', async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('data-theme attribute changes on system preference change', async () => {
|
test('data-theme attribute changes on system preference change', async () => {
|
||||||
let changeCallback: any = () => {}
|
let changeCallback: ({ matches }: { matches: boolean }) => void = () => {}
|
||||||
globalThis.window.matchMedia = () =>
|
globalThis.window.matchMedia = () =>
|
||||||
({
|
({
|
||||||
matches: false,
|
matches: false,
|
||||||
addEventListener: (_: any, callback: any) => {
|
addEventListener: (
|
||||||
|
_: any,
|
||||||
|
callback: ({ matches }: { matches: boolean }) => void
|
||||||
|
) => {
|
||||||
changeCallback = callback
|
changeCallback = callback
|
||||||
}
|
}
|
||||||
}) as any
|
}) as any
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { type ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import styles from './Empty.module.css'
|
|
||||||
import type { Post } from '../Search'
|
import type { Post } from '../Search'
|
||||||
|
import styles from './Empty.module.css'
|
||||||
|
|
||||||
const SearchResultsEmpty = ({
|
const SearchResultsEmpty = ({
|
||||||
query,
|
query,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
composes: content from '@layouts/Base/index.module.css';
|
composes: content from '@/layouts/Base/index.module.css';
|
||||||
padding: var(--spacer) calc(var(--spacer) / 2);
|
padding: var(--spacer) calc(var(--spacer) / 2);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -19,11 +19,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.post {
|
.post {
|
||||||
composes: post from '@components/PostTeaser/index.module.css';
|
composes: post from '@/components/PostTeaser/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
composes: title from '@components/PostTeaser/index.module.css';
|
composes: title from '@/components/PostTeaser/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.results li {
|
.results li {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import styles from './index.module.css'
|
|
||||||
import ResultsEmpty from './Empty'
|
|
||||||
import type { Post } from '../Search'
|
import type { Post } from '../Search'
|
||||||
|
import ResultsEmpty from './Empty'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
function SearchResultsPure({
|
function SearchResultsPure({
|
||||||
query,
|
query,
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
import { test, expect, vi, afterEach, beforeEach } from 'vitest'
|
import { isSearchOpen } from '@/stores/search'
|
||||||
import { render, fireEvent, waitFor, screen, act } from '@testing-library/react'
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import { isSearchOpen } from '@stores/search'
|
import {
|
||||||
|
type MockInstance,
|
||||||
|
afterEach,
|
||||||
|
beforeEach,
|
||||||
|
expect,
|
||||||
|
test,
|
||||||
|
vi
|
||||||
|
} from 'vitest'
|
||||||
import Search from './Search'
|
import Search from './Search'
|
||||||
|
|
||||||
let portalRoot: HTMLDivElement
|
let portalRoot: HTMLDivElement
|
||||||
let unsubscribe: () => void
|
let unsubscribe: () => void
|
||||||
let fetchSpy: any
|
let fetchSpy: MockInstance<GlobalFetch['fetch']>
|
||||||
let originalFetch: any
|
let originalFetch: GlobalFetch['fetch']
|
||||||
let storeState = false
|
let storeState = false
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -34,7 +41,7 @@ beforeEach(() => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
portalRoot.remove()
|
portalRoot.remove()
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
globalThis.fetch = originalFetch
|
;(globalThis.fetch as GlobalFetch['fetch']) = originalFetch
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Search component', async () => {
|
test('Search component', async () => {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { type ReactElement, useEffect, useState } from 'react'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import Fuse from 'fuse.js'
|
import Input from '@/components/Input'
|
||||||
|
import { isSearchOpen } from '@/stores/search'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { isSearchOpen } from '@stores/search'
|
import Fuse from 'fuse.js'
|
||||||
|
import { type ReactElement, useEffect, useState } from 'react'
|
||||||
import SearchResults from './Results'
|
import SearchResults from './Results'
|
||||||
import styles from './Search.module.css'
|
import styles from './Search.module.css'
|
||||||
import type { CollectionEntry } from 'astro:content'
|
|
||||||
import Input from '@components/Input'
|
|
||||||
|
|
||||||
export type Post = CollectionEntry<'articles' | 'links' | 'photos'>
|
export type Post = CollectionEntry<'articles' | 'links' | 'photos'>
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ export default function Search(): ReactElement {
|
|||||||
// Handle search and set results
|
// Handle search and set results
|
||||||
const fuse = allPosts ? new Fuse(allPosts, fuseOptions) : null
|
const fuse = allPosts ? new Fuse(allPosts, fuseOptions) : null
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: fuse not needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!query || query === '' || !fuse) {
|
if (!query || query === '' || !fuse) {
|
||||||
setResults([])
|
setResults([])
|
||||||
@ -73,6 +74,7 @@ export default function Search(): ReactElement {
|
|||||||
onClick={toggleSearch}
|
onClick={toggleSearch}
|
||||||
title="Close search"
|
title="Close search"
|
||||||
>
|
>
|
||||||
|
{/* biome-ignore lint/a11y/noSvgWithoutTitle: the button has title already */}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { Search as SearchIcon } from '@images/components'
|
import { Search as SearchIcon } from '@/images/components'
|
||||||
import Search from './Search.tsx'
|
import Search from './Search.tsx'
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import Search from './Search.tsx'
|
|||||||
<Search client:only="react" />
|
<Search client:only="react" />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { isSearchOpen } from '@stores/search'
|
import { isSearchOpen } from '@/stores/search'
|
||||||
const button = document.querySelector('#search-button')
|
const button = document.querySelector('#search-button')
|
||||||
|
|
||||||
isSearchOpen.subscribe((value) =>
|
isSearchOpen.subscribe((value) =>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useEffect, type ReactElement, useState } from 'react'
|
import { $amount, $selectedToken } from '@/features/Web3/stores'
|
||||||
import styles from './Conversion.module.css'
|
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $selectedToken } from '@features/Web3/stores/selectedToken'
|
import { type ReactElement, useEffect, useState } from 'react'
|
||||||
import { $amount } from '@features/Web3/stores'
|
import styles from './Conversion.module.css'
|
||||||
|
|
||||||
export function Conversion(): ReactElement | null {
|
export function Conversion(): ReactElement | null {
|
||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from 'vitest'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { render, fireEvent, screen } from '@testing-library/react'
|
import { expect, test } from 'vitest'
|
||||||
import { Web3Form } from './Form'
|
import { Web3Form } from './Form'
|
||||||
|
|
||||||
test('Web3Donation component', async () => {
|
test('Web3Donation component', async () => {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
|
import {
|
||||||
|
$amount,
|
||||||
|
$isInitSend,
|
||||||
|
$selectedToken,
|
||||||
|
$setAmount
|
||||||
|
} from '@/features/Web3/stores'
|
||||||
|
import siteConfig from '@config/blog.config'
|
||||||
|
import { useStore } from '@nanostores/react'
|
||||||
import { type ReactElement, useEffect, useState } from 'react'
|
import { type ReactElement, useEffect, useState } from 'react'
|
||||||
import { useAccount } from 'wagmi'
|
import { useAccount } from 'wagmi'
|
||||||
import { InputGroup } from '../Input'
|
import { InputGroup } from '../Input'
|
||||||
import styles from './Form.module.css'
|
|
||||||
import { useStore } from '@nanostores/react'
|
|
||||||
import {
|
|
||||||
$selectedToken,
|
|
||||||
$isInitSend,
|
|
||||||
$amount,
|
|
||||||
$setAmount
|
|
||||||
} from '@features/Web3/stores'
|
|
||||||
import siteConfig from '@config/blog.config'
|
|
||||||
import { Send } from '../Send'
|
|
||||||
import { RainbowKit } from '../RainbowKit/RainbowKit'
|
import { RainbowKit } from '../RainbowKit/RainbowKit'
|
||||||
|
import { Send } from '../Send'
|
||||||
|
import styles from './Form.module.css'
|
||||||
|
|
||||||
export function Web3Form(): ReactElement {
|
export function Web3Form(): ReactElement {
|
||||||
const { address: account } = useAccount()
|
const { address: account } = useAccount()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { InputGroup } from '.'
|
import { InputGroup } from '.'
|
||||||
|
|
||||||
describe('InputGroup', () => {
|
describe('InputGroup', () => {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { useState, type ReactElement } from 'react'
|
import Input from '@/components/Input'
|
||||||
import Input from '@components/Input'
|
|
||||||
import { Conversion } from '../Conversion'
|
|
||||||
import styles from './InputGroup.module.css'
|
|
||||||
import { TokenSelect } from '../TokenSelect'
|
|
||||||
import {
|
import {
|
||||||
$amount,
|
$amount,
|
||||||
$setAmount,
|
|
||||||
$isInitSend,
|
$isInitSend,
|
||||||
$selectedToken
|
$selectedToken,
|
||||||
} from '@features/Web3/stores'
|
$setAmount
|
||||||
|
} from '@/features/Web3/stores'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
import { type ReactElement, useState } from 'react'
|
||||||
|
import { Conversion } from '../Conversion'
|
||||||
|
import { TokenSelect } from '../TokenSelect'
|
||||||
|
import styles from './InputGroup.module.css'
|
||||||
|
|
||||||
export function InputGroup({
|
export function InputGroup({
|
||||||
isDisabled,
|
isDisabled,
|
||||||
@ -50,6 +50,7 @@ export function InputGroup({
|
|||||||
className={styles.inputInput}
|
className={styles.inputInput}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className={`${styles.submit} btn btn-primary`}
|
className={`${styles.submit} btn btn-primary`}
|
||||||
disabled={
|
disabled={
|
||||||
isDisabled ||
|
isDisabled ||
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import { truncateAddress } from '@/features/Web3/lib/truncateAddress'
|
||||||
|
import { $amount, $selectedToken } from '@/features/Web3/stores'
|
||||||
|
import { useStore } from '@nanostores/react'
|
||||||
import { useAccount, useChains, useEnsName } from 'wagmi'
|
import { useAccount, useChains, useEnsName } from 'wagmi'
|
||||||
import styles from './Data.module.css'
|
import styles from './Data.module.css'
|
||||||
import { useStore } from '@nanostores/react'
|
|
||||||
import { $amount, $selectedToken } from '@features/Web3/stores'
|
|
||||||
import { truncateAddress } from '@features/Web3/lib/truncateAddress'
|
|
||||||
|
|
||||||
export function Data({
|
export function Data({
|
||||||
to,
|
to,
|
||||||
@ -61,7 +61,7 @@ export function Data({
|
|||||||
<td className={styles.label}>on</td>
|
<td className={styles.label}>on</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="TokenLogo">
|
<div className="TokenLogo">
|
||||||
<img src={selectedToken?.chainLogo || ''} />
|
<img src={selectedToken?.chainLogo || ''} alt="Chain" />
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.network}>{networkName}</span>
|
<span className={styles.network}>{networkName}</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { describe, test, expect } from 'vitest'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
import { Preview } from './Preview'
|
import { Preview } from './Preview'
|
||||||
|
|
||||||
describe('Preview component', () => {
|
describe('Preview component', () => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Loader } from '@components/Loader'
|
import { Loader } from '@/components/Loader'
|
||||||
import { useSend } from '@features/Web3/hooks/useSend'
|
import { useSend } from '@/features/Web3/hooks/useSend'
|
||||||
import { $isInitSend } from '@features/Web3/stores'
|
import { $isInitSend } from '@/features/Web3/stores'
|
||||||
|
import siteConfig from '@config/blog.config'
|
||||||
import { useEnsAddress, useEnsName } from 'wagmi'
|
import { useEnsAddress, useEnsName } from 'wagmi'
|
||||||
import { Data } from './Data'
|
import { Data } from './Data'
|
||||||
import siteConfig from '@config/blog.config'
|
|
||||||
import styles from './Preview.module.css'
|
import styles from './Preview.module.css'
|
||||||
|
|
||||||
export function Preview() {
|
export function Preview() {
|
||||||
@ -26,6 +26,7 @@ export function Preview() {
|
|||||||
|
|
||||||
<footer className={styles.actions}>
|
<footer className={styles.actions}>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={async (e) => {
|
onClick={async (e) => {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
await handleSend()
|
await handleSend()
|
||||||
@ -36,6 +37,7 @@ export function Preview() {
|
|||||||
{isLoading ? <Loader /> : 'Make it rain'}
|
{isLoading ? <Loader /> : 'Make it rain'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => $isInitSend.set(false)}
|
onClick={() => $isInitSend.set(false)}
|
||||||
className="link"
|
className="link"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { $txHash } from '@/features/Web3/stores'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $txHash } from '@features/Web3/stores'
|
|
||||||
import { Success } from '../Success'
|
|
||||||
import { Preview } from '../Preview'
|
import { Preview } from '../Preview'
|
||||||
|
import { Success } from '../Success'
|
||||||
|
|
||||||
export function Send() {
|
export function Send() {
|
||||||
const txHash = useStore($txHash)
|
const txHash = useStore($txHash)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { $txHash } from '@features/Web3/stores'
|
import { $txHash } from '@/features/Web3/stores'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
|
|
||||||
export function ExplorerLink({
|
export function ExplorerLink({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { describe, test, expect } from 'vitest'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
import { Success } from './Success'
|
import { Success } from './Success'
|
||||||
|
|
||||||
describe('Success component', () => {
|
describe('Success component', () => {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { $isInitSend } from '@features/Web3/stores'
|
import { $isInitSend } from '@/features/Web3/stores'
|
||||||
import styles from './Success.module.css'
|
|
||||||
import { useAccount } from 'wagmi'
|
import { useAccount } from 'wagmi'
|
||||||
import { ExplorerLink } from './ExplorerLink'
|
import { ExplorerLink } from './ExplorerLink'
|
||||||
|
import styles from './Success.module.css'
|
||||||
|
|
||||||
const title = `You're amazing, thanks!`
|
const title = `You're amazing, thanks!`
|
||||||
const description = `Your transaction is on its way. You can check the status on`
|
const description =
|
||||||
|
'Your transaction is on its way. You can check the status on'
|
||||||
|
|
||||||
export function Success() {
|
export function Success() {
|
||||||
const account = useAccount()
|
const account = useAccount()
|
||||||
@ -23,6 +24,7 @@ export function Success() {
|
|||||||
|
|
||||||
<footer className={styles.actions}>
|
<footer className={styles.actions}>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => $isInitSend.set(false)}
|
onClick={() => $isInitSend.set(false)}
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
>
|
>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { forwardRef, type HTMLAttributes } from 'react'
|
|
||||||
import * as Select from '@radix-ui/react-select'
|
|
||||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||||
|
import * as Select from '@radix-ui/react-select'
|
||||||
|
import { type HTMLAttributes, forwardRef } from 'react'
|
||||||
import './Token.css'
|
import './Token.css'
|
||||||
import { Icon as Check } from '@images/components/react/Check'
|
import type { GetToken } from '@/features/Web3/hooks/useFetchTokens'
|
||||||
import type { GetToken } from '@features/Web3/hooks/useFetchTokens'
|
import { Icon as Check } from '@/images/components/react/Check'
|
||||||
|
|
||||||
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
interface SelectItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
token: GetToken | undefined
|
token: GetToken | undefined
|
||||||
@ -28,7 +28,7 @@ export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
|
|||||||
|
|
||||||
// const hasBalanceAndValue =
|
// const hasBalanceAndValue =
|
||||||
// balance && parseInt(balance) !== 0 && valueInUsd >= 1
|
// balance && parseInt(balance) !== 0 && valueInUsd >= 1
|
||||||
const hasBalance = balance && parseInt(balance) !== 0
|
const hasBalance = balance && Number.parseInt(balance) !== 0
|
||||||
|
|
||||||
return hasBalance ? (
|
return hasBalance ? (
|
||||||
<Select.Item
|
<Select.Item
|
||||||
@ -46,6 +46,7 @@ export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
|
|||||||
width="32"
|
width="32"
|
||||||
height="32"
|
height="32"
|
||||||
className="TokenLogoImage"
|
className="TokenLogoImage"
|
||||||
|
alt={token?.symbol?.substring(0, 3)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
token?.symbol?.substring(0, 3)
|
token?.symbol?.substring(0, 3)
|
||||||
@ -55,6 +56,7 @@ export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
|
|||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
className="TokenChainLogo"
|
className="TokenChainLogo"
|
||||||
|
alt="Chain"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</Select.ItemText>
|
</Select.ItemText>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import * as Select from '@radix-ui/react-select'
|
import * as Select from '@radix-ui/react-select'
|
||||||
import './TokenSelect.css'
|
import './TokenSelect.css'
|
||||||
import { Token } from './Token'
|
import { Loader } from '@/components/Loader'
|
||||||
import { Icon as ChevronDown } from '@images/components/react/ChevronDown'
|
import { useFetchTokens } from '@/features/Web3/hooks/useFetchTokens'
|
||||||
import { Icon as ChevronsDown } from '@images/components/react/ChevronsDown'
|
import { $selectedToken } from '@/features/Web3/stores'
|
||||||
import { Icon as ChevronsUp } from '@images/components/react/ChevronsUp'
|
import { Icon as ChevronDown } from '@/images/components/react/ChevronDown'
|
||||||
import { useFetchTokens } from '@features/Web3/hooks/useFetchTokens'
|
import { Icon as ChevronsDown } from '@/images/components/react/ChevronsDown'
|
||||||
|
import { Icon as ChevronsUp } from '@/images/components/react/ChevronsUp'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $selectedToken } from '@features/Web3/stores'
|
|
||||||
import { Loader } from '@components/Loader'
|
|
||||||
import { useAccount } from 'wagmi'
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { useAccount } from 'wagmi'
|
||||||
|
import { Token } from './Token'
|
||||||
|
|
||||||
export function TokenSelect() {
|
export function TokenSelect() {
|
||||||
const { address } = useAccount()
|
const { address } = useAccount()
|
||||||
@ -29,6 +29,7 @@ export function TokenSelect() {
|
|||||||
|
|
||||||
// Auto-select native token
|
// Auto-select native token
|
||||||
// when no selection was made yet
|
// when no selection was made yet
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedToken?.address || !tokens || !tokens?.length) return
|
if (selectedToken?.address || !tokens || !tokens?.length) return
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import '../lib/polyfills'
|
|
||||||
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
|
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
|
||||||
import { WagmiProvider } from 'wagmi'
|
|
||||||
import { wagmiConfig, theme } from '../lib/rainbowkit'
|
|
||||||
import { Web3Form } from './Form'
|
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
import { WagmiProvider } from 'wagmi'
|
||||||
|
import { theme, wagmiConfig } from '../lib/rainbowkit'
|
||||||
|
import { Web3Form } from './Form'
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect, vi } from 'vitest'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { expect, test, vi } from 'vitest'
|
||||||
import { useFetchTokens } from './useFetchTokens'
|
import { useFetchTokens } from './useFetchTokens'
|
||||||
|
|
||||||
test('useFetchTokens does not fetch anything when no chain or address are present', async () => {
|
test('useFetchTokens does not fetch anything when no chain or address are present', async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from 'vitest'
|
import { expect, test } from 'vitest'
|
||||||
import { isUnhelpfulErrorMessage } from './isUnhelpfulErrorMessage'
|
import { isUnhelpfulErrorMessage } from './isUnhelpfulErrorMessage'
|
||||||
|
|
||||||
test('returns true for unhelpful error messages', () => {
|
test('returns true for unhelpful error messages', () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { test, expect, vi } from 'vitest'
|
import { expect, test, vi } from 'vitest'
|
||||||
import { send } from './send'
|
|
||||||
import * as wagmiActionsMock from '../../../../../test/__mocks__/wagmi/actions'
|
import * as wagmiActionsMock from '../../../../../test/__mocks__/wagmi/actions'
|
||||||
|
import { send } from './send'
|
||||||
|
|
||||||
test('with undefined params', async () => {
|
test('with undefined params', async () => {
|
||||||
const result = await send(
|
const result = await send(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import { parseEther, parseUnits } from 'viem'
|
||||||
|
import type { UseConfigReturnType } from 'wagmi'
|
||||||
import { sendTransaction, writeContract } from 'wagmi/actions'
|
import { sendTransaction, writeContract } from 'wagmi/actions'
|
||||||
import type { GetToken } from '../useFetchTokens'
|
import type { GetToken } from '../useFetchTokens'
|
||||||
import { parseEther, parseUnits } from 'viem'
|
|
||||||
import { abiErc20Transfer } from './abiErc20Transfer'
|
import { abiErc20Transfer } from './abiErc20Transfer'
|
||||||
import type { UseConfigReturnType } from 'wagmi'
|
|
||||||
|
|
||||||
export async function send(
|
export async function send(
|
||||||
config: UseConfigReturnType,
|
config: UseConfigReturnType,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { $txHash, $selectedToken, $amount } from '@features/Web3/stores'
|
import { $amount, $selectedToken, $txHash } from '@/features/Web3/stores'
|
||||||
|
import siteConfig from '@config/blog.config'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { send } from './send'
|
|
||||||
import { isUnhelpfulErrorMessage } from './isUnhelpfulErrorMessage'
|
|
||||||
import { useAccount, useConfig, useEnsAddress, useSwitchChain } from 'wagmi'
|
import { useAccount, useConfig, useEnsAddress, useSwitchChain } from 'wagmi'
|
||||||
import siteConfig from '@config/blog.config'
|
import { isUnhelpfulErrorMessage } from './isUnhelpfulErrorMessage'
|
||||||
|
import { send } from './send'
|
||||||
|
|
||||||
export function useSend() {
|
export function useSend() {
|
||||||
const selectedToken = useStore($selectedToken)
|
const selectedToken = useStore($selectedToken)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from 'vitest'
|
import { expect, test } from 'vitest'
|
||||||
import { normalizeAmount } from './normalizeAmount'
|
import { normalizeAmount } from './normalizeAmount'
|
||||||
|
|
||||||
test('normalizeAmount replaces comma with a period', () => {
|
test('normalizeAmount replaces comma with a period', () => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export function normalizeAmount(amount: string): string {
|
export function normalizeAmount(amount: string): string {
|
||||||
// Replace comma used as a decimal separator with a period
|
// Replace comma used as a decimal separator with a period
|
||||||
amount = amount.replace(',', '.')
|
let newAmount = amount.replace(',', '.')
|
||||||
|
|
||||||
// Remove any non-digit or non-decimal characters
|
// Remove any non-digit or non-decimal characters
|
||||||
amount = amount.replace(/[^\d.]/g, '')
|
newAmount = newAmount.replace(/[^\d.]/g, '')
|
||||||
|
|
||||||
return amount
|
return newAmount
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { Buffer } from 'buffer'
|
|
||||||
|
|
||||||
window.global = window.global ?? window
|
|
||||||
window.Buffer = window.Buffer ?? Buffer
|
|
||||||
window.process = window.process ?? { env: {} } // Minimal process polyfill
|
|
||||||
|
|
||||||
export {}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { type Theme, getDefaultConfig } from '@rainbow-me/rainbowkit'
|
import { type Theme, getDefaultConfig } from '@rainbow-me/rainbowkit'
|
||||||
import { mainnet, polygon, base, optimism, arbitrum, zora } from 'wagmi/chains'
|
import { arbitrum, base, mainnet, optimism, polygon, zora } from 'wagmi/chains'
|
||||||
|
|
||||||
const PUBLIC_WALLETCONNECT_ID = import.meta.env.PUBLIC_WALLETCONNECT_ID
|
const PUBLIC_WALLETCONNECT_ID = import.meta.env.PUBLIC_WALLETCONNECT_ID
|
||||||
const isProduction = import.meta.env.PROD
|
const isProduction = import.meta.env.PROD
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from 'vitest'
|
import { expect, test } from 'vitest'
|
||||||
import { truncateAddress } from './truncateAddress'
|
import { truncateAddress } from './truncateAddress'
|
||||||
|
|
||||||
test('truncateAddress', () => {
|
test('truncateAddress', () => {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import LayoutBase from '@layouts/Base/index.astro'
|
|
||||||
import type { Page } from 'astro'
|
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import PostTeaser from '@components/PostTeaser/index.astro'
|
import LinkTeaser from '@/components/LinkTeaser/index.astro'
|
||||||
import Pagination from '@components/Pagination/index.astro'
|
import Pagination from '@/components/Pagination/index.astro'
|
||||||
import PhotoTeaser from '@components/PhotoTeaser.astro'
|
import PhotoTeaser from '@/components/PhotoTeaser.astro'
|
||||||
import LinkTeaser from '@components/LinkTeaser/index.astro'
|
import PostTeaser from '@/components/PostTeaser/index.astro'
|
||||||
|
import LayoutBase from '@/layouts/Base/index.astro'
|
||||||
|
import type { Page } from 'astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
page: Page<CollectionEntry<'articles' | 'links' | 'photos'>>
|
page: Page<CollectionEntry<'articles' | 'links' | 'photos'>>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import { getImage } from 'astro:assets'
|
import { getImage } from 'astro:assets'
|
||||||
import SchemaOrg, { type Props as SchemaProps } from './SchemaOrg.astro'
|
import faviconSrc from '@/images/favicon.png'
|
||||||
|
import faviconSvgSrc from '@/images/favicon.svg'
|
||||||
|
import { getUmamiConfig } from '@/lib/umami'
|
||||||
import config from '@config/blog.config'
|
import config from '@config/blog.config'
|
||||||
import { getUmamiConfig } from '@lib/umami'
|
import SchemaOrg, { type Props as SchemaProps } from './SchemaOrg.astro'
|
||||||
import faviconSrc from '@images/favicon.png'
|
import type { Props as IndexProps } from './index.astro'
|
||||||
import faviconSvgSrc from '@images/favicon.svg'
|
|
||||||
import { type Props as IndexProps } from './index.astro'
|
|
||||||
|
|
||||||
type Props = IndexProps
|
type Props = IndexProps
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ const imageFinal = `${Astro.site?.origin}${
|
|||||||
: faviconSrc.src
|
: faviconSrc.src
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const imageFinalAlt = image ? `Teaser image for ${title}` : `Logo`
|
const imageFinalAlt = image ? `Teaser image for ${title}` : 'Logo'
|
||||||
|
|
||||||
const schema: SchemaProps = {
|
const schema: SchemaProps = {
|
||||||
title: titleFinal,
|
title: titleFinal,
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
import '../../styles/global.css'
|
import '../../styles/global.css'
|
||||||
import '../../styles/imports.css'
|
import '../../styles/imports.css'
|
||||||
|
|
||||||
import type { ImageMetadata } from 'astro'
|
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import Header from '@components/Header/index.astro'
|
import Footer from '@/components/Footer/index.astro'
|
||||||
import Footer from '@components/Footer/index.astro'
|
import Header from '@/components/Header/index.astro'
|
||||||
import styles from './index.module.css'
|
import type { ImageMetadata } from 'astro'
|
||||||
import Head from './Head.astro'
|
import Head from './Head.astro'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export type Props = CollectionEntry<'articles' | 'links' | 'photos'>['data'] & {
|
export type Props = CollectionEntry<'articles' | 'links' | 'photos'>['data'] & {
|
||||||
pageTitle?: string
|
pageTitle?: string
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
background-color: var(--body-background-color);
|
background-color: var(--body-background-color);
|
||||||
padding-bottom: calc(var(--spacer) * 2);
|
padding-bottom: calc(var(--spacer) * 2);
|
||||||
border-top: 1px solid rgba(255 255 255 / 85%);
|
border-top: 1px solid rgba(255 255 255 / 85%);
|
||||||
box-shadow:
|
box-shadow: 0 1px 10px rgba(1 85 101 / 10%), 0 -1px 4px rgba(1 85 101 / 5%);
|
||||||
0 1px 10px rgba(1 85 101 / 10%),
|
|
||||||
0 -1px 4px rgba(1 85 101 / 5%);
|
|
||||||
|
|
||||||
/* animates the page menu opening/closing */
|
/* animates the page menu opening/closing */
|
||||||
transition: 0.2s ease-out;
|
transition: 0.2s ease-out;
|
||||||
@ -32,9 +30,7 @@
|
|||||||
|
|
||||||
[data-theme='dark'] .document {
|
[data-theme='dark'] .document {
|
||||||
border-top-color: rgba(255 255 255 / 5%);
|
border-top-color: rgba(255 255 255 / 5%);
|
||||||
box-shadow:
|
box-shadow: 0 1px 8px rgba(0 7 8 / 30%), 0 -1px 4px rgba(0 21 25 / 80%);
|
||||||
0 1px 8px rgba(0 7 8 / 30%),
|
|
||||||
0 -1px 4px rgba(0 21 25 / 80%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 40rem) and (min-height: 500px) {
|
@media (min-width: 40rem) and (min-height: 500px) {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string
|
title: string
|
||||||
text: string
|
text: string
|
||||||
icon: any
|
icon: React.FunctionComponent
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
import { Bitcoin, Github, Mastodon } from '@/images/components'
|
||||||
import config from '@config/blog.config'
|
import config from '@config/blog.config'
|
||||||
import styles from './Actions.module.css'
|
|
||||||
import Action from './Action.astro'
|
import Action from './Action.astro'
|
||||||
import { Mastodon, Bitcoin, Github } from '@images/components'
|
import styles from './Actions.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
githubLink: string
|
githubLink: string
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import Time from '@components/Time.astro'
|
import Time from '@/components/Time.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
date: Date | undefined
|
date: Date | undefined
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
import { ExternalLink, Link } from '@/images/components'
|
||||||
import styles from './LinkActions.module.css'
|
import styles from './LinkActions.module.css'
|
||||||
import { ExternalLink, Link } from '@images/components'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkurl?: string
|
linkurl?: string
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import { slugify } from '@lib/slugify'
|
import Tag from '@/components/Tag.astro'
|
||||||
|
import { slugify } from '@/lib/slugify'
|
||||||
import config from '@config/blog.config'
|
import config from '@config/blog.config'
|
||||||
import Tag from '@components/Tag.astro'
|
import DateComponent from './Date.astro'
|
||||||
import styles from './Meta.module.css'
|
import styles from './Meta.module.css'
|
||||||
import Date from './Date.astro'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: CollectionEntry<'articles' | 'links' | 'photos'>
|
post: CollectionEntry<'articles' | 'links' | 'photos'>
|
||||||
@ -22,7 +22,7 @@ const { date, updated, author, tags } = data
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Date date={date} updated={updated} />
|
<DateComponent date={date} updated={updated} />
|
||||||
|
|
||||||
{
|
{
|
||||||
collection === 'photos' && (
|
collection === 'photos' && (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Date from './Date.astro'
|
import { ExternalLink } from '@/images/components'
|
||||||
|
import DateComponent from './Date.astro'
|
||||||
import styles from './Title.module.css'
|
import styles from './Title.module.css'
|
||||||
import { ExternalLink } from '@images/components'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkurl?: string
|
linkurl?: string
|
||||||
@ -30,7 +30,7 @@ const linkHostname = linkurl ? new URL(linkurl).hostname : null
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h1 class={`${styles.title} ${className && className}`}>{title}</h1>
|
<h1 class={`${styles.title} ${className && className}`}>{title}</h1>
|
||||||
{date && <Date date={date} updated={updated} />}
|
{date && <DateComponent date={date} updated={updated} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import LayoutBase from '@layouts/Base/index.astro'
|
import Changelog from '@/components/Changelog/index.astro'
|
||||||
import Title from './Title.astro'
|
import Exif from '@/components/Exif/index.astro'
|
||||||
|
import Picture from '@/components/Picture/index.astro'
|
||||||
|
import RelatedPosts from '@/components/RelatedPosts/index.astro'
|
||||||
|
import Toc from '@/components/Toc.astro'
|
||||||
|
import LayoutBase from '@/layouts/Base/index.astro'
|
||||||
|
import type { Exif as ExifType } from '@/lib/exif'
|
||||||
import Actions from './Actions.astro'
|
import Actions from './Actions.astro'
|
||||||
import styles from './index.module.css'
|
|
||||||
import Meta from './Meta.astro'
|
|
||||||
import Picture from '@components/Picture/index.astro'
|
|
||||||
import Toc from '@components/Toc.astro'
|
|
||||||
import Exif from '@components/Exif/index.astro'
|
|
||||||
import type { Exif as ExifType } from '@lib/exif'
|
|
||||||
import Changelog from '@components/Changelog/index.astro'
|
|
||||||
import RelatedPosts from '@components/RelatedPosts/index.astro'
|
|
||||||
import LinkActions from './LinkActions.astro'
|
import LinkActions from './LinkActions.astro'
|
||||||
|
import Meta from './Meta.astro'
|
||||||
|
import Title from './Title.astro'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
type Props = CollectionEntry<'articles' | 'links' | 'photos'> & {
|
type Props = CollectionEntry<'articles' | 'links' | 'photos'> & {
|
||||||
lead?: string // comes in through remark plugin as html
|
lead?: string // comes in through remark plugin as html
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
.imageWrapper {
|
.imageWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
composes: breakout from '@layouts/Base/index.module.css';
|
composes: breakout from '@/layouts/Base/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { test, expect, vi, describe, beforeEach, afterEach } from 'vitest'
|
|
||||||
import * as loadAndFormatCollectionModule from './loadAndFormatCollection'
|
|
||||||
import { getAllPosts } from './getAllPosts'
|
|
||||||
import mockArticles from '@test/__fixtures__/getCollectionArticles.json'
|
import mockArticles from '@test/__fixtures__/getCollectionArticles.json'
|
||||||
import mockLinks from '@test/__fixtures__/getCollectionLinks.json'
|
import mockLinks from '@test/__fixtures__/getCollectionLinks.json'
|
||||||
import mockPhotos from '@test/__fixtures__/getCollectionPhotos.json'
|
import mockPhotos from '@test/__fixtures__/getCollectionPhotos.json'
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||||
|
import { getAllPosts } from './getAllPosts'
|
||||||
|
import * as loadAndFormatCollectionModule from './loadAndFormatCollection'
|
||||||
|
|
||||||
let loadAndFormatCollectionSpy: any
|
let loadAndFormatCollectionSpy: any
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { sortPosts } from './sortPosts'
|
|
||||||
import { loadAndFormatCollection } from './loadAndFormatCollection'
|
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
|
import { loadAndFormatCollection } from './loadAndFormatCollection'
|
||||||
|
import { sortPosts } from './sortPosts'
|
||||||
|
|
||||||
export async function getAllPosts(): Promise<
|
export async function getAllPosts(): Promise<
|
||||||
CollectionEntry<'articles' | 'links' | 'photos'>[]
|
CollectionEntry<'articles' | 'links' | 'photos'>[]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import { getAllPosts } from './getAllPosts'
|
import { getAllPosts } from './getAllPosts'
|
||||||
|
|
||||||
// helps to reduce DOM size
|
// helps to reduce DOM size
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getAllPosts } from './index'
|
|
||||||
import { slugifyAll } from '../slugify'
|
import { slugifyAll } from '../slugify'
|
||||||
|
import { getAllPosts } from './index'
|
||||||
|
|
||||||
export type AllTags = {
|
export type AllTags = {
|
||||||
name: string
|
name: string
|
||||||
@ -10,8 +10,7 @@ export async function getAllTags(): Promise<AllTags> {
|
|||||||
const allPosts = await getAllPosts()
|
const allPosts = await getAllPosts()
|
||||||
const allTagsArray = allPosts
|
const allTagsArray = allPosts
|
||||||
.filter((post) => post.data.tags)
|
.filter((post) => post.data.tags)
|
||||||
.map((post) => post.data.tags)
|
.flatMap((post) => post.data.tags) as string[]
|
||||||
.flat() as string[]
|
|
||||||
const allTagsArrayCleaned = slugifyAll(allTagsArray)
|
const allTagsArrayCleaned = slugifyAll(allTagsArray)
|
||||||
|
|
||||||
// Explicitly define the type of tagCounts
|
// Explicitly define the type of tagCounts
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user