From 1d703bdc489f27a9461e8f2c0ee119bc1a0a3b9c Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 15 Jul 2023 18:00:30 +0100 Subject: [PATCH] feat: add preliminary social feed (#1243) --- budget.json | 2 +- nuxt.config.ts | 9 + package.json | 1 + pnpm-lock.yaml | 213 ++++++++++++++++-- src/components/FeedPost.vue | 53 +++++ src/components/TheMastodonFeed.server.vue | 36 +++ src/components/layout/TheSiteHeader.vue | 4 + src/modules/mocks/semver.ts | 7 + src/modules/runtime/path.global.ts | 2 +- .../runtime/server/_social/mastodon.get.ts | 44 ++++ src/modules/social.ts | 68 ++++++ src/pages/feed.vue | 14 ++ test/unit/bundle.spec.ts | 22 +- 13 files changed, 458 insertions(+), 17 deletions(-) create mode 100644 src/components/FeedPost.vue create mode 100644 src/components/TheMastodonFeed.server.vue create mode 100644 src/modules/mocks/semver.ts create mode 100644 src/modules/runtime/server/_social/mastodon.get.ts create mode 100644 src/modules/social.ts create mode 100644 src/pages/feed.vue 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", -- 2.51.2