Create new entrypoint for sharable Wrapstodon (#37121)

This commit is contained in:
Echo
2025-12-04 16:48:10 +01:00
committed by GitHub
parent 75b9e9a8b0
commit 0dac31dfd5
8 changed files with 132 additions and 5 deletions

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
module WrapstodonHelper
def render_wrapstodon_share_data(report)
json = ActiveModelSerializers::SerializableResource.new(
AnnualReportsPresenter.new([report]),
serializer: REST::AnnualReportsSerializer,
scope: nil,
scope_name: :current_user
).to_json
# rubocop:disable Rails/OutputSafety
content_tag(:script, json_escape(json).html_safe, type: 'application/json', id: 'wrapstodon-data')
# rubocop:enable Rails/OutputSafety
end
end

View File

@@ -0,0 +1,58 @@
import { createRoot } from 'react-dom/client';
import { Provider as ReduxProvider } from 'react-redux';
import {
importFetchedAccounts,
importFetchedStatuses,
} from '@/mastodon/actions/importer';
import type { ApiAnnualReportResponse } from '@/mastodon/api/annual_report';
import { Router } from '@/mastodon/components/router';
import { WrapstodonShare } from '@/mastodon/features/annual_report/share';
import { IntlProvider, loadLocale } from '@/mastodon/locales';
import { loadPolyfills } from '@/mastodon/polyfills';
import ready from '@/mastodon/ready';
import { setReport } from '@/mastodon/reducers/slices/annual_report';
import { store } from '@/mastodon/store';
function loaded() {
const mountNode = document.getElementById('wrapstodon');
if (!mountNode) {
throw new Error('Mount node not found');
}
const propsNode = document.getElementById('wrapstodon-data');
if (!propsNode) {
throw new Error('Initial state prop not found');
}
const initialState = JSON.parse(
propsNode.textContent,
) as ApiAnnualReportResponse;
const report = initialState.annual_reports[0];
if (!report) {
throw new Error('Initial state report not found');
}
store.dispatch(importFetchedAccounts(initialState.accounts));
store.dispatch(importFetchedStatuses(initialState.statuses));
store.dispatch(setReport(report));
const root = createRoot(mountNode);
root.render(
<IntlProvider>
<ReduxProvider store={store}>
<Router>
<WrapstodonShare />
</Router>
</ReduxProvider>
</IntlProvider>,
);
}
loadPolyfills()
.then(loadLocale)
.then(() => ready(loaded))
.catch((err: unknown) => {
console.error(err);
});

View File

@@ -1,3 +1,5 @@
import classNames from 'classnames';
import logo from '@/images/logo.svg';
export const WordmarkLogo: React.FC = () => (
@@ -7,8 +9,12 @@ export const WordmarkLogo: React.FC = () => (
</svg>
);
export const IconLogo: React.FC = () => (
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
export const IconLogo: React.FC<{ className?: string }> = ({ className }) => (
<svg
viewBox='0 0 79 79'
className={classNames('logo logo--icon', className)}
role='img'
>
<title>Mastodon</title>
<use xlinkHref='#logo-symbol-icon' />
</svg>

View File

@@ -0,0 +1,19 @@
.wrapper {
max-width: 40rem;
margin: 0 auto;
}
.footer {
text-align: center;
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: center;
color: var(--color-text-secondary);
}
.logo {
width: 2rem;
opacity: 0.6;
}

View File

@@ -0,0 +1,18 @@
import type { FC } from 'react';
import { IconLogo } from '@/mastodon/components/logo';
import { AnnualReport } from './index';
import classes from './share.module.css';
export const WrapstodonShare: FC = () => {
return (
<main className={classes.wrapper}>
<AnnualReport share={false} />
<footer className={classes.footer}>
<IconLogo className={classes.logo} />
Generated with by the Mastodon team
</footer>
</main>
);
};

View File

@@ -1,3 +1,4 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import {
@@ -29,7 +30,12 @@ interface AnnualReportState {
const annualReportSlice = createSlice({
name: 'annualReport',
initialState: {} as AnnualReportState,
reducers: {},
reducers: {
setReport(state, action: PayloadAction<AnnualReport>) {
state.report = action.payload;
state.state = 'available';
},
},
extraReducers(builder) {
builder
.addCase(fetchReportState.fulfilled, (state, action) => {
@@ -47,6 +53,7 @@ const annualReportSlice = createSlice({
});
export const annualReport = annualReportSlice.reducer;
export const { setReport } = annualReportSlice.actions;
export const selectWrapstodonYear = createAppSelector(
[(state) => state.server.getIn(['server', 'wrapstodon'])],

View File

@@ -22,7 +22,7 @@ class AnnualReport
return unless Mastodon::Feature.wrapstodon_enabled?
datetime = Time.now.utc
datetime.year if datetime.month == 12 && (10..31).cover?(datetime.day)
datetime.year if datetime.month == 12 && (1..31).cover?(datetime.day)
end
def initialize(account, year)

View File

@@ -6,4 +6,7 @@
= opengraph 'og:site_name', site_title
= opengraph 'profile:username', acct(@account)[1..]
= render 'shared/web_app'
= vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous'
#wrapstodon
= render_wrapstodon_share_data @generated_annual_report