Lazy Home: Lazy load latest blog posts and assets and group by week and
project. Javascript tutti.js and timeline.js is needed, and then the following to init the timeline: $('.timeline') .timeline({ url: '/api/timeline' }); # Javascript Notes: ## ES6 transpile: * Files in src/scripts/js/es6/common will be transpiled from modern es6 js to old es5 js, and then added to tutti.js * Files in src/scripts/js/es6/individual will be transpiled from modern es6 js to old es5 js to individual module files ## JS Testing * Added the Jest test framework to write javascript tests. * `npm test` will run all the javascript tests Thanks to Sybren for reviewing
This commit is contained in:
@@ -42,36 +42,6 @@ def _homepage_context() -> dict:
|
|||||||
|
|
||||||
# Get latest blog posts
|
# Get latest blog posts
|
||||||
api = system_util.pillar_api()
|
api = system_util.pillar_api()
|
||||||
latest_posts = Node.all({
|
|
||||||
'projection': {
|
|
||||||
'name': 1,
|
|
||||||
'project': 1,
|
|
||||||
'node_type': 1,
|
|
||||||
'picture': 1,
|
|
||||||
'properties.url': 1,
|
|
||||||
'properties.content': 1,
|
|
||||||
'properties.attachments': 1,
|
|
||||||
'properties.status': 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
'where': {'node_type': 'post', 'properties.status': 'published'},
|
|
||||||
'embedded': {'project': 1},
|
|
||||||
'sort': '-_created',
|
|
||||||
'max_results': '3'
|
|
||||||
}, api=api)
|
|
||||||
|
|
||||||
# Append picture Files to last_posts
|
|
||||||
for post in latest_posts._items:
|
|
||||||
post.picture = get_file(post.picture, api=api)
|
|
||||||
post.url = url_for_node(node=post)
|
|
||||||
|
|
||||||
# Get latest assets added to any project
|
|
||||||
latest_assets = Node.latest('assets', api=api)
|
|
||||||
|
|
||||||
# Append picture Files to latest_assets
|
|
||||||
for asset in latest_assets._items:
|
|
||||||
asset.picture = get_file(asset.picture, api=api)
|
|
||||||
asset.url = url_for_node(node=asset)
|
|
||||||
|
|
||||||
# Get latest comments to any node
|
# Get latest comments to any node
|
||||||
latest_comments = Node.latest('comments', api=api)
|
latest_comments = Node.latest('comments', api=api)
|
||||||
@@ -116,20 +86,9 @@ def _homepage_context() -> dict:
|
|||||||
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
|
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
|
||||||
main_project.picture_header = get_file(main_project.picture_header, api=api)
|
main_project.picture_header = get_file(main_project.picture_header, api=api)
|
||||||
|
|
||||||
# Merge latest assets and comments into one activity stream.
|
|
||||||
def sort_key(item):
|
|
||||||
return item._created
|
|
||||||
|
|
||||||
activity_stream = sorted(latest_assets._items, key=sort_key, reverse=True)
|
|
||||||
|
|
||||||
for node in activity_stream:
|
|
||||||
node.url = url_for_node(node=node)
|
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
main_project=main_project,
|
main_project=main_project,
|
||||||
latest_posts=latest_posts._items,
|
|
||||||
latest_comments=latest_comments._items,
|
latest_comments=latest_comments._items,
|
||||||
activity_stream=activity_stream,
|
|
||||||
random_featured=random_featured)
|
random_featured=random_featured)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,162 +2,27 @@
|
|||||||
* Support for fetching & rendering assets by tags.
|
* Support for fetching & rendering assets by tags.
|
||||||
*/
|
*/
|
||||||
(function($) {
|
(function($) {
|
||||||
/* How many nodes to load initially, and when clicked on the 'Load Next' link. */
|
$.fn.loadTaggedAssets = function(load_initial_count, load_next_count, has_subscription) {
|
||||||
const LOAD_INITIAL_COUNT = 8;
|
|
||||||
const LOAD_NEXT_COUNT = 8;
|
|
||||||
var mark_if_public = true;
|
|
||||||
|
|
||||||
/* Renders a node as an asset card, returns a jQuery object. */
|
|
||||||
function renderAsset(node) {
|
|
||||||
let card = $('<a class="card asset card-image-fade pr-0 mx-0 mb-2">')
|
|
||||||
.addClass('js-tagged-asset')
|
|
||||||
.attr('href', '/nodes/' + node._id + '/redir')
|
|
||||||
.attr('title', node.name);
|
|
||||||
|
|
||||||
let thumbnail_container = $('<div class="embed-responsive embed-responsive-16by9">');
|
|
||||||
|
|
||||||
function warnNoPicture() {
|
|
||||||
let card_icon = $('<div class="card-img-top card-icon embed-responsive-item">');
|
|
||||||
card_icon.html('<i class="pi-' + node.node_type + '">');
|
|
||||||
thumbnail_container.append(card_icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.picture) {
|
|
||||||
warnNoPicture();
|
|
||||||
} else {
|
|
||||||
$(window).trigger('pillar:workStart');
|
|
||||||
|
|
||||||
// TODO: show 'loading' thingy
|
|
||||||
$.get('/api/files/' + node.picture)
|
|
||||||
.fail(function(error) {
|
|
||||||
let msg = xhrErrorResponseMessage(error);
|
|
||||||
console.log(msg);
|
|
||||||
})
|
|
||||||
.done(function(resp) {
|
|
||||||
|
|
||||||
// Render the picture if it has the proper size.
|
|
||||||
var show_variation = null;
|
|
||||||
if (typeof resp.variations != 'undefined') {
|
|
||||||
for (variation of resp.variations) {
|
|
||||||
if (variation.size != 'm') continue;
|
|
||||||
show_variation = variation;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_variation == null) {
|
|
||||||
warnNoPicture();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let img = $('<img class="card-img-top embed-responsive-item">')
|
|
||||||
.attr('alt', node.name)
|
|
||||||
.attr('src', variation.link)
|
|
||||||
.attr('width', variation.width)
|
|
||||||
.attr('height', variation.height);
|
|
||||||
thumbnail_container.append(img);
|
|
||||||
})
|
|
||||||
.always(function(){
|
|
||||||
$(window).trigger('pillar:workStop');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
card.append(thumbnail_container);
|
|
||||||
|
|
||||||
/* Card body for title and meta info. */
|
|
||||||
let card_body = $('<div class="card-body py-2 d-flex flex-column">');
|
|
||||||
let card_title = $('<div class="card-title mb-1 font-weight-bold">');
|
|
||||||
card_title.text(node.name);
|
|
||||||
card_body.append(card_title);
|
|
||||||
|
|
||||||
let card_meta = $('<ul class="card-text list-unstyled d-flex text-black-50 mt-auto">');
|
|
||||||
let card_project = $('<a class="font-weight-bold pr-2">')
|
|
||||||
.attr('href', '/p/' + node.project.url)
|
|
||||||
.attr('title', node.project.name)
|
|
||||||
.text(node.project.name);
|
|
||||||
|
|
||||||
card_meta.append(card_project);
|
|
||||||
card_meta.append('<li>' + node.pretty_created + '</li>');
|
|
||||||
card_body.append(card_meta);
|
|
||||||
|
|
||||||
/* Video progress and 'watched' label. */
|
|
||||||
if (node.view_progress){
|
|
||||||
let card_progress = $('<div class="progress rounded-0">');
|
|
||||||
let card_progress_bar = $('<div class="progress-bar">');
|
|
||||||
card_progress_bar.css('width', node.view_progress.progress_in_percent + '%');
|
|
||||||
card_progress.append(card_progress_bar);
|
|
||||||
thumbnail_container.append(card_progress);
|
|
||||||
|
|
||||||
if (node.view_progress.done){
|
|
||||||
let card_progress_done = $('<div class="card-label">WATCHED</div>');
|
|
||||||
thumbnail_container.append(card_progress_done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.properties.duration){
|
|
||||||
let card_duration = $('<div class="card-label right">' + node.properties.duration + '</div>');
|
|
||||||
thumbnail_container.append(card_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 'Free' ribbon for public assets. */
|
|
||||||
if (mark_if_public && node.permissions && node.permissions.world){
|
|
||||||
card.addClass('free');
|
|
||||||
}
|
|
||||||
|
|
||||||
card.append(card_body);
|
|
||||||
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadNext(card_deck_element) {
|
|
||||||
let $card_deck = $(card_deck_element);
|
|
||||||
let tagged_assets = card_deck_element.tagged_assets; // Stored here by loadTaggedAssets().
|
|
||||||
let already_loaded = $card_deck.find('a.js-tagged-asset').length;
|
|
||||||
|
|
||||||
let load_next = $card_deck.find('a.js-load-next');
|
|
||||||
|
|
||||||
let nodes_to_load = tagged_assets.slice(already_loaded, already_loaded + LOAD_NEXT_COUNT);
|
|
||||||
for (node of nodes_to_load) {
|
|
||||||
let link = renderAsset(node);
|
|
||||||
load_next.before(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (already_loaded + LOAD_NEXT_COUNT >= tagged_assets.length)
|
|
||||||
load_next.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
$.fn.loadTaggedAssets = function(LOAD_INITIAL_COUNT, LOAD_NEXT_COUNT, has_subscription) {
|
|
||||||
mark_if_public = !has_subscription;
|
mark_if_public = !has_subscription;
|
||||||
this.each(function(index, card_deck_element) {
|
this.each(function(index, each) {
|
||||||
// TODO(Sybren): show a 'loading' animation.
|
let $card_deck_element = $(each)
|
||||||
$.get('/api/nodes/tagged/' + card_deck_element.dataset.assetTag)
|
$card_deck_element.trigger('pillar:workStart');
|
||||||
|
$.get('/api/nodes/tagged/' + $card_deck_element.data('assetTag'))
|
||||||
.fail(function(error) {
|
.fail(function(error) {
|
||||||
let msg = xhrErrorResponseMessage(error);
|
let msg = xhrErrorResponseMessage(error);
|
||||||
$('<a>').addClass('bg-danger').text(msg).appendTo(card_deck_element);
|
$card_deck_element
|
||||||
|
.append(
|
||||||
|
$('<p>').addClass('bg-danger').text(msg)
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.done(function(resp) {
|
.done(function(resp) {
|
||||||
// 'resp' is a list of node documents.
|
// 'resp' is a list of node documents.
|
||||||
// Store the response on the DOM card_deck_element so that we can later render more.
|
$card_deck_element.append(
|
||||||
card_deck_element.tagged_assets = resp;
|
pillar.templates.Nodes.createListOf$nodeItems(resp, load_initial_count, load_next_count)
|
||||||
|
);
|
||||||
// Here render the first N.
|
})
|
||||||
for (node of resp.slice(0, LOAD_INITIAL_COUNT)) {
|
.always(function() {
|
||||||
let li = renderAsset(node);
|
$card_deck_element.trigger('pillar:workStop');
|
||||||
li.appendTo(card_deck_element);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't bother with a 'load next' link if there is no more.
|
|
||||||
if (resp.length <= LOAD_INITIAL_COUNT) return;
|
|
||||||
|
|
||||||
if (LOAD_NEXT_COUNT > 0) {
|
|
||||||
// Construct the 'load next' link.
|
|
||||||
let link = $('<a class="btn btn-outline-primary px-5 mb-auto mx-3 btn-block">')
|
|
||||||
.addClass('js-load-next')
|
|
||||||
.attr('href', 'javascript:void(0);')
|
|
||||||
.click(function() { loadNext(card_deck_element); return false; })
|
|
||||||
.text('Load More');
|
|
||||||
link.appendTo(card_deck_element);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
body.homepage
|
|
||||||
.blog
|
|
||||||
.jumbotron
|
|
||||||
padding-top: 6em
|
|
||||||
padding-bottom: 6em
|
|
||||||
|
|
||||||
*
|
|
||||||
font-size: $h1-font-size
|
|
||||||
|
|
||||||
.lead
|
|
||||||
font-size: $font-size-base
|
|
@@ -71,6 +71,7 @@
|
|||||||
@import "../../../pillar/src/styles/components/checkbox"
|
@import "../../../pillar/src/styles/components/checkbox"
|
||||||
@import "../../../pillar/src/styles/components/overlay"
|
@import "../../../pillar/src/styles/components/overlay"
|
||||||
@import "../../../pillar/src/styles/components/card"
|
@import "../../../pillar/src/styles/components/card"
|
||||||
|
@import "../../../pillar/src/styles/components/timeline"
|
||||||
|
|
||||||
@import "../../../pillar/src/styles/comments"
|
@import "../../../pillar/src/styles/comments"
|
||||||
@import "../../../pillar/src/styles/notifications"
|
@import "../../../pillar/src/styles/notifications"
|
||||||
@@ -82,7 +83,6 @@
|
|||||||
@import "../../../pillar/src/styles/_user"
|
@import "../../../pillar/src/styles/_user"
|
||||||
|
|
||||||
@import _welcome
|
@import _welcome
|
||||||
@import _homepage
|
|
||||||
@import _services
|
@import _services
|
||||||
@import _about
|
@import _about
|
||||||
@import "../../../pillar/src/styles/_search"
|
@import "../../../pillar/src/styles/_search"
|
||||||
|
@@ -29,26 +29,19 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
|||||||
.container-fluid.dashboard-container.imgs-fluid
|
.container-fluid.dashboard-container.imgs-fluid
|
||||||
.row
|
.row
|
||||||
.col-md-8.col-xl-9
|
.col-md-8.col-xl-9
|
||||||
section.blog
|
section.timeline
|
||||||
| {% if latest_posts %}
|
|
||||||
| {% for node in latest_posts %}
|
|
||||||
| {{ render_blog_post(node) }}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% else %}
|
|
||||||
| No blog entries... yet!
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
.d-block.text-center
|
.d-block.text-center
|
||||||
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
|
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
|
||||||
| See All Blog Posts
|
| See All Blog Posts
|
||||||
|
|
||||||
a.d-inline-block.p-3.text-muted(
|
a.d-inline-block.p-3.text-muted(
|
||||||
href="{{ url_for('main.feeds_blogs') }}",
|
href="{{ url_for('main.feeds_blogs') }}",
|
||||||
title="Blogs Feed",
|
title="Blogs Feed",
|
||||||
data-toggle="tooltip",
|
data-toggle="tooltip",
|
||||||
data-placement="left")
|
data-placement="left")
|
||||||
i.pi-rss
|
i.pi-rss
|
||||||
| RSS Feed
|
| RSS Feed
|
||||||
|
|
||||||
.col-md-4.col-xl-3
|
.col-md-4.col-xl-3
|
||||||
section.pt-3
|
section.pt-3
|
||||||
@@ -64,23 +57,6 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
|||||||
p.text-muted.pt-2.
|
p.text-muted.pt-2.
|
||||||
A poetic short film about a mountain spirit and her wise little dog. #[a.text-muted(href="/p/spring/") Check it out].
|
A poetic short film about a mountain spirit and her wise little dog. #[a.text-muted(href="/p/spring/") Check it out].
|
||||||
|
|
||||||
section.py-3
|
|
||||||
h6.title-underline What's Going On
|
|
||||||
|
|
||||||
| {% if activity_stream %}
|
|
||||||
+card-deck()(class='card-deck-vertical pl-3')
|
|
||||||
| {% for child in activity_stream %}
|
|
||||||
| {% if child.node_type not in ['comment'] %}
|
|
||||||
| {{ asset_list_item(child, current_user) }}
|
|
||||||
| {% endif %}
|
|
||||||
| {% endfor %}
|
|
||||||
| {% else %}
|
|
||||||
.card
|
|
||||||
.card-body
|
|
||||||
h6.card-title
|
|
||||||
| No assets.
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
section.py-3.border-bottom.mb-3
|
section.py-3.border-bottom.mb-3
|
||||||
h6.title-underline
|
h6.title-underline
|
||||||
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
|
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
|
||||||
@@ -143,6 +119,11 @@ script.
|
|||||||
$(this).text($(this).text().replace(/\*|\@|\<(.*?)\>/g, ''));
|
$(this).text($(this).text().replace(/\*|\@|\<(.*?)\>/g, ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.timeline')
|
||||||
|
.timeline({
|
||||||
|
url: '/api/timeline'
|
||||||
|
});
|
||||||
|
|
||||||
hopToTop(); // Display jump to top button
|
hopToTop(); // Display jump to top button
|
||||||
});
|
});
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
@@ -32,6 +32,7 @@ html(lang="en")
|
|||||||
| {% endblock og %}
|
| {% endblock og %}
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/timeline.min.js') }}")
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
||||||
| {% if current_user.is_authenticated %}
|
| {% if current_user.is_authenticated %}
|
||||||
|
Reference in New Issue
Block a user