client/posts: add proof of concept for post list
This commit is contained in:
parent
28009bf46d
commit
f8e6d07fea
|
@ -1,5 +1,88 @@
|
|||
@import colors
|
||||
|
||||
.post-list
|
||||
ul
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
display: flex
|
||||
align-content: flex-end
|
||||
flex-wrap: wrap
|
||||
margin: 0 -0.25em
|
||||
|
||||
li
|
||||
position: relative
|
||||
flex-grow: 1
|
||||
margin: 0 0.25em 0.5em 0.25em
|
||||
display: inline-block
|
||||
text-align: left
|
||||
min-width: 10em
|
||||
width: 15vw
|
||||
&:not(.flexbox-dummy)
|
||||
min-height: 7.5em
|
||||
height: 11.25vw
|
||||
|
||||
a
|
||||
display: inline-block
|
||||
width: 100%
|
||||
height: 100%
|
||||
line-height: 100%
|
||||
font-size: 80%
|
||||
color: white
|
||||
|
||||
.type
|
||||
position: absolute
|
||||
left: -1px
|
||||
bottom: -1px
|
||||
padding: 1em 4em 0.5em 0.5em
|
||||
background-image: radial-gradient(
|
||||
ellipse 100% 100% at bottom left,
|
||||
rgba(0,0,0,0.5),
|
||||
rgba(0,0,0,0));
|
||||
&[data-type=image]
|
||||
display: none
|
||||
|
||||
.stats
|
||||
position: absolute
|
||||
right: -1px
|
||||
bottom: -1px
|
||||
text-align: right
|
||||
/*padding: 0.5em
|
||||
background: rgba(0,0,0,0.5);*/
|
||||
padding: 1em 0.5em 0.5em 4em
|
||||
background-image: radial-gradient(
|
||||
ellipse 100% 100% at bottom right,
|
||||
rgba(0,0,0,0.5),
|
||||
rgba(0,0,0,0));
|
||||
i
|
||||
margin-right: 0.5em
|
||||
.icon:not(:first-of-type)
|
||||
margin-left: 1em
|
||||
|
||||
.thumbnail
|
||||
border: 1px solid $inactive-link-color
|
||||
background-position: 50% 30%
|
||||
width: 100%
|
||||
height: 100%
|
||||
margin: 0 0.6em 0 0
|
||||
&:hover
|
||||
background: $main-color
|
||||
.thumbnail
|
||||
opacity: .9
|
||||
border: 1px solid $main-color
|
||||
outline: 2px solid $main-color
|
||||
|
||||
.post-list-header
|
||||
text-align: left
|
||||
form
|
||||
width: auto
|
||||
input[name=search-text]
|
||||
width: 25em
|
||||
max-width: 90vw
|
||||
.append
|
||||
font-size: 0.95em
|
||||
color: $inactive-link-color
|
||||
|
||||
.post-container
|
||||
text-align: center
|
||||
.post-content
|
||||
|
|
|
@ -69,12 +69,11 @@
|
|||
.user-list
|
||||
ul
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
display: flex
|
||||
align-content: flex-end
|
||||
flex-wrap: wrap
|
||||
margin: 0 -0.5em 0 -0.5em
|
||||
margin: 0 -0.5em
|
||||
li
|
||||
flex-grow: 1
|
||||
width: 20em
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<div class='post-list-header'>
|
||||
<form class='horizontal'>
|
||||
<div class='input'>
|
||||
<ul>
|
||||
<li>
|
||||
<%= ctx.makeTextInput({id: 'search-text', name: 'search-text', value: ctx.searchQuery.text}) %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='buttons'>
|
||||
<input type='submit' value='Search'/>
|
||||
<a class='button append' href='/help/search/posts'>Syntax help</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,39 @@
|
|||
<div class='post-list'>
|
||||
<% if (ctx.results.length) { %>
|
||||
<ul>
|
||||
<% _.each(ctx.results, post => { %>
|
||||
<li>
|
||||
<a href='/post/<%= post.id %>' title='@<%= post.id %> (<%= post.type %>) Tags: <%= post.tags.map(tag => '#' + tag).join(' ') %>'>
|
||||
<%= ctx.makeThumbnail(post.thumbnailUrl) %>
|
||||
<span class='type' data-type='<%= post.type %>'>
|
||||
<%= post.type %>
|
||||
</span>
|
||||
<% if (post.score || post.favoriteCount || post.commentCount) { %>
|
||||
<span class='stats'>
|
||||
<% if (post.score) { %>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-star'></i>
|
||||
<%= post.score %>
|
||||
</span>
|
||||
<% } %>
|
||||
<% if (post.favoriteCount) { %>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-heart'></i>
|
||||
<%= post.favoriteCount %>
|
||||
</span>
|
||||
<% } %>
|
||||
<% if (post.commentCount) { %>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-commenting'></i>
|
||||
<%= post.commentCount %>
|
||||
</span>
|
||||
<% } %>
|
||||
</span>
|
||||
<% } %>
|
||||
</a>
|
||||
</li>
|
||||
<% }) %>
|
||||
<%= ctx.makeFlexboxAlign() %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</div>
|
|
@ -2,15 +2,24 @@
|
|||
|
||||
const misc = require('../util/misc.js');
|
||||
const page = require('page');
|
||||
const api = require('../api.js');
|
||||
const topNavController = require('../controllers/top_nav_controller.js');
|
||||
const pageController = require('../controllers/page_controller.js');
|
||||
const PostsHeaderView = require('../views/posts_header_view.js');
|
||||
const PostsPageView = require('../views/posts_page_view.js');
|
||||
const EmptyView = require('../views/empty_view.js');
|
||||
|
||||
class PostsController {
|
||||
constructor() {
|
||||
this._postsHeaderView = new PostsHeaderView();
|
||||
this._postsPageView = new PostsPageView();
|
||||
}
|
||||
|
||||
registerRoutes() {
|
||||
page('/upload', (ctx, next) => { this._uploadPostsRoute(); });
|
||||
page('/posts/:query?',
|
||||
(ctx, next) => { misc.parseSearchQueryRoute(ctx, next); },
|
||||
(ctx, next) => { this._listPostsRoute(); });
|
||||
(ctx, next) => { this._listPostsRoute(ctx); });
|
||||
page(
|
||||
'/post/:id',
|
||||
(ctx, next) => { this._showPostRoute(ctx.params.id); });
|
||||
|
@ -25,9 +34,23 @@ class PostsController {
|
|||
this._emptyView.render();
|
||||
}
|
||||
|
||||
_listPostsRoute() {
|
||||
_listPostsRoute(ctx) {
|
||||
topNavController.activate('posts');
|
||||
this._emptyView.render();
|
||||
|
||||
pageController.run({
|
||||
state: ctx.state,
|
||||
requestPage: page => {
|
||||
const text = ctx.searchQuery.text;
|
||||
return api.get(
|
||||
`/posts/?query=${text}&page=${page}&pageSize=40&_fields=` +
|
||||
`id,type,tags,score,favoriteCount,commentCount,thumbnailUrl`);
|
||||
},
|
||||
clientUrl: '/posts/' + misc.formatSearchQuery({
|
||||
text: ctx.searchQuery.text, page: '{page}'}),
|
||||
searchQuery: ctx.searchQuery,
|
||||
headerRenderer: this._postsHeaderView,
|
||||
pageRenderer: this._postsPageView,
|
||||
});
|
||||
}
|
||||
|
||||
_showPostRoute(id) {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
const page = require('page');
|
||||
const keyboard = require('../util/keyboard.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const TagAutoCompleteControl =
|
||||
require('../controls/tag_auto_complete_control.js');
|
||||
|
||||
class PostsHeaderView {
|
||||
constructor() {
|
||||
this._template = views.getTemplate('posts-header');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this._template(ctx);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
const searchTextInput = form.querySelector('[name=search-text]');
|
||||
|
||||
if (searchTextInput) {
|
||||
new TagAutoCompleteControl(searchTextInput);
|
||||
}
|
||||
|
||||
keyboard.bind('q', () => {
|
||||
form.querySelector('input').focus();
|
||||
});
|
||||
|
||||
form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
const text = searchTextInput.value;
|
||||
searchTextInput.blur();
|
||||
page('/posts/' + misc.formatSearchQuery({text: text}));
|
||||
});
|
||||
|
||||
views.showView(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PostsHeaderView;
|
|
@ -0,0 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
const views = require('../util/views.js');
|
||||
|
||||
class PostsPageView {
|
||||
constructor() {
|
||||
this._template = views.getTemplate('posts-page');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = ctx.target;
|
||||
const source = this._template(ctx);
|
||||
views.showView(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PostsPageView;
|
Loading…
Reference in New Issue