client/posts: add proof of concept for post list

This commit is contained in:
rr- 2016-06-02 00:07:51 +02:00
parent 28009bf46d
commit f8e6d07fea
7 changed files with 222 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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 %>)&#10;&#10;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>

View File

@ -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) {

View File

@ -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;

View File

@ -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;