client/home: view featured post, add search form
This commit is contained in:
parent
6d6cce20dd
commit
53fd6fb29b
10 changed files with 310 additions and 20 deletions
|
@ -3,6 +3,27 @@
|
|||
|
||||
h1
|
||||
margin-top: 0
|
||||
margin-bottom: 0.5em
|
||||
|
||||
.message
|
||||
margin-bottom: 2em
|
||||
|
||||
.post-container
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
nav
|
||||
margin-bottom: 0.5em
|
||||
a
|
||||
padding: 0.5em
|
||||
|
||||
form
|
||||
width: auto
|
||||
|
||||
aside
|
||||
margin-top: 0.2em
|
||||
margin-bottom: 1em
|
||||
|
||||
footer
|
||||
font-size: 80%
|
||||
|
|
|
@ -132,7 +132,6 @@ a .access-key
|
|||
.access-key
|
||||
text-decoration: underline
|
||||
.thumbnail
|
||||
vertical-align: middle
|
||||
width: 1.5em
|
||||
height: 1.5em
|
||||
margin: calc((2.3em - 1.5em) / 2)
|
||||
|
@ -161,15 +160,19 @@ a .access-key
|
|||
|
||||
.thumbnail
|
||||
/*background-image: attr(data-src url)*/ /* not available yet */
|
||||
vertical-align: middle
|
||||
background-repeat: no-repeat
|
||||
background-size: cover
|
||||
background-position: center
|
||||
display: inline-block
|
||||
|
||||
.thumbnail img
|
||||
opacity: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
img
|
||||
opacity: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
span>.thumbnail
|
||||
width: 20px
|
||||
height: 20px
|
||||
margin: 0 0.4em 0 0.4em
|
||||
|
||||
.flexbox-dummy
|
||||
height: 0 !important
|
||||
|
|
29
client/css/posts.styl
Normal file
29
client/css/posts.styl
Normal file
|
@ -0,0 +1,29 @@
|
|||
@import colors
|
||||
|
||||
.post-container
|
||||
text-align: center
|
||||
.post-content
|
||||
text-align: left
|
||||
margin: 0 auto
|
||||
max-height: 100%
|
||||
max-width: 100%
|
||||
object-fit: contain
|
||||
position: relative
|
||||
|
||||
img, object, video, .post-overlay
|
||||
position: absolute
|
||||
height: 100%
|
||||
width: 100%
|
||||
left: 0
|
||||
right: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
|
||||
.post-overlay>*
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
width: 100%
|
||||
height: 100%
|
|
@ -2,7 +2,53 @@
|
|||
<div class='messages'></div>
|
||||
<header>
|
||||
<h1><%= ctx.name %></h1>
|
||||
<p>Serving <%= ctx.postCount %> posts (<%= ctx.makeFileSize(ctx.diskUsage) %>)</p>
|
||||
</header>
|
||||
<footer>Version: <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%= ctx.version %></a> (built <%= ctx.makeRelativeTime(ctx.buildDate) %>)</footer>
|
||||
<nav>
|
||||
<ul>
|
||||
<% if (ctx.canListPosts) { %>
|
||||
<li><a href='/posts'><%= ctx.makeAccessKey('Posts', 'P') %></a></li>
|
||||
<% } %>
|
||||
<% if (ctx.canListComments) { %>
|
||||
<li><a href='/comments'><%= ctx.makeAccessKey('Comments', 'C') %></a></li>
|
||||
<% } %>
|
||||
<% if (ctx.canListTags) { %>
|
||||
<li><a href='/tags'><%= ctx.makeAccessKey('Tags', 'T') %></a></li>
|
||||
<% } %>
|
||||
<% if (ctx.canListUsers) { %>
|
||||
<li><a href='/users'><%= ctx.makeAccessKey('Users', 'U') %></a></li>
|
||||
<% } %>
|
||||
<li><a href='/help'><%= ctx.makeAccessKey('Help', 'E') %></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<% if (ctx.canListPosts) { %>
|
||||
<form class='horizontal'>
|
||||
<div class='input'>
|
||||
<ul>
|
||||
<li>
|
||||
<%= ctx.makeTextInput({id: 'search-text', name: 'search-text'}) %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='buttons'>
|
||||
<input type='submit' value='Search'/>
|
||||
</div>
|
||||
</form>
|
||||
<% } %>
|
||||
<div class='post-container'></div>
|
||||
<% if (ctx.featuredPost) { %>
|
||||
<aside>
|
||||
<%= ctx.makePostLink(ctx.featuredPost.id) %>
|
||||
uploaded
|
||||
<%= ctx.makeRelativeTime(ctx.featuredPost.creationTime) %>
|
||||
by
|
||||
<%= ctx.makeUserLink(ctx.featuredPost.user) %>
|
||||
</aside>
|
||||
<% } %>
|
||||
<footer>
|
||||
Serving <%= ctx.postCount %> posts (<%= ctx.makeFileSize(ctx.diskUsage) %>)
|
||||
•
|
||||
Running <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%= ctx.version %></a>
|
||||
•
|
||||
Built <%= ctx.makeRelativeTime(ctx.buildDate) %>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
30
client/html/post_content.tpl
Normal file
30
client/html/post_content.tpl
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class='post-content post-type-<%= ctx.post.type %>'>
|
||||
<% if (['image', 'animation'].includes(ctx.post.type)) { %>
|
||||
|
||||
<img alt='<%= ctx.post.id %>' src='<%= ctx.post.contentUrl %>'/>
|
||||
|
||||
<% } else if (ctx.post.type === 'flash') { %>
|
||||
|
||||
<object width='<%= ctx.post.canvasWidth %>' height='<%= ctx.post.canvasHeight %>' data='<%= ctx.post.contentUrl %>'>
|
||||
<param name='wmode' value='opaque'/>
|
||||
<param name='movie' value='<%= ctx.post.contentUrl %>'/>
|
||||
</object>
|
||||
|
||||
<% } else if (ctx.post.type === 'video') { %>
|
||||
|
||||
<% if (ctx.post.flags.includes('loop')) { %>
|
||||
<video id='video' controls loop='loop'>
|
||||
<% } else { %>
|
||||
<video id='video' controls>
|
||||
<% } %>
|
||||
|
||||
<source type='<%= ctx.post.mimeType %>' src='<%= ctx.post.contentUrl %>'/>
|
||||
|
||||
Your browser doesn't support HTML5 videos.
|
||||
</video>
|
||||
|
||||
<% } else { console.log(new Error('Unknown post type')); } %>
|
||||
|
||||
<div class='post-overlay'>
|
||||
</div>
|
||||
</div>
|
|
@ -24,13 +24,22 @@ class HomeController {
|
|||
api.get('/info')
|
||||
.then(response => {
|
||||
this._homeView.render({
|
||||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
canListComments: api.hasPrivilege('comments:list'),
|
||||
canListTags: api.hasPrivilege('tags:list'),
|
||||
canListUsers: api.hasPrivilege('users:list'),
|
||||
diskUsage: response.diskUsage,
|
||||
postCount: response.postCount,
|
||||
featuredPost: response.featuredPost,
|
||||
});
|
||||
},
|
||||
response => {
|
||||
this._homeView.render({});
|
||||
this._homeView.render({
|
||||
canListPosts: api.hasPrivilege('posts:list'),
|
||||
canListComments: api.hasPrivilege('comments:list'),
|
||||
canListTags: api.hasPrivilege('tags:list'),
|
||||
canListUsers: api.hasPrivilege('users:list'),
|
||||
});
|
||||
events.notify(events.Error, response.description);
|
||||
});
|
||||
}
|
||||
|
|
79
client/js/controls/post_content_control.js
Normal file
79
client/js/controls/post_content_control.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
const views = require('../util/views.js');
|
||||
const optimizedResize = require('../util/optimized_resize.js');
|
||||
|
||||
class PostContentControl {
|
||||
constructor(containerNode, post, viewportSizeCalculator) {
|
||||
post.canvasWidth = post.canvasWidth || 800;
|
||||
post.canvasHeight = post.canvasHeight || 450;
|
||||
|
||||
this._post = post;
|
||||
this._viewportSizeCalculator = viewportSizeCalculator;
|
||||
this._containerNode = containerNode;
|
||||
this._template = views.getTemplate('post-content');
|
||||
|
||||
this._install();
|
||||
|
||||
this._currentFitFunction = this.fitBoth;
|
||||
this._currentFitFunction();
|
||||
}
|
||||
|
||||
fitWidth() {
|
||||
this._currentFitFunction = this.fitWidth;
|
||||
const mul = this._post.canvasHeight / this._post.canvasWidth;
|
||||
this._resize(this._viewportWidth, this._viewportWidth * mul);
|
||||
}
|
||||
|
||||
fitHeight() {
|
||||
this._currentFitFunction = this.fitHeight;
|
||||
const mul = this._post.canvasWidth / this._post.canvasHeight;
|
||||
this._resize(this._viewportHeight * mul, this._viewportHeight);
|
||||
}
|
||||
|
||||
fitBoth() {
|
||||
this._currentFitFunction = this.fitBoth;
|
||||
let mul = this._post.canvasHeight / this._post.canvasWidth;
|
||||
if (this._viewportWidth * mul < this._viewportHeight) {
|
||||
this._resize(this._viewportWidth, this._viewportWidth * mul);
|
||||
} else {
|
||||
mul = this._post.canvasWidth / this._post.canvasHeight;
|
||||
this._resize(this._viewportHeight * mul, this._viewportHeight);
|
||||
}
|
||||
}
|
||||
|
||||
get _viewportWidth() {
|
||||
return this._viewportSizeCalculator()[0];
|
||||
}
|
||||
|
||||
get _viewportHeight() {
|
||||
return this._viewportSizeCalculator()[1];
|
||||
}
|
||||
|
||||
_resize(width, height) {
|
||||
const postContentNode =
|
||||
this._containerNode.querySelector('.post-content');
|
||||
postContentNode.style.width = width + 'px';
|
||||
postContentNode.style.height = height + 'px';
|
||||
}
|
||||
|
||||
_refreshSize() {
|
||||
this._currentFitFunction();
|
||||
}
|
||||
|
||||
_install() {
|
||||
const postContentNode = this._template({
|
||||
post: this._post,
|
||||
});
|
||||
this._containerNode.appendChild(postContentNode);
|
||||
optimizedResize.add(() => this._refreshSize());
|
||||
views.monitorNodeRemoval(
|
||||
this._containerNode, () => { this._uninstall(); });
|
||||
}
|
||||
|
||||
_uninstall() {
|
||||
optimizedResize.remove(() => this._refreshSize());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PostContentControl;
|
33
client/js/util/optimized_resize.js
Normal file
33
client/js/util/optimized_resize.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
|
||||
let callbacks = [];
|
||||
let running = false;
|
||||
|
||||
function resize() {
|
||||
if (!running) {
|
||||
running = true;
|
||||
if (window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame(runCallbacks);
|
||||
} else {
|
||||
setTimeout(runCallbacks, 66);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runCallbacks() {
|
||||
callbacks.forEach(function(callback) {
|
||||
callback();
|
||||
});
|
||||
running = false;
|
||||
}
|
||||
|
||||
function add(callback) {
|
||||
callbacks.push(callback);
|
||||
}
|
||||
|
||||
function remove(callback) {
|
||||
callbacks = callbacks.filter(c => c !== callback);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
module.exports = {add: add, remove: remove};
|
|
@ -141,19 +141,27 @@ function makeColorInput(options) {
|
|||
'label', {class: 'color'}, colorInput + textInput);
|
||||
}
|
||||
|
||||
function makePostLink(id) {
|
||||
return makeNonVoidElement('a', {
|
||||
'href': '/post/' + id,
|
||||
}, '@' + id);
|
||||
}
|
||||
|
||||
function makeTagLink(name) {
|
||||
let category = null;
|
||||
try {
|
||||
category = tags.getTagByName(name).category;
|
||||
} catch (e) {
|
||||
category = 'unknown';
|
||||
}
|
||||
const tag = tags.getTagByName(name);
|
||||
let category = tag ? tag.category : 'unknown';
|
||||
return makeNonVoidElement('a', {
|
||||
'href': '/tag/' + name,
|
||||
'class': 'tag-' + category,
|
||||
}, name);
|
||||
}
|
||||
|
||||
function makeUserLink(user) {
|
||||
return makeNonVoidElement('span', {class: 'user'},
|
||||
makeThumbnail(user.avatarUrl) +
|
||||
makeNonVoidElement('a', {'href': '/user/' + user.name}, user.name));
|
||||
}
|
||||
|
||||
function makeFlexboxAlign(options) {
|
||||
return Array.from(misc.range(20))
|
||||
.map(() => '<li class="flexbox-dummy"></li>').join('');
|
||||
|
@ -265,7 +273,9 @@ function getTemplate(templatePath) {
|
|||
makePasswordInput: makePasswordInput,
|
||||
makeEmailInput: makeEmailInput,
|
||||
makeColorInput: makeColorInput,
|
||||
makePostLink: makePostLink,
|
||||
makeTagLink: makeTagLink,
|
||||
makeUserLink: makeUserLink,
|
||||
makeFlexboxAlign: makeFlexboxAlign,
|
||||
makeAccessKey: makeAccessKey,
|
||||
});
|
||||
|
|
|
@ -1,25 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
const page = require('page');
|
||||
const config = require('../config.js');
|
||||
const misc = require('../util/misc.js');
|
||||
const views = require('../util/views.js');
|
||||
const PostContentControl = require('../controls/post_content_control.js');
|
||||
const TagAutoCompleteControl =
|
||||
require('../controls/tag_auto_complete_control.js');
|
||||
|
||||
class HomeView {
|
||||
constructor() {
|
||||
this._template = views.getTemplate('home');
|
||||
this._homeTemplate = views.getTemplate('home');
|
||||
}
|
||||
|
||||
render(ctx) {
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this._template({
|
||||
Object.assign(ctx, {
|
||||
name: config.name,
|
||||
version: config.meta.version,
|
||||
buildDate: config.meta.buildDate,
|
||||
diskUsage: ctx.diskUsage,
|
||||
postCount: ctx.postCount,
|
||||
});
|
||||
const target = document.getElementById('content-holder');
|
||||
const source = this._homeTemplate(ctx);
|
||||
|
||||
views.listenToMessages(source);
|
||||
views.showView(target, source);
|
||||
|
||||
const form = source.querySelector('form');
|
||||
if (form) {
|
||||
const searchTextInput = form.querySelector('input');
|
||||
new TagAutoCompleteControl(searchTextInput);
|
||||
form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
const text = searchTextInput.value;
|
||||
searchTextInput.blur();
|
||||
page('/posts/' + misc.formatSearchQuery({text: text}));
|
||||
});
|
||||
}
|
||||
|
||||
const postContainerNode = source.querySelector('.post-container');
|
||||
|
||||
if (postContainerNode && ctx.featuredPost) {
|
||||
new PostContentControl(
|
||||
postContainerNode,
|
||||
ctx.featuredPost,
|
||||
() => {
|
||||
return [
|
||||
window.innerWidth * 0.8,
|
||||
window.innerHeight * 0.7,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue