summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2018-08-09 17:20:10 +0200
committerqsm-odoo <qsm@odoo.com>2018-08-12 18:04:27 +0200
commitaf30c2ee6136e2f6e693881fa4e81af76d7cb215 (patch)
tree3c27738516c259e87376c2d1b196c8c329eb69df /addons
parent8517a3c3353bbfa5db596efbec226b8cd557f67e (diff)
[IMP] website_blog: integrate snippet latest posts
Moved from the themes repository. Now, users can use a snippet which showcases the latest blog posts as soon as they install the blog app. Note: in the future we may want to develop a snippet which can showcase any model records (products, projects, ...). task-1874216
Diffstat (limited to 'addons')
-rw-r--r--addons/website_blog/controllers/main.py5
-rw-r--r--addons/website_blog/static/src/img/s_latest_posts.jpgbin0 -> 3402 bytes
-rw-r--r--addons/website_blog/static/src/img/s_latest_posts_big_picture.jpgbin0 -> 4021 bytes
-rw-r--r--addons/website_blog/static/src/js/s_latest_posts_editor.js106
-rw-r--r--addons/website_blog/static/src/js/s_latest_posts_frontend.js142
-rw-r--r--addons/website_blog/static/src/scss/primary_variables.scss5
-rw-r--r--addons/website_blog/static/src/scss/website_blog.scss119
-rw-r--r--addons/website_blog/views/snippets.xml38
-rw-r--r--addons/website_blog/views/website_blog_templates.xml66
9 files changed, 481 insertions, 0 deletions
diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py
index 8adb6c5b9e6..f16f6ed761b 100644
--- a/addons/website_blog/controllers/main.py
+++ b/addons/website_blog/controllers/main.py
@@ -293,3 +293,8 @@ class WebsiteBlog(http.Controller):
if not post_id:
return False
return request.env['blog.post'].browse(int(post_id)).write({'cover_properties': json.dumps(cover_properties)})
+
+ @http.route(['/blog/render_latest_posts'], type='json', auth='public', website=True)
+ def render_latest_posts(self, template, domain, limit=None, order='published_date desc'):
+ posts = request.env['blog.post'].search(domain, limit=limit, order=order)
+ return request.env.ref(template).render({'posts': posts})
diff --git a/addons/website_blog/static/src/img/s_latest_posts.jpg b/addons/website_blog/static/src/img/s_latest_posts.jpg
new file mode 100644
index 00000000000..87d14400e43
--- /dev/null
+++ b/addons/website_blog/static/src/img/s_latest_posts.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/s_latest_posts_big_picture.jpg b/addons/website_blog/static/src/img/s_latest_posts_big_picture.jpg
new file mode 100644
index 00000000000..38d2ca15a39
--- /dev/null
+++ b/addons/website_blog/static/src/img/s_latest_posts_big_picture.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/js/s_latest_posts_editor.js b/addons/website_blog/static/src/js/s_latest_posts_editor.js
new file mode 100644
index 00000000000..10052de87b7
--- /dev/null
+++ b/addons/website_blog/static/src/js/s_latest_posts_editor.js
@@ -0,0 +1,106 @@
+odoo.define('website_blog.s_latest_posts_editor', function (require) {
+'use strict';
+
+var core = require('web.core');
+var sOptions = require('web_editor.snippets.options');
+
+var _t = core._t;
+
+sOptions.registry.js_get_posts_limit = sOptions.Class.extend({
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * @see this.selectClass for parameters
+ */
+ postsLimit: function (previewMode, value, $opt) {
+ value = parseInt(value);
+ this.$target.attr('data-posts-limit', value).data('postsLimit', value);
+ this.trigger_up('animation_start_demand', {
+ editableMode: true,
+ $target: this.$target,
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _setActive: function () {
+ var self = this;
+ this._super.apply(this, arguments);
+ this.$('[data-posts-limit]').addBack('[data-posts-limit]')
+ .removeClass('active')
+ .filter(function () {
+ return (self.$target.data('postsLimit') || 3) == $(this).data('postsLimit');
+ })
+ .addClass('active');
+ },
+});
+
+sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+
+ var def = this._rpc({
+ model: 'blog.blog',
+ method: 'search_read',
+ args: [[], ['name', 'id']],
+ }).then(function (blogs) {
+ var $menu = self.$el.find('[data-filter-by-blog-id="0"]').parent();
+ _.each(blogs, function (blog) {
+ $menu.append($('<a/>', {
+ class: 'dropdown-item',
+ 'data-filter-by-blog-id': blog.id,
+ 'data-no-preview': 'true',
+ text: blog.name,
+ }));
+ });
+ });
+
+ return $.when(this._super.apply(this, arguments), def);
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * @see this.selectClass for parameters
+ */
+ filterByBlogId: function (previewMode, value, $opt) {
+ value = parseInt(value);
+ this.$target.attr('data-filter-by-blog-id', value).data('filterByBlogId', value);
+ this.trigger_up('animation_start_demand', {
+ editableMode: true,
+ $target: this.$target,
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _setActive: function () {
+ var self = this;
+ this._super.apply(this, arguments);
+ this.$('[data-filter-by-blog-id]').addBack('[data-filter-by-blog-id]')
+ .removeClass('active')
+ .filter(function () {
+ return (self.$target.data('filterByBlogId') || 0) == $(this).data('filterByBlogId');
+ })
+ .addClass('active');
+ },
+});
+});
diff --git a/addons/website_blog/static/src/js/s_latest_posts_frontend.js b/addons/website_blog/static/src/js/s_latest_posts_frontend.js
new file mode 100644
index 00000000000..f1b8ea4f23d
--- /dev/null
+++ b/addons/website_blog/static/src/js/s_latest_posts_frontend.js
@@ -0,0 +1,142 @@
+odoo.define('website_blog.s_latest_posts_frontend', function (require) {
+'use strict';
+
+var core = require('web.core');
+var sAnimation = require('website.content.snippets.animation');
+
+var _t = core._t;
+
+sAnimation.registry.js_get_posts = sAnimation.Class.extend({
+ selector : '.js_get_posts',
+
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+ var limit = self.$target.data('postsLimit') || 3;
+ var blogID = self.$target.data('filterByBlogId');
+ var template = self.$target.data('template') || 'website_blog.s_latest_posts_list_template';
+ var loading = self.$target.data('loading');
+
+ this.$target.empty(); // Compatibility with db that saved content inside by mistake
+ this.$target.attr('contenteditable', 'False'); // Prevent user edition
+
+ var domain = [['website_published', '=', true]];
+ if (blogID) {
+ domain.push(['blog_id', '=', parseInt(blogID)]);
+ }
+
+ var def = $.Deferred();
+ this._rpc({
+ route: '/blog/render_latest_posts',
+ params: {
+ template: template,
+ domain: domain,
+ limit: limit,
+ },
+ }).then(function (posts) {
+ var $posts = $(posts).filter('.s_latest_posts_post');
+ if (!$posts.length) {
+ self.$target.append($('<div/>', {class: 'col-md-6 offset-md-3'})
+ .append($('<div/>', {
+ class: 'alert alert-warning alert-dismissible text-center',
+ text: _t("No blog post was found. Make sure your posts are published."),
+ })));
+ return;
+ }
+
+ if (loading && loading === true) {
+ // Perform an intro animation
+ self._showLoading($posts);
+ } else {
+ self.$target.html($posts);
+ }
+ }, function (e) {
+ if (self.editableMode) {
+ self.$target.append($('<p/>', {
+ class: 'text-danger',
+ text: _t("An error occured with this latest posts block. If the problem persists, please consider deleting it and adding a new one"),
+ }));
+ }
+ }).always(def.resolve.bind(def));
+ return $.when(this._super.apply(this, arguments), def);
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ this.$target.empty();
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _showLoading: function ($posts) {
+ var self = this;
+
+ _.each($posts, function (post) {
+ var $post = $(post);
+ var $loadingContainer = $post.find('.loading_container');
+ var $thumb = $post.find('.thumb .o_blog_cover_image');
+ var $progress = $('<div/>', {
+ class: 'progress js-loading',
+ }).append($('<div/>', {
+ class: 'progress-bar',
+ role: 'progressbar',
+ 'aria-valuenow': '0',
+ 'aria-valuemin': '0',
+ 'aria-valuemax': '100',
+ css: {
+ width: 0,
+ },
+ }));
+
+ // If can't find loading container or thumb inside the post, then they are the post itself
+ if (!$loadingContainer.length) {
+ $loadingContainer = $post;
+ }
+ if (!$thumb.length) {
+ $thumb = $post;
+ }
+
+ $post.addClass('js-loading');
+ $progress.appendTo($loadingContainer);
+ $post.appendTo(self.$target);
+
+ var m = $thumb.css('background-image').match(/url\(["']?(.+)["']?\)/);
+ var bg = m ? m[1] : 'none';
+ var loaded = false;
+
+ var $bar = $progress.find('.progress-bar');
+ $bar.css('width', '50%').attr('aria-valuenow', '50');
+
+ var $dummyImg = $('<img/>');
+
+ // Show the post after 5sec in any case
+ var timer = setTimeout(function () {
+ $dummyImg.remove();
+ $post.removeClass('js-loading');
+ $progress.hide();
+ }, 5000);
+
+ $dummyImg.load(function () {
+ $bar.css('width', '100%').attr('aria-valuenow', '100');
+ setTimeout(function () {
+ $post.removeClass('js-loading');
+ $progress.fadeOut(500);
+ }, 500);
+ $dummyImg.remove();
+ clearTimeout(timer);
+ });
+
+ $dummyImg.attr('src', bg);
+ });
+ },
+});
+});
diff --git a/addons/website_blog/static/src/scss/primary_variables.scss b/addons/website_blog/static/src/scss/primary_variables.scss
new file mode 100644
index 00000000000..f5cda7678dc
--- /dev/null
+++ b/addons/website_blog/static/src/scss/primary_variables.scss
@@ -0,0 +1,5 @@
+
+$s-latest-posts-big-picture-figure-bg: null;
+
+@mixin s-latest-posts-js-get-posts-hook {};
+@mixin s-latest-posts-figure-hook {};
diff --git a/addons/website_blog/static/src/scss/website_blog.scss b/addons/website_blog/static/src/scss/website_blog.scss
index cff15ed82e5..f16afd40bf2 100644
--- a/addons/website_blog/static/src/scss/website_blog.scss
+++ b/addons/website_blog/static/src/scss/website_blog.scss
@@ -232,3 +232,122 @@
padding: 2px 15px;
}
}
+
+//------------------------------------------------------------------------------
+// Latest posts snippets
+//------------------------------------------------------------------------------
+
+.s_latest_posts .media > a {
+ width: 100%;
+ height: 200px;
+
+ @include media-breakpoint-up(md) {
+ width: 100px;
+ height: 100px;
+ }
+
+ > .o_blog_cover_container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+
+ > .o_blog_cover_component {
+ @include o-position-absolute(0, 0, 0, 0);
+ }
+ > .o_blog_cover_image {
+ background-size: cover;
+ background-position: center;
+ }
+ }
+}
+
+$s-latest-posts-big-picture-figure-bg: $white !default;
+
+.s_latest_posts_big_picture {
+ .s_latest_posts_post {
+ position: relative;
+ height: 250px;
+ background: $s-latest-posts-big-picture-figure-bg;
+ text-align: center;
+ cursor: pointer;
+ transition: all 300ms;
+
+ &.js-loading {
+ background: gray('200') !important;
+ }
+
+ .thumb {
+ position: relative;
+ display: block;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ opacity: 0.8;
+
+ > .o_blog_cover_component {
+ @include o-position-absolute(0, 0, 0, 0);
+ }
+ > .o_blog_cover_image {
+ background-size: cover;
+ background-position: center;
+ }
+ }
+ > div, > div > a {
+ @include o-position-absolute(0, 0, 0, 0);
+ padding: 2em;
+ color: $white;
+ text-transform: uppercase;
+ font-size: 1.25em;
+ backface-visibility: hidden;
+ &::after, &::before {
+ pointer-events: none;
+ }
+
+ > a {
+ z-index: 1000;
+ text-indent: 200%;
+ white-space: nowrap;
+ font-size: 0;
+ opacity: 0;
+ }
+ h2 {
+ margin: 0;
+ font-size: 1.5em;
+ font-weight: 300;
+ span {
+ font-weight: 800;
+ }
+ }
+ p {
+ margin: 1em 0 0;
+ letter-spacing: 1px;
+ font-size: 68.5%;
+ }
+ }
+ @include s-latest-posts-figure-hook;
+ }
+
+ @include s-latest-posts-js-get-posts-hook;
+}
+
+.s_latest_posts, .s_latest_posts_big_picture {
+
+ .js_get_posts {
+ position: relative;
+ min-height: 100px;
+ }
+
+ .progress.js-loading {
+ @include o-position-absolute(0, 0, 0, 0);
+ border-radius: 0;
+
+ .progress-bar {
+ @include o-position-absolute(45%, 10%, auto, 10%);
+ height: 10px;
+ margin: 0;
+ background: gray('600');
+ box-shadow: none;
+ }
+ }
+}
diff --git a/addons/website_blog/views/snippets.xml b/addons/website_blog/views/snippets.xml
index 6366b9c5458..9bb7a22136b 100644
--- a/addons/website_blog/views/snippets.xml
+++ b/addons/website_blog/views/snippets.xml
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="website_blog" inherit_id="website.snippets" name="Snippet Blog">
+ <xpath expr="//div[@id='snippet_feature']/div[hasclass('o_panel_body')]/t" position="before">
+ <t t-snippet="website_blog.s_latest_posts" t-thumbnail="/website_blog/static/src/img/s_latest_posts.jpg"/>
+ <t t-snippet="website_blog.s_latest_posts_big_picture" t-thumbnail="/website_blog/static/src/img/s_latest_posts_big_picture.jpg"/>
+ </xpath>
+
<xpath expr="//div[@id='snippet_options']" position="inside">
<div data-js="blog_cover" data-selector=".website_blog .o_blog_cover_container" data-no-check="true">
<a href="#" class="dropdown-item" data-change="true" data-no-preview="true">Change Cover</a>
@@ -37,4 +42,37 @@
</div>
</xpath>
</template>
+
+<!-- Snippets' Options -->
+<template id="s_latest_posts_options" inherit_id="website.snippet_options">
+ <xpath expr="//div[@data-js='background']" position="before">
+ <div data-js="js_get_posts_selectBlog" data-selector=".js_get_posts">
+ <div class="dropdown-submenu">
+ <a tabindex="-1" href="#" class="dropdown-item">Choose a blog</a>
+ <div class="dropdown-menu">
+ <a href="#" class="dropdown-item" data-filter-by-blog-id="0" data-no-preview="true">All blogs</a>
+ <!-- the blog list will be generated in js -->
+ </div>
+ </div>
+ </div>
+
+ <div data-js="js_get_posts_limit" data-selector=".js_get_posts">
+ <div class="dropdown-submenu">
+ <a tabindex="-1" href="#" class="dropdown-item">Number of posts</a>
+ <div class="dropdown-menu" data-no-preview="true">
+ <a href="#" class="dropdown-item" data-posts-limit="1">1 post</a>
+ <a href="#" class="dropdown-item" data-posts-limit="2">2 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="3">3 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="4">4 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="5">5 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="6">6 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="7">7 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="8">8 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="9">9 posts</a>
+ <a href="#" class="dropdown-item" data-posts-limit="10">10 posts</a>
+ </div>
+ </div>
+ </div>
+ </xpath>
+</template>
</odoo>
diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml
index 44d1d4a9ffa..1853eaefaa8 100644
--- a/addons/website_blog/views/website_blog_templates.xml
+++ b/addons/website_blog/views/website_blog_templates.xml
@@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
+<template id="_assets_primary_variables" inherit_id="web._assets_primary_variables">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/website_blog/static/src/scss/primary_variables.scss"/>
+ </xpath>
+</template>
+
<template id="assets_editor" inherit_id="website.assets_editor" name="Blog Editor">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.editor.js"></script>
+ <script type="text/javascript" src="/website_blog/static/src/js/s_latest_posts_editor.js"/>
<script type="text/javascript" src="/website_blog/static/src/js/website.tour.blog.js"></script>
</xpath>
</template>
@@ -14,6 +21,7 @@
<script type="text/javascript" src="/website_blog/static/src/js/contentshare.js"/>
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"/>
+ <script type="text/javascript" src="/website_blog/static/src/js/s_latest_posts_frontend.js"/>
</xpath>
</template>
@@ -585,4 +593,62 @@
</p>
</template>
+<template id="s_latest_posts" name="Latest Posts - List">
+ <section class="s_latest_posts">
+ <div class="container">
+ <h2 class="text-center">Latest Posts</h2>
+ <div class="row js_get_posts"
+ data-loading="true"
+ data-template="website_blog.s_latest_posts_list_template"/>
+ </div>
+ </section>
+</template>
+
+<template id="s_latest_posts_big_picture" name="Latest Posts - Big Images">
+ <section class="s_latest_posts_big_picture">
+ <div class="container-fluid">
+ <h2 class="text-center">Latest Posts</h2>
+ <div class="row js_get_posts"
+ data-loading="true"
+ data-template="website_blog.s_latest_posts_big_picture_template"/>
+ </div>
+ </section>
+</template>
+
+<template id="s_latest_posts_list_template">
+ <t t-foreach="posts" t-as="p">
+ <t t-set="properties" t-value="json.loads(p.cover_properties)"/>
+ <div class="col-12 media mt-3 s_latest_posts_post">
+ <a t-attf-href="/blog/#{p.blog_id.id}/post/#{p.id}">
+ <t t-call="website_blog.blog_cover">
+ <t t-set="cover_properties" t-value="properties"/>
+ <t t-set="additionnal_classes" t-value="'loading_container thumb'"/>
+ </t>
+ </a>
+ <div class="media-body ml-3">
+ <h4 t-field="p.name"/>
+ <p t-field="p.subtitle"/>
+ <a class="btn btn-sm btn-secondary" t-attf-href="/blog/#{p.blog_id.id}/post/#{p.id}">Read More</a>
+ </div>
+ </div>
+ </t>
+</template>
+
+<template id="s_latest_posts_big_picture_template">
+ <t t-foreach="posts" t-as="p">
+ <t t-set="properties" t-value="json.loads(p.cover_properties)"/>
+ <div class="col-12 col-md-6 my-3 loading_container s_latest_posts_post">
+ <t t-call="website_blog.blog_cover">
+ <t t-set="cover_properties" t-value="properties"/>
+ <t t-set="additionnal_classes" t-value="'thumb'"/>
+ </t>
+ <div>
+ <h2 t-field="p.name"/>
+ <p t-field="p.subtitle"/>
+ <a t-attf-href="/blog/#{p.blog_id.id}/post/#{p.id}">Read More</a>
+ </div>
+ </div>
+ </t>
+</template>
+
</odoo>