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:
parent
c6fb93d280
commit
65ac303308
10 changed files with 155 additions and 33 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
function Avatar() {
|
||||
return (
|
||||
<div class="avatar">
|
||||
<div className="avatar">
|
||||
<p>JW</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue