diff --git a/package-lock.json b/package-lock.json index 73955239..6d10134e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,11 @@ "front-matter": "^4.0.2", "lunr": "^2.3.9", "next": "14.0.3", + "node-html-markdown": "^1.3.0", "react": "18.2.0", "react-dom": "18.2.0", "remark-parse": "^11.0.0", "swr": "^2.2.4", - "twitter-api-sdk": "^1.2.1", "typescript": "5.3.2", "unified": "^11.0.4", "yerror": "^6.2.1" @@ -784,17 +784,6 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -1101,6 +1090,11 @@ "node": ">= 14" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1298,6 +1292,32 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -1423,6 +1443,57 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1448,6 +1519,17 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", @@ -1997,14 +2079,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -2500,6 +2574,14 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/html-entities": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", @@ -3864,6 +3946,26 @@ } } }, + "node_modules/node-html-markdown": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-html-markdown/-/node-html-markdown-1.3.0.tgz", + "integrity": "sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g==", + "dependencies": { + "node-html-parser": "^6.1.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/node-html-parser": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.11.tgz", + "integrity": "sha512-FAgwwZ6h0DSDWxfD0Iq1tsDcBCxdJB1nXpLPPxX8YyVWzbfCjKWEzaynF4gZZ/8hziUmp7ZSaKylcn0iKhufUQ==", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -3891,6 +3993,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4948,18 +5061,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/twitter-api-sdk": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/twitter-api-sdk/-/twitter-api-sdk-1.2.1.tgz", - "integrity": "sha512-tNQ6DGYucFk94JlnUMsHCkHg5o1wnCdHh71Y2ukygNVssOdD1gNVjOpaojJrdwbEAhoZvcWdGHerCa55F8HKxQ==", - "dependencies": { - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 05591433..888558ce 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,11 @@ "front-matter": "^4.0.2", "lunr": "^2.3.9", "next": "14.0.3", + "node-html-markdown": "^1.3.0", "react": "18.2.0", "react-dom": "18.2.0", "remark-parse": "^11.0.0", "swr": "^2.2.4", - "twitter-api-sdk": "^1.2.1", "typescript": "5.3.2", "unified": "^11.0.4", "yerror": "^6.2.1" diff --git a/src/components/tootList.tsx b/src/components/tootList.tsx new file mode 100644 index 00000000..b94f24b8 --- /dev/null +++ b/src/components/tootList.tsx @@ -0,0 +1,62 @@ +import Heading2 from './h2'; +import { renderMarkdown } from '../utils/markdown'; +import Paragraph from './p'; +import Anchor from './a'; +import type { MarkdownRootNode } from '../utils/markdown'; + +export type Toots = { + id: string; + text: MarkdownRootNode; + createdAt: string; + url: string; +}[]; + +const TootList = ({ toots }: { toots: Toots }) => ( + +); + +export default TootList; diff --git a/src/components/tweetList.tsx b/src/components/tweetList.tsx deleted file mode 100644 index f47310d9..00000000 --- a/src/components/tweetList.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Heading2 from '../components/h2'; -import { renderMarkdown } from '../utils/markdown'; -import type { MarkdownRootNode } from '../utils/markdown'; -export type Tweets = { - id: string; - content: MarkdownRootNode; -}[]; - -const TweetList = ({ tweets }: { tweets: Tweets }) => ( - -); - -export default TweetList; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index caef469f..34cb3cf2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,6 +1,5 @@ import Layout from '../layouts/main'; import ContentBlock from '../components/contentBlock'; -import TweetList from '../components/tweetList'; import Heading1 from '../components/h1'; import Heading2 from '../components/h2'; import Paragraph from '../components/p'; @@ -8,17 +7,24 @@ import Strong from '../components/strong'; import Anchor from '../components/a'; import UnorderedList from '../components/ul'; import ListItem from '../components/li'; -import { Client } from 'twitter-api-sdk'; import { parseMarkdown } from '../utils/markdown'; +import TootList from '../components/tootList'; +import { NodeHtmlMarkdown } from "node-html-markdown"; +import { + MASTODON_ACCOUNT_ID, + MASTODON_SERVER, +} from "../utils/constants"; import type { MarkdownRootNode } from '../utils/markdown'; import type { GetStaticProps } from 'next'; -import type {Tweets} from '../components/tweetList'; +import type {Toots} from '../components/tootList'; type Props = { - tweets:Tweets; + toots:Toots; }; -const Page = ({ tweets }: Props) => { +const htmlToMarkdown = new NodeHtmlMarkdown({}); + +const Page = ({ toots }: Props) => { return ( { qui fédère les acteurs de JavaScript afin de promouvoir ce langage et de faciliter son développement en France. - + ); }; export const getStaticProps: GetStaticProps = async () => { - const client = new Client(process.env.TWITTER_API_TOKEN as string); - const userId = (await client.users.findUserByUsername('chtijs')).data?.id; - const data = (await client.tweets.usersIdTweets(userId as string)).data; - const tweets = data - ?.map(({ id, text }) => { + const body = (await ( + await fetch( + `https://${MASTODON_SERVER}/api/v1/accounts/${MASTODON_ACCOUNT_ID}/statuses`, + { + headers: new Headers({ + Authorization: `Bearer ${process.env.MASTODON_ACCESS_TOKEN}`, + }), + mode: "cors", + cache: "default", + } + ) + ).json()) as Status[]; + const toots = body + .filter((toot) => !toot.in_reply_to_id) + .filter((toot) => toot.content) + .map((toot) => { + const text = parseMarkdown( + htmlToMarkdown.translate(toot.content) + ) as MarkdownRootNode; + return { - id, - content: parseMarkdown( - text - .replace(/#([\w_]+)/mg, '[#$1](https://twitter.com/hashtag/$1)') - .replace(/@([\w_]+)/mg, '[@$1](https://twitter.com/$1)') - ) as MarkdownRootNode, + id: toot.id, + createdAt: toot.created_at, + text, + url: toot.url, }; }) .slice(0, 3); - return { props: { tweets } as Props }; + return { props: { toots } as Props }; }; -export default Page; \ No newline at end of file + +export default Page; + +type Status = NonNullable<{ + id: NonNullable; + uri: NonNullable; + created_at: NonNullable; + account: Account; + content: NonNullable; + visibility: "public" | "unlisted" | "private" | "direct"; + sensitive: NonNullable; + spoiler_text: NonNullable; + media_attachements: NonNullable; + application: Application; + url?: NonNullable; + in_reply_to_id?: NonNullable; + in_reply_to_account_id?: NonNullable; + reblog?: Status; + poll?: Poll; + card?: Card; + language?: NonNullable; + text?: NonNullable; + mentions: NonNullable; + tags: NonNullable; + emojis: NonNullable; + reblogs_count: NonNullable; + favourites_count: NonNullable; + replies_count: NonNullable; + favourited?: NonNullable; + reblogged?: NonNullable; + muted?: NonNullable; + bookmarked?: NonNullable; + pinned?: NonNullable; +}>; +type Field = NonNullable<{ + name: NonNullable; + value: NonNullable; + verified_at?: NonNullable; +}>; +type Emoji = NonNullable<{ + shortcode: NonNullable; + url: NonNullable; + static_url: NonNullable; + visible_in_picker: NonNullable; + category?: NonNullable; +}>; +type Attachment = NonNullable<{ + id: NonNullable; + url: NonNullable; + preview_url: NonNullable; + remote_url?: NonNullable; + description?: NonNullable; + blurhash?: NonNullable; +}>; +type Poll = NonNullable<{ + id: NonNullable; + expires_at?: NonNullable; + expired: NonNullable; + multiple: NonNullable; + votes_count: NonNullable; + voters_count?: NonNullable; + voted?: NonNullable; + own_votes?: NonNullable[]>; + options: NonNullable< + NonNullable<{ + title: NonNullable; + votes_count?: NonNullable; + }>[] + >; + emojis: NonNullable; +}>; +type Card = NonNullable<{ + url: NonNullable; + title: NonNullable; + description: NonNullable; + type: "link" | "photo" | "video" | "rich"; + author_name?: NonNullable; + author_url?: NonNullable; + provider_name?: NonNullable; + provider_url?: NonNullable; + html?: NonNullable; + width?: NonNullable; + height?: NonNullable; + image?: NonNullable; + embed_url?: NonNullable; + blurhash?: NonNullable; +}>; +type Mention = NonNullable<{ + id: NonNullable; + username: NonNullable; + acct: NonNullable; + url: NonNullable; +}>; +type Tag = NonNullable<{ + name: NonNullable; + url: NonNullable; + history?: NonNullable; +}>; +type History = NonNullable<{ + day: NonNullable; + uses: NonNullable; + accounts: NonNullable; +}>; +type Account = NonNullable<{ + id: NonNullable; + username: NonNullable; + acct: NonNullable; + url: NonNullable; + moved?: Account; + fields?: Field; + bot?: NonNullable; + suspended?: NonNullable; + mute_expires_at?: NonNullable; + created_at: NonNullable; + last_status_at?: NonNullable; + statuses_count: NonNullable; + followers_count: NonNullable; + following_count: NonNullable; + display_name: NonNullable; + note: NonNullable; + avatar: NonNullable; + avatar_static: NonNullable; + header: NonNullable; + header_static: NonNullable; + locked: NonNullable; + emojis: NonNullable; + discoverable: NonNullable; +}>; +type Application = NonNullable<{ + name: NonNullable; + website?: NonNullable; + vapid_key?: NonNullable; +}>; \ No newline at end of file diff --git a/src/utils/constants.tsx b/src/utils/constants.tsx index fa71cdc1..064236f6 100644 --- a/src/utils/constants.tsx +++ b/src/utils/constants.tsx @@ -4,6 +4,8 @@ export const ORGANISATION_PRIMARY_COLOR = '#f2c80a'; export const PUBLISHER = 'ChtiJS'; export const DOMAIN_NAME = 'chtijs.francejs.org'; export const TWITTER_ACCOUNT = 'chtijs'; +export const MASTODON_ACCOUNT_ID = "111449166761360064"; +export const MASTODON_SERVER = "piaille.fr"; // Sadly, breakpoints can't currently use CSS var() / calc() // so let's use those constants