Compare commits
No commits in common. "30cdde0fc2e7dc748ddef0cf44cce125412f5eba" and "f571670e13daa179f3644de2e61e907ca7e59230" have entirely different histories.
30cdde0fc2
...
f571670e13
6 changed files with 112 additions and 1006 deletions
|
@ -4,8 +4,6 @@ import sitemap from '@astrojs/sitemap';
|
||||||
|
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://terminal-blog.example.com',
|
site: 'https://terminal-blog.example.com',
|
||||||
base: '/',
|
base: '/',
|
||||||
|
@ -22,7 +20,5 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
integrations: [sitemap(), mdx()],
|
integrations: [sitemap(), mdx()]
|
||||||
|
|
||||||
adapter: cloudflare()
|
|
||||||
});
|
});
|
|
@ -11,9 +11,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "^12.5.2",
|
|
||||||
"@astrojs/mdx": "^4.2.6",
|
"@astrojs/mdx": "^4.2.6",
|
||||||
"@astrojs/node": "^9.2.1",
|
|
||||||
"@astrojs/rss": "^4.0.1",
|
"@astrojs/rss": "^4.0.1",
|
||||||
"@astrojs/sitemap": "^3.3.1",
|
"@astrojs/sitemap": "^3.3.1",
|
||||||
"astro": "^5.2.5",
|
"astro": "^5.2.5",
|
||||||
|
|
752
pnpm-lock.yaml
generated
752
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,6 @@ import FediverseComments from "./helper/comments/Fediverse.astro";
|
||||||
const method = siteConfig.comments.type
|
const method = siteConfig.comments.type
|
||||||
const ArtalkConfig = siteConfig.comments.artalk
|
const ArtalkConfig = siteConfig.comments.artalk
|
||||||
const giscusConfig = siteConfig.comments.giscus
|
const giscusConfig = siteConfig.comments.giscus
|
||||||
const FediverseConfig = siteConfig.comments.fediverse
|
|
||||||
interface Props {
|
interface Props {
|
||||||
path?: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +48,4 @@ let { path='/' } = Astro.props;
|
||||||
async
|
async
|
||||||
></script>
|
></script>
|
||||||
)}
|
)}
|
||||||
<!-- if prerender === true is set then render from client -->
|
{method === 'fediverse' && <FediverseComments path={path} /> }
|
||||||
{(method === 'fediverse' && !FediverseConfig.renderOnServer ) && <FediverseComments path={path} /> }
|
|
||||||
{(method === 'fediverse' && FediverseConfig.renderOnServer ) && <FediverseComments server:defer path={path} ><p>Loading comments...</p></FediverseComments> }
|
|
|
@ -1,155 +1,47 @@
|
||||||
---
|
---
|
||||||
import { siteConfig } from "../../../config";
|
import { siteConfig } from "../../../config";
|
||||||
|
|
||||||
const fediverseConfig = siteConfig.comments.fediverse;
|
const fediverseConfig = siteConfig.comments.fediverse;
|
||||||
const {
|
const {
|
||||||
renderOnServer,
|
instanceDomain,
|
||||||
instanceDomain,
|
useV2api,
|
||||||
useV2api,
|
token,
|
||||||
token,
|
useReverseProxy,
|
||||||
useReverseProxy,
|
reverseProxyUrl,
|
||||||
reverseProxyUrl,
|
accountId
|
||||||
accountId
|
} = fediverseConfig;
|
||||||
} = fediverseConfig;
|
|
||||||
|
|
||||||
const serverRender = fediverseConfig.renderOnServer
|
interface Props {
|
||||||
|
path: string;
|
||||||
interface Props {
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { path } = Astro.props;
|
|
||||||
|
|
||||||
// Create the full URL to search for
|
|
||||||
const fullSiteUrl = Astro.url.host;
|
|
||||||
const postUrl = `https://${fullSiteUrl}${path.startsWith('/') ? path : '/' + path}`;
|
|
||||||
|
|
||||||
// Define the search API endpoint based on configuration
|
|
||||||
let searchEndpoint: string;
|
|
||||||
if (useReverseProxy && reverseProxyUrl) {
|
|
||||||
searchEndpoint = reverseProxyUrl;
|
|
||||||
} else {
|
|
||||||
const apiVersion = useV2api ? 'v2' : 'v1';
|
|
||||||
searchEndpoint = `https://${instanceDomain}/api/${apiVersion}/search`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare default variables
|
|
||||||
let commentData: any = null;
|
|
||||||
let replies: any[] = [];
|
|
||||||
|
|
||||||
if (serverRender) {
|
|
||||||
// Server-side rendering - fetch data during build
|
|
||||||
// Prepare URL
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('q', postUrl);
|
|
||||||
params.append('type', 'statuses');
|
|
||||||
if (accountId) {
|
|
||||||
params.append('account_id', accountId);
|
|
||||||
}
|
|
||||||
const url = `${searchEndpoint}?${params.toString()}`;
|
|
||||||
|
|
||||||
// Prepare fetch options
|
|
||||||
const options: RequestInit = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add authorization if token is provided
|
|
||||||
if (token) {
|
|
||||||
(options.headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data
|
const { path } = Astro.props;
|
||||||
commentData = await fetch(url, options).then(r => r.json());
|
|
||||||
|
|
||||||
// Extract original post and get replies if available
|
// Create the full URL to search for
|
||||||
const statuses = commentData.statuses || [];
|
const fullSiteUrl = Astro.url.host;
|
||||||
if (statuses.length > 0) {
|
const postUrl = `https://${fullSiteUrl}${path.startsWith('/') ? path : '/' + path}`;
|
||||||
const originalPost = statuses[0];
|
|
||||||
|
|
||||||
// Fetch replies
|
// Define the search API endpoint based on configuration
|
||||||
if (originalPost) {
|
let searchEndpoint;
|
||||||
const statusId = originalPost.id;
|
if (useReverseProxy && reverseProxyUrl) {
|
||||||
const contextUrl = `https://${instanceDomain}/api/v1/statuses/${statusId}/context`;
|
searchEndpoint = reverseProxyUrl;
|
||||||
const contextResponse = await fetch(contextUrl, options);
|
} else {
|
||||||
if (contextResponse.ok) {
|
const apiVersion = useV2api ? 'v2' : 'v1';
|
||||||
const contextData = await contextResponse.json();
|
searchEndpoint = `https://${instanceDomain}/api/${apiVersion}/search`;
|
||||||
replies = contextData.descendants || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
---
|
||||||
---
|
|
||||||
|
|
||||||
<div class="fediverse-comments">
|
<div class="fediverse-comments">
|
||||||
<div class="fediverse-status">
|
<div class="fediverse-status">
|
||||||
{serverRender ? (
|
<div id="loading-message">Loading comments from the Fediverse...</div>
|
||||||
<>
|
<div id="error-message" style="display: none;"></div>
|
||||||
{!commentData || !commentData.statuses || commentData.statuses.length === 0 ? (
|
<div id="no-posts-message" style="display: none;">
|
||||||
<div>No discussions found for this post on the Fediverse yet.</div>
|
No discussions found for this post on the Fediverse yet.
|
||||||
) : null}
|
|
||||||
|
|
||||||
{commentData && commentData.statuses && commentData.statuses.length > 0 ? (
|
|
||||||
<div class="original-post">
|
|
||||||
<div class="post-header">
|
|
||||||
<img src={commentData.statuses[0].account.avatar} alt={commentData.statuses[0].account.display_name} class="avatar" />
|
|
||||||
<div class="post-meta">
|
|
||||||
<div class="post-author">{commentData.statuses[0].account.display_name}</div>
|
|
||||||
<div class="post-username">@{commentData.statuses[0].account.acct}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="post-content" set:html={commentData.statuses[0].content} />
|
|
||||||
<div class="post-stats">
|
|
||||||
<span>🔁 {commentData.statuses[0].reblogs_count || 0}</span>
|
|
||||||
<span>⭐ {commentData.statuses[0].favourites_count || 0}</span>
|
|
||||||
</div>
|
|
||||||
<div class="post-link">
|
|
||||||
<a href={commentData.statuses[0].url} target="_blank" rel="noopener noreferrer">View post</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div id="loading-message">Loading comments from the Fediverse...</div>
|
|
||||||
<div id="error-message" style="display: none;"></div>
|
|
||||||
<div id="no-posts-message" style="display: none;">
|
|
||||||
No discussions found for this post on the Fediverse yet.
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="fediverse-replies">
|
|
||||||
{!serverRender ? (
|
|
||||||
<div class="replies-container">
|
|
||||||
{replies.map((reply) => (
|
|
||||||
<div class="reply">
|
|
||||||
<div class="post-header">
|
|
||||||
<img src={reply.account.avatar} alt={reply.account.display_name} class="avatar" />
|
|
||||||
<div class="post-meta">
|
|
||||||
<div class="post-author">{reply.account.display_name}</div>
|
|
||||||
<div class="post-username">@{reply.account.acct}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="reply-content" set:html={reply.content} />
|
|
||||||
<div class="post-stats">
|
|
||||||
<span>🔁 {reply.reblogs_count || 0}</span>
|
|
||||||
<span>⭐ {reply.favourites_count || 0}</span>
|
|
||||||
</div>
|
|
||||||
<div class="post-link">
|
|
||||||
<a href={reply.url} target="_blank" rel="noopener noreferrer">View reply</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</div>
|
||||||
|
<div id="fediverse-replies"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{!serverRender && (
|
|
||||||
<script define:vars={{
|
<script define:vars={{
|
||||||
searchEndpoint,
|
searchEndpoint,
|
||||||
postUrl,
|
postUrl,
|
||||||
|
@ -210,7 +102,7 @@ if (serverRender) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the original post (by checking for our URL)
|
// Find the original post (by checking for our URL)
|
||||||
const originalPost = statuses[0];
|
const originalPost = statuses[0]
|
||||||
|
|
||||||
if (!originalPost) {
|
if (!originalPost) {
|
||||||
loadingEl.style.display = 'none';
|
loadingEl.style.display = 'none';
|
||||||
|
@ -218,31 +110,6 @@ if (serverRender) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the original post
|
|
||||||
const originalPostHtml = `
|
|
||||||
<div class="original-post">
|
|
||||||
<div class="post-header">
|
|
||||||
<img src="${originalPost.account.avatar}" alt="${originalPost.account.display_name}" class="avatar">
|
|
||||||
<div class="post-meta">
|
|
||||||
<div class="post-author">${originalPost.account.display_name}</div>
|
|
||||||
<div class="post-username">@${originalPost.account.acct}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="post-content">
|
|
||||||
${originalPost.content}
|
|
||||||
</div>
|
|
||||||
<div class="post-stats">
|
|
||||||
<span>🔁 ${originalPost.reblogs_count || 0}</span>
|
|
||||||
<span>⭐ ${originalPost.favourites_count || 0}</span>
|
|
||||||
</div>
|
|
||||||
<div class="post-link">
|
|
||||||
<a href="${originalPost.url}" target="_blank" rel="noopener noreferrer">View post</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.querySelector('.fediverse-status').innerHTML = originalPostHtml;
|
|
||||||
|
|
||||||
// Fetch the status and its context (replies)
|
// Fetch the status and its context (replies)
|
||||||
const statusId = originalPost.id;
|
const statusId = originalPost.id;
|
||||||
const contextUrl = `https://${instanceDomain}/api/v1/statuses/${statusId}/context`;
|
const contextUrl = `https://${instanceDomain}/api/v1/statuses/${statusId}/context`;
|
||||||
|
@ -279,12 +146,15 @@ if (serverRender) {
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
repliesContainer.innerHTML = `
|
repliesContainer.innerHTML += `
|
||||||
<div class="replies-container">
|
<div class="replies-container">
|
||||||
${repliesHtml}
|
${repliesHtml}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadingEl.style.display = 'none';
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching Fediverse comments:', error);
|
console.error('Error fetching Fediverse comments:', error);
|
||||||
loadingEl.style.display = 'none';
|
loadingEl.style.display = 'none';
|
||||||
|
@ -296,85 +166,84 @@ if (serverRender) {
|
||||||
// Call the fetch function when the component is loaded
|
// Call the fetch function when the component is loaded
|
||||||
document.addEventListener('DOMContentLoaded', fetchFediverseComments);
|
document.addEventListener('DOMContentLoaded', fetchFediverseComments);
|
||||||
</script>
|
</script>
|
||||||
)}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.fediverse-comments {
|
.fediverse-comments {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fediverse-status {
|
.fediverse-status {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#error-message {
|
#error-message {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border-left: 3px solid #e74c3c;
|
border-left: 3px solid #e74c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.original-post, .reply {
|
.original-post, .reply {
|
||||||
border: 1px solid var(--border-color, #ddd);
|
border: 1px solid var(--border-color, #ddd);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
background-color: var(--comment-bg, rgba(0, 0, 0, 0.02));
|
background-color: var(--comment-bg, rgba(0, 0, 0, 0.02));
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-header, .reply-header {
|
.post-header, .reply-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-author {
|
.post-author {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-username {
|
.post-username {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-content, .reply-content {
|
.post-content, .reply-content {
|
||||||
margin: 0.75rem 0;
|
margin: 0.75rem 0;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-stats {
|
.post-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--text-secondary, #666);
|
color: var(--text-secondary, #666);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-link {
|
.post-link {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-link a {
|
.post-link a {
|
||||||
color: var(--link-color, #0366d6);
|
color: var(--link-color, #0366d6);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-link a:hover {
|
.post-link a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.replies-container {
|
.replies-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
border-left: 2px solid var(--border-color, #ddd);
|
border-left: 2px solid var(--border-color, #ddd);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -24,19 +24,17 @@ export const siteConfig = {
|
||||||
fediverse: {
|
fediverse: {
|
||||||
// use Mastodon (compatible) api to search posts and parse replies
|
// use Mastodon (compatible) api to search posts and parse replies
|
||||||
// it will search for the post's link by default
|
// it will search for the post's link by default
|
||||||
renderOnServer: false, // render comments on server-side or client-side, may different from the astro config
|
// the comments are rendered at the client side (by now)
|
||||||
// the comments are rendered at the client side by default
|
// a reverse proxy is required in pure client-side rendering mode to get the posts from the fediverse instance
|
||||||
// but if you want to deploy site on Cloudflare pages or so you can set it to true.
|
useReverseProxy: true,
|
||||||
// (but in pure SSG mode, the comments will be rendered at build time, which mean delayed updates,maybe?)
|
|
||||||
// a reverse proxy is recommended in pure client-side rendering mode to get the posts from the fediverse instance
|
|
||||||
// that requires to be authorized to use search api the instance
|
|
||||||
useReverseProxy: false,
|
|
||||||
reverseProxyUrl: '', // the url of the reverse proxy, usually a cloudflare worker proxying the search api
|
reverseProxyUrl: '', // the url of the reverse proxy, usually a cloudflare worker proxying the search api
|
||||||
// the reverse proxy should be able to handle the following request:
|
// the reverse proxy should be able to handle the following request:
|
||||||
// GET /api/v1/search?q={query}&type=statuses&account_id=12345678
|
// GET /api/v1/search?q={query}&type=statuses&account_id=12345678
|
||||||
// GET /api/v1/statuses/12345678/context
|
// GET /api/v1/statuses/12345678/context
|
||||||
// response body should be returned from the origin (fediverse instance) as-is.
|
// response body should be returned from the origin fediverse instance as-is.
|
||||||
accountId: '', // the account id to search posts from, can be got from api like: https://{instance}/api/v1/accounts/{username without domain part}
|
accountId: '', // the account id to search posts from, can be got from api like: https://{instance}/api/v1/accounts/{username without domain part}
|
||||||
|
// for development purpose only (by now):
|
||||||
|
// TODO: render the comments on the server-side when in server-side render/hybrid render mode
|
||||||
instanceDomain: '', // the domain of the fediverse instance to search posts from
|
instanceDomain: '', // the domain of the fediverse instance to search posts from
|
||||||
useV2api: true, // use /api/v2/search instead of /api/v1/search to search on instance using newer version of mastodon/pleroma/akkoma
|
useV2api: true, // use /api/v2/search instead of /api/v1/search to search on instance using newer version of mastodon/pleroma/akkoma
|
||||||
token: process.env.MASTODON_API_TOKEN, // the token to use to authenticate with the fediverse instance, usually a read:search-only token
|
token: process.env.MASTODON_API_TOKEN, // the token to use to authenticate with the fediverse instance, usually a read:search-only token
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue