diff --git a/budget.json b/budget.json
index 81b5ebf5..cf038eeb 100644
--- a/budget.json
+++ b/budget.json
@@ -16,7 +16,7 @@
},
{
"resourceType": "other",
- "budget": 7.1
+ "budget": 7.6
}
],
"resourceCounts": [
diff --git a/nuxt.config.ts b/nuxt.config.ts
index a1bdb089..6b390201 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -83,6 +83,14 @@ export default defineNuxtConfig({
sourcemap: false,
+ social: {
+ networks: {
+ mastodon: {
+ identifier: '@daniel@roe.dev',
+ },
+ },
+ },
+
devTo: {
enabled: !!process.env.SYNC_DEV_TO,
},
@@ -162,6 +170,7 @@ export default defineNuxtConfig({
image: {
domains: [
'avatars.githubusercontent.com',
+ 's3.nl-ams.scw.cloud',
'https://avatars.githubusercontent.com',
],
screens: {
diff --git a/package.json b/package.json
index 8f05c15b..5c33f06b 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"knitwork": "^1.0.0",
"magic-regexp": "0.7.0",
"magic-string": "^0.30.0",
+ "masto": "^5.11.4",
"mlly": "^1.4.0",
"nuxt": "3.6.3",
"nuxt-time": "^0.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2bac5f22..302870c3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -74,6 +74,9 @@ dependencies:
magic-string:
specifier: ^0.30.0
version: 0.30.1
+ masto:
+ specifier: ^5.11.4
+ version: 5.11.4
mlly:
specifier: ^1.4.0
version: 1.4.0
@@ -1307,6 +1310,18 @@ packages:
- encoding
- supports-color
+ /@mastojs/ponyfills@1.0.4:
+ resolution: {integrity: sha512-1NaIGmcU7OmyNzx0fk+cYeGTkdXlOJOSdetaC4pStVWsrhht2cdlYSAfe5NDW3FcUmcEm2vVceB9lcClN1RCxw==}
+ dependencies:
+ '@types/node': 18.16.19
+ '@types/node-fetch': 2.6.4
+ abort-controller: 3.0.0
+ form-data: 4.0.0
+ node-fetch: 2.6.12
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/@netlify/functions@1.6.0:
resolution: {integrity: sha512-6G92AlcpFrQG72XU8YH8pg94eDnq7+Q0YJhb8x4qNpdGsvuzvrfHWBmqFGp/Yshmv4wex9lpsTRZOocdrA2erQ==}
engines: {node: '>=14.0.0'}
@@ -2175,6 +2190,17 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false
+ /@types/node-fetch@2.6.4:
+ resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
+ dependencies:
+ '@types/node': 20.4.2
+ form-data: 3.0.1
+ dev: false
+
+ /@types/node@18.16.19:
+ resolution: {integrity: sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==}
+ dev: false
+
/@types/node@20.4.2:
resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
@@ -3015,6 +3041,13 @@ packages:
/abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+ /abort-controller@3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+ dependencies:
+ event-target-shim: 5.0.1
+ dev: false
+
/acorn-import-assertions@1.9.0(acorn@8.10.0):
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
peerDependencies:
@@ -3330,7 +3363,6 @@ packages:
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
- dev: true
/autoprefixer@10.4.14(postcss@8.4.25):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
@@ -3584,12 +3616,18 @@ packages:
dependencies:
function-bind: 1.1.1
get-intrinsic: 1.2.1
- dev: true
/callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ /camel-case@4.1.2:
+ resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+ dependencies:
+ pascal-case: 3.1.2
+ tslib: 2.6.0
+ dev: false
+
/camelcase-keys@6.2.2:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
engines: {node: '>=8'}
@@ -3633,6 +3671,14 @@ packages:
/caniuse-lite@1.0.30001510:
resolution: {integrity: sha512-z35lD6xjHklPNgjW4P68R30Er5OCIZE0C1bUf8IMXSh34WJYXfIy+GxIEraQXYJ2dvTU8TumjYAeLrPhpMlsuw==}
+ /capital-case@1.0.4:
+ resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.0
+ upper-case-first: 2.0.2
+ dev: false
+
/ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
dev: false
@@ -3674,6 +3720,23 @@ packages:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ /change-case@4.1.2:
+ resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
+ dependencies:
+ camel-case: 4.1.2
+ capital-case: 1.0.4
+ constant-case: 3.0.4
+ dot-case: 3.0.4
+ header-case: 2.0.4
+ no-case: 3.0.4
+ param-case: 3.0.4
+ pascal-case: 3.1.2
+ path-case: 3.0.4
+ sentence-case: 3.0.4
+ snake-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
/character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
dev: false
@@ -3853,7 +3916,6 @@ packages:
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
- dev: true
/comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@@ -3925,6 +3987,14 @@ packages:
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+ /constant-case@3.0.4:
+ resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.0
+ upper-case: 2.0.2
+ dev: false
+
/conventional-changelog-angular@5.0.13:
resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==}
engines: {node: '>=10'}
@@ -4333,7 +4403,6 @@ packages:
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
- dev: true
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
@@ -4433,6 +4502,13 @@ packages:
domelementtype: 2.3.0
domhandler: 5.0.3
+ /dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
/dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
@@ -5126,9 +5202,18 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
+ /event-target-shim@5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
+ dev: false
+
/eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+ /eventemitter3@5.0.1:
+ resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+ dev: false
+
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
@@ -5374,6 +5459,15 @@ packages:
cross-spawn: 7.0.3
signal-exit: 4.0.2
+ /form-data@3.0.1:
+ resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
+ engines: {node: '>= 6'}
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+ dev: false
+
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
@@ -5381,7 +5475,6 @@ packages:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
- dev: true
/formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
@@ -5494,7 +5587,6 @@ packages:
has: 1.0.3
has-proto: 1.0.1
has-symbols: 1.0.3
- dev: true
/get-port-please@3.0.1:
resolution: {integrity: sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==}
@@ -5776,12 +5868,10 @@ packages:
/has-proto@1.0.1:
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
engines: {node: '>= 0.4'}
- dev: true
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
- dev: true
/has-tostringtag@1.0.0:
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
@@ -5910,6 +6000,13 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
+ /header-case@2.0.4:
+ resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
+ dependencies:
+ capital-case: 1.0.4
+ tslib: 2.6.0
+ dev: false
+
/hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
@@ -6486,6 +6583,14 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ /isomorphic-ws@5.0.0(ws@8.13.0):
+ resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
+ peerDependencies:
+ ws: '*'
+ dependencies:
+ ws: 8.13.0
+ dev: false
+
/istanbul-lib-coverage@3.2.0:
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
engines: {node: '>=8'}
@@ -6882,6 +6987,12 @@ packages:
get-func-name: 2.0.0
dev: true
+ /lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+ dependencies:
+ tslib: 2.6.0
+ dev: false
+
/lru-cache@10.0.0:
resolution: {integrity: sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==}
engines: {node: 14 || >=16.14}
@@ -6998,6 +7109,22 @@ packages:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: false
+ /masto@5.11.4:
+ resolution: {integrity: sha512-sLF3SJTNZDAP57Y+8vAdd1KQTuWWxmGUrBF1R2GLPL6zij/1wXxV05+h8GZhnfg+696arkt+w6ZlKvEEfH1yvg==}
+ dependencies:
+ '@mastojs/ponyfills': 1.0.4
+ change-case: 4.1.2
+ eventemitter3: 5.0.1
+ isomorphic-ws: 5.0.0(ws@8.13.0)
+ qs: 6.11.2
+ semver: 7.5.3
+ ws: 8.13.0
+ transitivePeerDependencies:
+ - bufferutil
+ - encoding
+ - utf-8-validate
+ dev: false
+
/mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
@@ -7752,6 +7879,13 @@ packages:
- encoding
- supports-color
+ /no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.6.0
+ dev: false
+
/node-abi@3.45.0:
resolution: {integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==}
engines: {node: '>=10'}
@@ -8075,7 +8209,7 @@ packages:
scule: 1.0.0
strip-literal: 1.0.1
ufo: 1.1.2
- ultrahtml: 1.2.0
+ ultrahtml: 1.3.0
uncrypto: 0.1.3
unctx: 2.3.1
unenv: 1.5.1
@@ -8128,7 +8262,6 @@ packages:
/object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
- dev: true
/object-is@1.1.5:
resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==}
@@ -8312,6 +8445,13 @@ packages:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
dev: false
+ /param-case@3.0.4:
+ resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -8365,6 +8505,20 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
+ /pascal-case@3.1.2:
+ resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
+ /path-case@3.0.4:
+ resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -8935,6 +9089,13 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
+ /qs@6.11.2:
+ resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.4
+ dev: false
+
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -9491,6 +9652,14 @@ packages:
transitivePeerDependencies:
- supports-color
+ /sentence-case@3.0.4:
+ resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.0
+ upper-case-first: 2.0.2
+ dev: false
+
/serialize-javascript@6.0.1:
resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==}
dependencies:
@@ -9558,7 +9727,6 @@ packages:
call-bind: 1.0.2
get-intrinsic: 1.2.1
object-inspect: 1.12.3
- dev: true
/siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -9671,6 +9839,13 @@ packages:
/smob@1.4.0:
resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}
+ /snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.0
+ dev: false
+
/socket.io-client@4.7.1:
resolution: {integrity: sha512-Qk3Xj8ekbnzKu3faejo4wk2MzXA029XppiXtTF/PkbTg+fcwaTw1PlDrTrrrU4mKoYC4dvlApOnSeyLCKwek2w==}
engines: {node: '>=10.0.0'}
@@ -10430,8 +10605,8 @@ packages:
/ufo@1.1.2:
resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
- /ultrahtml@1.2.0:
- resolution: {integrity: sha512-vxZM2yNvajRmCj/SknRYGNXk2tqiy6kRNvZjJLaleG3zJbSh/aNkOqD1/CVzypw8tyHyhpzYuwQgMMhUB4ZVNQ==}
+ /ultrahtml@1.3.0:
+ resolution: {integrity: sha512-xmXvE8tC8t4PVqy0/g1fe7H9USY/Brr425q4dD/0QbQMQit7siCtb06+SCqE4GfU24nwsZz8Th1g7L7mm1lL5g==}
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@@ -10741,6 +10916,18 @@ packages:
escalade: 3.1.1
picocolors: 1.0.0
+ /upper-case-first@2.0.2:
+ resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
+ dependencies:
+ tslib: 2.6.0
+ dev: false
+
+ /upper-case@2.0.2:
+ resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
+ dependencies:
+ tslib: 2.6.0
+ dev: false
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
diff --git a/src/components/FeedPost.vue b/src/components/FeedPost.vue
new file mode 100644
index 00000000..d983956e
--- /dev/null
+++ b/src/components/FeedPost.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/TheMastodonFeed.server.vue b/src/components/TheMastodonFeed.server.vue
new file mode 100644
index 00000000..a7f411be
--- /dev/null
+++ b/src/components/TheMastodonFeed.server.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/src/components/layout/TheSiteHeader.vue b/src/components/layout/TheSiteHeader.vue
index d1453511..2f730447 100644
--- a/src/components/layout/TheSiteHeader.vue
+++ b/src/components/layout/TheSiteHeader.vue
@@ -24,6 +24,10 @@ const menu = [
name: 'Uses',
path: '/uses',
},
+ {
+ name: 'Feed',
+ path: '/feed',
+ },
{
name: 'Blog',
path: '/blog',
diff --git a/src/modules/mocks/semver.ts b/src/modules/mocks/semver.ts
new file mode 100644
index 00000000..ed698333
--- /dev/null
+++ b/src/modules/mocks/semver.ts
@@ -0,0 +1,7 @@
+import proxy from 'unenv/runtime/mock/proxy'
+
+export const lt = proxy
+export const gt = proxy
+export const gte = proxy
+export const satisfies = proxy
+export class SemVer {}
diff --git a/src/modules/runtime/path.global.ts b/src/modules/runtime/path.global.ts
index 6ad6545c..622ee4da 100644
--- a/src/modules/runtime/path.global.ts
+++ b/src/modules/runtime/path.global.ts
@@ -17,7 +17,7 @@ function augmentMatched(route: RouteLocationNormalized) {
route.matched = [
{
components: {
- default: match(route.path)?.component.__asyncResolved.__name,
+ default: match(route.path)?.component.__asyncResolved?.__name,
},
} as any as RouteRecordNormalized,
]
diff --git a/src/modules/runtime/server/_social/mastodon.get.ts b/src/modules/runtime/server/_social/mastodon.get.ts
new file mode 100644
index 00000000..9f802d0a
--- /dev/null
+++ b/src/modules/runtime/server/_social/mastodon.get.ts
@@ -0,0 +1,44 @@
+import { createClient } from 'masto'
+import { parseURL, withProtocol } from 'ufo'
+
+export default defineLazyEventHandler(async () => {
+ const acct = 'daniel@roe.dev'
+
+ const data = await $fetch<{ subject: string; aliases: string[] }>(
+ '.well-known/webfinger',
+ {
+ baseURL: withProtocol(acct.split('@')[1], 'https://'),
+ query: {
+ resource: `acct:${acct}`,
+ },
+ }
+ )
+ const { host, protocol } = parseURL(data.aliases[0])
+
+ const client = createClient({
+ url: withProtocol(host!, protocol!),
+ disableVersionCheck: true,
+ disableDeprecatedWarning: true,
+ })
+ const { id } = await client.v1.accounts.lookup({ acct })
+
+ return defineEventHandler(async () => {
+ const posts = await client.v1.accounts.listStatuses(id)
+ return Promise.all(
+ posts
+ .filter(p => p.content && !p.inReplyToId)
+ // .slice(0, 1)
+ .map(p => ({
+ createdAt: p.createdAt,
+ permalink: p.url?.replace('https://', 'https://elk.zone/') ?? p.uri,
+ media: p.mediaAttachments.map(m => ({
+ url: m.url,
+ width: m.meta?.original?.width,
+ height: m.meta?.original?.height,
+ alt: m.description,
+ })),
+ html: p.content,
+ }))
+ )
+ })
+})
diff --git a/src/modules/social.ts b/src/modules/social.ts
new file mode 100644
index 00000000..ec3d5e9f
--- /dev/null
+++ b/src/modules/social.ts
@@ -0,0 +1,68 @@
+import {
+ addServerHandler,
+ createResolver,
+ defineNuxtModule,
+ useNuxt,
+} from 'nuxt/kit'
+import { defu } from 'defu'
+
+const networks = {
+ mastodon: 'mastodon',
+}
+
+type Network = keyof typeof networks
+
+type NetworkOptions = {
+ identifier: string
+}
+
+export default defineNuxtModule({
+ meta: {
+ name: 'social-sync',
+ configKey: 'social',
+ },
+ defaults: {
+ networks: {} as Partial>,
+ },
+ setup(options) {
+ const nuxt = useNuxt()
+ const resolver = createResolver(import.meta.url)
+
+ // Prevent all the extra stuff `masto` will add
+ nuxt.options.alias['node-fetch'] = 'node-fetch-native'
+ nuxt.options.build.transpile.push('masto', '@mastojs/ponyfills')
+
+ nuxt.options.nitro = defu(nuxt.options.nitro, {
+ alias: {
+ eventemitter3: 'unenv/runtime/mock/proxy',
+ 'isomorphic-ws': 'unenv/runtime/mock/proxy',
+ },
+ })
+
+ nuxt.options.alias = defu(nuxt.options.alias, {
+ querystring: 'rollup-plugin-node-polyfills/polyfills/qs',
+ 'change-case': 'scule',
+ semver: resolver.resolve('./mocks/semver'),
+ })
+
+ for (const network in options.networks) {
+ if (!isSupportedNetwork(network)) {
+ console.warn('Ignoring unsupported network:', network)
+ continue
+ }
+ addServerHandler({
+ route: `/_social/${network}`,
+ handler: resolver.resolve(`./runtime/server/_social/${network}.get.ts`),
+ })
+ }
+
+ nuxt.options.nitro.routeRules ||= {}
+ nuxt.options.nitro.routeRules['/_social/**'] = {
+ swr: 60,
+ }
+ },
+})
+
+function isSupportedNetwork(id: string): id is Network {
+ return id in networks
+}
diff --git a/src/pages/feed.vue b/src/pages/feed.vue
new file mode 100644
index 00000000..a813877c
--- /dev/null
+++ b/src/pages/feed.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/test/unit/bundle.spec.ts b/test/unit/bundle.spec.ts
index 1df1fcb0..1f6a83c0 100644
--- a/test/unit/bundle.spec.ts
+++ b/test/unit/bundle.spec.ts
@@ -64,6 +64,7 @@ describe('project sizes', () => {
"_nuxt/SocialPost.js",
"_nuxt/_article_.js",
"_nuxt/entry.js",
+ "_nuxt/feed.js",
"_nuxt/index.js",
"_nuxt/index.js",
"_nuxt/talks.js",
@@ -77,12 +78,12 @@ describe('project sizes', () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect
.soft(roundToKilobytes(stats.server.totalBytes))
- .toMatchInlineSnapshot('"207k"')
+ .toMatchInlineSnapshot('"338k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect
.soft(roundToKilobytes(modules.totalBytes))
- .toMatchInlineSnapshot('"25529k"')
+ .toMatchInlineSnapshot('"26175k"')
const packages = modules.files
.filter(m => m.endsWith('package.json'))
@@ -91,7 +92,10 @@ describe('project sizes', () => {
expect.soft(packages).toMatchInlineSnapshot(`
[
"@fastify/accept-negotiator",
+ "abort-controller",
+ "asynckit",
"bail",
+ "call-bind",
"ccount",
"character-entities",
"character-entities-html4",
@@ -101,6 +105,7 @@ describe('project sizes', () => {
"color-convert",
"color-name",
"color-string",
+ "combined-stream",
"comma-separated-tokens",
"consola",
"cookie-es",
@@ -108,19 +113,27 @@ describe('project sizes', () => {
"debug",
"decode-named-character-reference",
"defu",
+ "delayed-stream",
"destr",
"detab",
"detect-libc",
"emoticon",
"escape-string-regexp",
"etag",
+ "event-target-shim",
"extend",
"feed",
"flat",
+ "form-data",
+ "function-bind",
+ "get-intrinsic",
"github-slugger",
"h3",
"h3/node_modules/destr",
+ "has",
"has-flag",
+ "has-proto",
+ "has-symbols",
"hast-util-from-parse5",
"hast-util-has-property",
"hast-util-heading-rank",
@@ -194,9 +207,12 @@ describe('project sizes', () => {
"micromark-util-resolve-all",
"micromark-util-sanitize-uri",
"micromark-util-subtokenize",
+ "mime-db",
+ "mime-types",
"ms",
"node-emoji",
"node-fetch-native",
+ "object-inspect",
"ofetch",
"ofetch/node_modules/destr",
"ohash",
@@ -204,6 +220,7 @@ describe('project sizes', () => {
"parse5",
"pathe",
"property-information",
+ "qs",
"radix3",
"rehype-external-links",
"rehype-raw",
@@ -221,6 +238,7 @@ describe('project sizes', () => {
"semver",
"sharp",
"shiki-es",
+ "side-channel",
"simple-swizzle",
"slugify",
"space-separated-tokens",