0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-15 03:01:37 -05:00

Added basic pagination and API mocking

This commit is contained in:
Simon Backx 2022-07-05 14:24:29 +02:00
parent c6fb93d280
commit 65ac303308
10 changed files with 155 additions and 33 deletions

View file

@ -609,11 +609,11 @@ video {
}
.rounded-md {
border-radius: 0.6rem;
border-radius: 0.375rem;
}
.rounded {
border-radius: 0.4rem;
border-radius: 0.25rem;
}
.border {
@ -701,3 +701,4 @@ body {
all: initial;
}

View file

@ -33,8 +33,10 @@ export default class App extends React.Component {
initStatus: 'running',
member: null,
comments: null,
pagination: null,
popupNotification: null,
customSiteUrl: props.customSiteUrl
customSiteUrl: props.customSiteUrl,
postCommentId: props.postCommentId
};
}
@ -47,11 +49,15 @@ export default class App extends React.Component {
try {
// Fetch data from API, links, preview, dev sources
const {site, member} = await this.fetchApiData();
const {comments, pagination} = await this.fetchComments();
const state = {
site,
member,
action: 'init:success',
initStatus: 'success'
initStatus: 'success',
comments,
pagination
};
this.setState(state);
@ -118,6 +124,16 @@ export default class App extends React.Component {
}
}
/** Fetch first few comments */
async fetchComments() {
const data = this.GhostApi.comments.browse({page: 1});
return {
comments: data.comments,
pagination: data.meta.pagination
};
}
/** Setup Sentry */
setupSentry({site}) {
if (hasMode(['test'])) {
@ -146,12 +162,14 @@ export default class App extends React.Component {
/**Get final App level context from App state*/
getContextFromState() {
const {action, popupNotification, customSiteUrl, member} = this.state;
const {action, popupNotification, customSiteUrl, member, comments, pagination} = this.state;
return {
action,
popupNotification,
customSiteUrl,
member,
comments,
pagination,
onAction: (_action, data) => this.dispatchAction(_action, data)
};
}

View file

@ -1,5 +1,19 @@
function loadMoreComments({state, api}) {
let page = 1;
if (state.pagination && state.pagination.page) {
page = state.pagination.page + 1;
}
const data = api.comments.browse({page});
return {
comments: [...data.comments, ...state.comments],
pagination: data.meta.pagination
};
}
const Actions = {
// Put your actions here
loadMoreComments
};
/** Handle actions in the App, returns updated state */

View file

@ -1,6 +1,6 @@
function Avatar() {
return (
<div class="avatar">
<div className="avatar">
<p>JW</p>
</div>
);

View file

@ -3,21 +3,23 @@ import Like from './Like';
import Reply from './Reply';
import More from './More';
function Comment() {
function Comment(props) {
const comment = props.comment;
return (
<div className="mb-8">
<div className="flex justify-between items-end mb-3">
<div>
<Avatar />
<h4 className="text-lg font-sans font-semibold mb-1">Someone's Name</h4>
<h6 className="text-sm text-gray-400 font-sans">Someone's Bio</h6>
<h4 className="text-lg font-sans font-semibold mb-1">{comment.member.name}</h4>
<h6 className="text-sm text-gray-400 font-sans">{comment.member.bio}</h6>
</div>
<div className="text-sm text-gray-400 font-sans font-normal">
2 mins ago
</div>
</div>
<div className="mb-4 font-sans leading-normal">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis erat vitae diam gravida accumsan vitae quis nisl. Donec luctus laoreet mauris, nec posuere turpis accumsan in. Proin sagittis magna quis vulputate tempus. Duis sagittis purus mattis enim condimentum, quis tempus est tristique.</p>
<p>{comment.html}</p>
</div>
<div className="flex">
<Like />

View file

@ -2,6 +2,9 @@ import React from 'react';
import AppContext from '../AppContext';
import NotSignedInBox from './NotSignedInBox';
import Form from './Form';
import TotalComments from './TotalComments';
import Comment from './Comment';
import Pagination from './Pagination';
class CommentsBox extends React.Component {
static contextType = AppContext;
@ -12,9 +15,21 @@ class CommentsBox extends React.Component {
}
render() {
const comments = !this.context.comments ? 'Loading...' : this.context.comments.map(comment => <Comment comment={comment} key={comment.id} />);
return (
<section>
{ this.context.member ? <Form /> : <NotSignedInBox /> }
<div className="flex justify-between items-center mb-3">
<h1 className="text-2xl font-sans font-medium">Members discussion</h1>
<TotalComments />
</div>
<Pagination />
<div>
{comments}
</div>
<div>
{ this.context.member ? <Form /> : <NotSignedInBox /> }
</div>
</section>
);
}

View file

@ -1,8 +1,5 @@
import React from 'react';
import AppContext from '../AppContext';
import TotalComments from './TotalComments';
import Comment from './Comment';
import Pagination from './Pagination';
class Form extends React.Component {
static contextType = AppContext;
@ -61,15 +58,7 @@ class Form extends React.Component {
render() {
return (
<form onSubmit={this.submitForm} className="comment-form">
<div className="flex justify-between items-center mb-3">
<h1 className="text-2xl font-sans font-medium">Members discussion</h1>
<TotalComments />
</div>
<Pagination />
<div>
<Comment />
<Comment />
</div>
<div>
<div>
<figure>

View file

@ -1,9 +1,39 @@
function Pagination() {
return (
<div className="w-full rounded-md border p-3 mb-3 font-sans text-sm text-center">
Show 32 more comments
</div>
);
import React from 'react';
import AppContext from '../AppContext';
class Pagination extends React.Component {
static contextType = AppContext;
constructor(props) {
super(props);
this.state = {
message: ''
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
this.context.onAction('loadMoreComments');
}
render() {
if (!this.context.pagination) {
return null;
}
const left = this.context.pagination.total - this.context.pagination.page * this.context.pagination.limit;
if (left <= 0) {
return null;
}
return (
<div className="w-full rounded-md border p-3 mb-3 font-sans text-sm text-center" onClick={this.loadMore}>
Show {left} more comments
</div>
);
}
}
export default Pagination;

View file

@ -32,7 +32,8 @@ function getSiteData() {
const apiKey = scriptTag.dataset.key;
const apiUrl = scriptTag.dataset.api;
const sentryDsn = scriptTag.dataset.sentryDsn;
return {siteUrl, apiKey, apiUrl, sentryDsn};
const postCommentId = scriptTag.dataset.commentId;
return {siteUrl, apiKey, apiUrl, sentryDsn, postCommentId};
}
return {};
}
@ -52,12 +53,12 @@ function setup({siteUrl}) {
function init() {
// const customSiteUrl = getSiteUrl();
const {siteUrl: customSiteUrl, sentryDsn} = getSiteData();
const {siteUrl: customSiteUrl, sentryDsn, postCommentId} = getSiteData();
const siteUrl = customSiteUrl || window.location.origin;
setup({siteUrl});
ReactDOM.render(
<React.StrictMode>
{<App siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn}/>}
{<App siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn} postCommentId={postCommentId} />}
</React.StrictMode>,
document.getElementById(ROOT_DIV_ID)
);

View file

@ -2,11 +2,15 @@ import {transformApiSiteData} from './helpers';
function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
const apiPath = 'members/api';
const commentsApiPath = 'comments/api';
function endpointFor({type, resource}) {
function endpointFor({type, resource, params = ''}) {
if (type === 'members') {
return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`;
}
if (type === 'comments') {
return `${siteUrl.replace(/\/$/, '')}/${commentsApiPath}/${resource}/${params}`;
}
}
function contentEndpointFor({resource, params = ''}) {
@ -75,6 +79,54 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
};
api.comments = {
browse({page}) {
const limit = 15;
const comments = (new Array(limit)).fill().map(() => {
return {
id: 'comment-' + Math.random() * 10000 + Date.now(),
member: {
avatar: '',
bio: 'CEO',
name: 'Just a name'
},
html: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mollis erat vitae diam gravida accumsan vitae quis nisl. Donec luctus laoreet mauris, nec posuere turpis accumsan in. Proin sagittis magna quis vulputate tempus. Duis sagittis purus mattis enim condimentum, quis tempus est tristique.'
};
});
// Temporary placeholder until we have a proper API
return {
comments,
meta: {
pagination: {
page: page,
limit,
pages: 3,
total: 15 * 3,
next: null,
prev: null
}
}
};
/*
const url = endpointFor({type: 'comments', resource: 'comment', params: '?limit=15&page='+page});
return makeRequest({
url,
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin'
}).then(function (res) {
if (res.ok) {
return res.json();
} else {
throw new Error('Failed to fetch comments');
}
});*/
}
};
api.init = async () => {
let [member] = await Promise.all([
api.member.sessionData()