[IMP] website_blog: add a blog snippet that relies on snippet filters
Before this commit the latest posts snippet implemented the lookup for its templates and its rendering procedure. After this commit a new blog posts snippet is introduced which relies on the dynamic snippet mechanisms. task-2477207 https://github.com/odoo/odoo/pull/67334
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tools.json import scriptsafe as json_safe
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -102,7 +101,7 @@ class WebsiteCoverPropertiesMixin(models.AbstractModel):
|
||||
_name = 'website.cover_properties.mixin'
|
||||
_description = 'Cover Properties Website Mixin'
|
||||
|
||||
cover_properties = fields.Text('Cover Properties', default=lambda s: json.dumps(s._default_cover_properties()))
|
||||
cover_properties = fields.Text('Cover Properties', default=lambda s: json_safe.dumps(s._default_cover_properties()))
|
||||
|
||||
def _default_cover_properties(self):
|
||||
return {
|
||||
@@ -112,6 +111,21 @@ class WebsiteCoverPropertiesMixin(models.AbstractModel):
|
||||
"resize_class": "o_half_screen_height",
|
||||
}
|
||||
|
||||
def _get_background(self, height=None, width=None):
|
||||
self.ensure_one()
|
||||
properties = json_safe.loads(self.cover_properties)
|
||||
img = properties.get('background-image', "none")
|
||||
|
||||
if img.startswith('url(/web/image/'):
|
||||
suffix = ""
|
||||
if height is not None:
|
||||
suffix += "&height=%s" % height
|
||||
if width is not None:
|
||||
suffix += "&width=%s" % width
|
||||
if suffix:
|
||||
suffix = '?' not in img and "?%s" % suffix or suffix
|
||||
img = img[:-1] + suffix + ')'
|
||||
return img
|
||||
|
||||
class WebsiteMultiMixin(models.AbstractModel):
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ class WebsiteSnippetFilter(models.Model):
|
||||
if self.website_id and self.env['website'].get_current_website() != self.website_id:
|
||||
return ''
|
||||
|
||||
if self.model_name.replace('.', '_') not in template_key:
|
||||
return ''
|
||||
|
||||
records = self._prepare_values(limit, search_domain)
|
||||
is_sample = with_sample and not records
|
||||
if is_sample:
|
||||
@@ -173,10 +176,11 @@ class WebsiteSnippetFilter(models.Model):
|
||||
sample = []
|
||||
model = self.env[self.model_name]
|
||||
sample_data = self._get_hardcoded_sample(model)
|
||||
for index in range(0, length):
|
||||
single_sample_data = sample_data[index % len(sample_data)].copy()
|
||||
self._fill_sample(single_sample_data, index)
|
||||
sample.append(model.new(single_sample_data))
|
||||
if sample_data:
|
||||
for index in range(0, length):
|
||||
single_sample_data = sample_data[index % len(sample_data)].copy()
|
||||
self._fill_sample(single_sample_data, index)
|
||||
sample.append(model.new(single_sample_data))
|
||||
return sample
|
||||
|
||||
def _fill_sample(self, sample, index):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="website.s_dynamic_snippet.grid">
|
||||
<!-- Content -->
|
||||
<t t-set="colClass" t-value="'col-' + (12 / chunkSize).toString()"/>
|
||||
<t t-set="colClass" t-value="'d-flex flex-grow-0 flex-shrink-0 col-' + Math.trunc(12 / chunkSize).toString()"/>
|
||||
<t t-set="rowIndexGenerator" t-value="Array.from(Array(Math.ceil(data.length/chunkSize)).keys())"/>
|
||||
<t t-set="colIndexGenerator" t-value="Array.from(Array(chunkSize).keys())"/>
|
||||
<t t-foreach="rowIndexGenerator" t-as="rowIndex">
|
||||
|
||||
@@ -42,12 +42,19 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
this.$target.get(0).dataset.numberOfRecords = filter.limit;
|
||||
this._filterUpdated(filter);
|
||||
}
|
||||
if (params.attributeName === 'templateKey' && previewMode === false) {
|
||||
this._templateUpdated(widgetValue, params.activeValue);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_getTemplateClass: function (templateKey) {
|
||||
return templateKey.replace(/.*\.dynamic_filter_template_/, "s_");
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
@@ -148,6 +155,7 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
*/
|
||||
_renderDynamicFilterTemplatesSelector: async function (uiFragment) {
|
||||
const dynamicFilterTemplates = await this._fetchDynamicFilterTemplates();
|
||||
this.dynamicFilterTemplates = {};
|
||||
for (let index in dynamicFilterTemplates) {
|
||||
this.dynamicFilterTemplates[dynamicFilterTemplates[index].key] = dynamicFilterTemplates[index];
|
||||
}
|
||||
@@ -155,7 +163,10 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
const selectedTemplateId = this.$target.get(0).dataset['templateKey'];
|
||||
if (!this.dynamicFilterTemplates[selectedTemplateId]) {
|
||||
this.$target.get(0).dataset['templateKey'] = dynamicFilterTemplates[0].key;
|
||||
this._refreshPublicWidgets();
|
||||
setTimeout(() => {
|
||||
this._templateUpdated(dynamicFilterTemplates[0].key, selectedTemplateId);
|
||||
this._refreshPublicWidgets();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._refreshPublicWidgets();
|
||||
@@ -190,6 +201,18 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
this._rerenderXML();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Take the new template selection into account
|
||||
* @param newTemplate
|
||||
* @param oldTemplate
|
||||
* @private
|
||||
*/
|
||||
_templateUpdated: function (newTemplate, oldTemplate) {
|
||||
if (oldTemplate) {
|
||||
this.$target.removeClass(this._getTemplateClass(oldTemplate));
|
||||
}
|
||||
this.$target.addClass(this._getTemplateClass(newTemplate));
|
||||
},
|
||||
/**
|
||||
* Sets the option value.
|
||||
* @param optionName
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div t-att-id="uniqueId" class="carousel slide" t-att-data-interval="interval">
|
||||
<!-- Content -->
|
||||
<div class="carousel-inner row w-100 mx-auto" role="listbox">
|
||||
<t t-set="colClass" t-value="'col-' + (12 / chunkSize).toString()"/>
|
||||
<t t-set="colClass" t-value="'d-flex flex-grow-0 flex-shrink-0 col-' + Math.trunc(12 / chunkSize).toString()"/>
|
||||
<t t-set="slideIndexGenerator" t-value="Array.from(Array(Math.ceil(data.length/chunkSize)).keys())"/>
|
||||
<t t-set="itemIndexGenerator" t-value="Array.from(Array(chunkSize).keys())"/>
|
||||
<t t-foreach="slideIndexGenerator" t-as="slideIndex">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="website.s_dynamic_snippet_template">
|
||||
<section t-attf-class="#{snippet_name} s_dynamic d-none pt24 pb24">
|
||||
<section t-attf-class="#{snippet_name} #{snippet_classes} s_dynamic d-none pt24 pb24">
|
||||
<div class="container o_not_editable">
|
||||
<div class="css_non_editable_mode_hidden">
|
||||
<div class="missing_option_warning alert alert-info rounded-0 fade show d-none d-print-none">
|
||||
@@ -24,21 +24,7 @@
|
||||
<we-select string="Template" data-name="template_opt" data-attribute-name="templateKey" data-no-preview="true">
|
||||
</we-select>
|
||||
<we-select string="Fetched elements" data-name="number_of_records_opt" data-attribute-name="numberOfRecords" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="1">1</we-button>
|
||||
<we-button data-select-data-attribute="2">2</we-button>
|
||||
<we-button data-select-data-attribute="3">3</we-button>
|
||||
<we-button data-select-data-attribute="4">4</we-button>
|
||||
<we-button data-select-data-attribute="6">6</we-button>
|
||||
<we-button data-select-data-attribute="7">7</we-button>
|
||||
<we-button data-select-data-attribute="8">8</we-button>
|
||||
<we-button data-select-data-attribute="9">9</we-button>
|
||||
<we-button data-select-data-attribute="10">10</we-button>
|
||||
<we-button data-select-data-attribute="11">11</we-button>
|
||||
<we-button data-select-data-attribute="12">12</we-button>
|
||||
<we-button data-select-data-attribute="13">13</we-button>
|
||||
<we-button data-select-data-attribute="14">14</we-button>
|
||||
<we-button data-select-data-attribute="15">15</we-button>
|
||||
<we-button data-select-data-attribute="16">16</we-button>
|
||||
<we-button t-foreach="range(1, 17)" t-as="value" t-att-data-select-data-attribute="value" t-esc="value"/>
|
||||
</we-select>
|
||||
<we-title class="mt-2"><t t-esc="grouping_message"/></we-title>
|
||||
<we-select string="⌙ Desktop" data-name="number_of_elements_opt" data-attribute-name="numberOfElements" data-no-preview="true"><!--   -->
|
||||
|
||||
@@ -1918,16 +1918,19 @@
|
||||
|
||||
<template id="record_cover">
|
||||
<t t-set="_cp" t-value="_cp or json.loads(_record.cover_properties)"/>
|
||||
<t t-set="_name" t-value="_name or _record._name"/>
|
||||
<t t-set="_id" t-value="_id or _record.id"/>
|
||||
<t t-set="_bg" t-value="_bg or _record._get_background(height=_resize_height, width=_resize_width)"/>
|
||||
<t t-set="default_cover_name">Cover</t>
|
||||
<div t-att-data-name="display_opt_name or default_cover_name"
|
||||
t-att-style="_cp.get('background_color_style')"
|
||||
t-att-data-use_size="use_size"
|
||||
t-att-data-use_filters="use_filters"
|
||||
t-att-data-use_text_align="use_text_align"
|
||||
t-att-data-res-model="request.env.user.has_group('website.group_website_publisher') and _record._name"
|
||||
t-att-data-res-id="request.env.user.has_group('website.group_website_publisher') and _record.id"
|
||||
t-att-data-res-model="request.env.user.has_group('website.group_website_publisher') and _name"
|
||||
t-att-data-res-id="request.env.user.has_group('website.group_website_publisher') and _id"
|
||||
t-attf-class="o_record_cover_container d-flex flex-column h-100 o_colored_level o_cc #{_cp.get('background_color_class')} #{use_size and _cp.get('resize_class')} #{use_text_align and _cp.get('text_align_class')} #{additionnal_classes}">
|
||||
<div t-attf-class="o_record_cover_component o_record_cover_image #{snippet_autofocus and 'o_we_snippet_autofocus'}" t-attf-style="background-image: #{_cp.get('background-image')};"/>
|
||||
<div t-attf-class="o_record_cover_component o_record_cover_image #{snippet_autofocus and 'o_we_snippet_autofocus'}" t-attf-style="background-image: #{_bg};"/>
|
||||
<div t-if="use_filters" t-attf-class="o_record_cover_component o_record_cover_filter oe_black" t-attf-style="opacity: #{_cp.get('opacity', 0.0)};"/>
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
'data/mail_data.xml',
|
||||
'data/mail_templates.xml',
|
||||
'data/website_blog_data.xml',
|
||||
'data/blog_snippet_template_data.xml',
|
||||
'views/website_blog_views.xml',
|
||||
'views/website_blog_components.xml',
|
||||
'views/website_blog_posts_loop.xml',
|
||||
'views/website_blog_templates.xml',
|
||||
'views/snippets/snippets.xml',
|
||||
'views/snippets/s_blog_posts.xml',
|
||||
'views/snippets/s_latest_posts.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/website_blog_security.xml',
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Filters for Dynamic Filter -->
|
||||
<record id="dynamic_snippet_latest_blog_post_filter" model="ir.filters">
|
||||
<field name="name">Latest Blog Posts</field>
|
||||
<field name="model_id">blog.post</field>
|
||||
<field name="user_id" eval="False" />
|
||||
<field name="domain">[('post_date', '<=', context_today())]</field>
|
||||
<field name="sort">['post_date desc']</field>
|
||||
</record>
|
||||
<record id="dynamic_snippet_most_viewed_blog_post_filter" model="ir.filters">
|
||||
<field name="name">Most Viewed Blog Posts</field>
|
||||
<field name="model_id">blog.post</field>
|
||||
<field name="user_id" eval="False" />
|
||||
<field name="domain">[('post_date', '<=', context_today()), ('visits', '!=', False)]</field>
|
||||
<field name="sort">['visits desc']</field>
|
||||
</record>
|
||||
<!-- Dynamic Filter -->
|
||||
<record id="dynamic_filter_latest_blog_posts" model="website.snippet.filter">
|
||||
<field name="name">Latest Blog Posts</field>
|
||||
<field name="filter_id" ref="website_blog.dynamic_snippet_latest_blog_post_filter"/>
|
||||
<field name="field_names">name,teaser,subtitle</field>
|
||||
<field name="limit" eval="6"/>
|
||||
</record>
|
||||
<record id="dynamic_filter_most_viewed_blog_posts" model="website.snippet.filter">
|
||||
<field name="name">Most Viewed Blog Posts</field>
|
||||
<field name="filter_id" ref="website_blog.dynamic_snippet_most_viewed_blog_post_filter"/>
|
||||
<field name="field_names">name,teaser,subtitle</field>
|
||||
<field name="limit" eval="6"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import website
|
||||
from . import website_blog
|
||||
from . import website_snippet_filter
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import models, fields, _
|
||||
|
||||
|
||||
class WebsiteSnippetFilter(models.Model):
|
||||
_inherit = 'website.snippet.filter'
|
||||
|
||||
def _get_hardcoded_sample(self, model):
|
||||
samples = super()._get_hardcoded_sample(model)
|
||||
if model._name == 'blog.post':
|
||||
data = [{
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_2.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('Islands'),
|
||||
'subtitle': _('Alone in the ocean'),
|
||||
'post_date': fields.Date.today() - timedelta(days=1),
|
||||
'website_url': "",
|
||||
}, {
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_3.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('With a View'),
|
||||
'subtitle': _('Awesome hotel rooms'),
|
||||
'post_date': fields.Date.today() - timedelta(days=2),
|
||||
'website_url': "",
|
||||
}, {
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_4.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('Skies'),
|
||||
'subtitle': _('Taking pictures in the dark'),
|
||||
'post_date': fields.Date.today() - timedelta(days=3),
|
||||
'website_url': "",
|
||||
}, {
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_5.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('Satellites'),
|
||||
'subtitle': _('Seeing the world from above'),
|
||||
'post_date': fields.Date.today() - timedelta(days=4),
|
||||
'website_url': "",
|
||||
}, {
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_6.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('Viewpoints'),
|
||||
'subtitle': _('Seaside vs mountain side'),
|
||||
'post_date': fields.Date.today() - timedelta(days=5),
|
||||
'website_url': "",
|
||||
}, {
|
||||
'cover_properties': '{"background-image": "url(\'/website_blog/static/src/img/cover_7.jpg\')", "resize_class": "o_record_has_cover o_half_screen_height", "opacity": "0"}',
|
||||
'name': _('Jungle'),
|
||||
'subtitle': _('Spotting the fauna'),
|
||||
'post_date': fields.Date.today() - timedelta(days=6),
|
||||
'website_url': "",
|
||||
}]
|
||||
merged = []
|
||||
for index in range(0, max(len(samples), len(data))):
|
||||
merged.append({**samples[index % len(samples)], **data[index % len(data)]})
|
||||
# merge definitions
|
||||
samples = merged
|
||||
return samples
|
||||
@@ -1,135 +1,34 @@
|
||||
odoo.define('website_blog.s_latest_posts_frontend', function (require) {
|
||||
odoo.define('website_blog.s_blog_posts_frontend', function (require) {
|
||||
'use strict';
|
||||
|
||||
var core = require('web.core');
|
||||
var wUtils = require('website.utils');
|
||||
var publicWidget = require('web.public.widget');
|
||||
const DynamicSnippet = require('website.s_dynamic_snippet');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
publicWidget.registry.js_get_posts = publicWidget.Widget.extend({
|
||||
selector: '.js_get_posts',
|
||||
const DynamicSnippetBlogPosts = DynamicSnippet.extend({
|
||||
selector: '.s_dynamic_snippet_blog_posts',
|
||||
disabledInEditableMode: false,
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
const data = self.$target[0].dataset;
|
||||
const limit = parseInt(data.postsLimit) || 4;
|
||||
const blogID = parseInt(data.filterByBlogId);
|
||||
// Compatibility with old template xml id
|
||||
if (data.template && data.template.endsWith('.s_latest_posts_big_orizontal_template')) {
|
||||
data.template = 'website_blog.s_latest_posts_horizontal_template';
|
||||
}
|
||||
const template = data.template || 'website_blog.s_latest_posts_list_template';
|
||||
const loading = data.loading === 'true';
|
||||
const order = data.order || 'published_date desc';
|
||||
|
||||
this.$target.empty(); // Compatibility with db that saved content inside by mistake
|
||||
this.$target.attr('contenteditable', 'False'); // Prevent user edition
|
||||
|
||||
var domain = [];
|
||||
if (blogID) {
|
||||
domain.push(['blog_id', '=', blogID]);
|
||||
}
|
||||
if (order.includes('visits')) {
|
||||
domain.push(['visits', '!=', false]);
|
||||
}
|
||||
|
||||
var prom = new Promise(function (resolve) {
|
||||
self._rpc({
|
||||
route: '/blog/render_latest_posts',
|
||||
params: {
|
||||
template: template,
|
||||
domain: domain,
|
||||
limit: limit,
|
||||
order: order,
|
||||
},
|
||||
}).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."),
|
||||
})));
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
// Perform an intro animation
|
||||
self._showLoading($posts);
|
||||
} else {
|
||||
self.$target.html($posts);
|
||||
}
|
||||
resolve();
|
||||
}).guardedCatch(function () {
|
||||
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"),
|
||||
}));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return Promise.all([this._super.apply(this, arguments), prom]);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
this.$target.empty();
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method to be overridden in child components in order to provide a search
|
||||
* domain if needed.
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_showLoading: function ($posts) {
|
||||
var self = this;
|
||||
|
||||
_.each($posts, function (post, i) {
|
||||
var $post = $(post);
|
||||
var $progress = $post.find('.s_latest_posts_loader');
|
||||
var bgUrl = $post.find('.o_record_cover_image').css('background-image').replace('url(','').replace(')','').replace(/\"/gi, "") || 'none';
|
||||
|
||||
// Append $post to the snippet, regardless by the loading state.
|
||||
$post.appendTo(self.$target);
|
||||
|
||||
// No cover-image found. Add a 'flag' class and exit.
|
||||
if (bgUrl === 'none') {
|
||||
$post.addClass('s_latest_posts_loader_no_cover');
|
||||
$progress.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cover image found. Show the spinning icon.
|
||||
$progress.find('> div').removeClass('d-none').css('animation-delay', i * 200 + 'ms');
|
||||
var $dummyImg = $('<img/>', {src: bgUrl});
|
||||
|
||||
// If the image is not loaded in 10 sec, remove loader and provide a fallback bg-color to the container.
|
||||
// Hopefully one day the image will load, covering the bg-color...
|
||||
var timer = setTimeout(function () {
|
||||
$post.find('.o_record_cover_image').addClass('bg-200');
|
||||
$progress.remove();
|
||||
}, 10000);
|
||||
|
||||
wUtils.onceAllImagesLoaded($dummyImg).then(function () {
|
||||
$progress.fadeOut(500, function () {
|
||||
$progress.removeClass('d-flex');
|
||||
});
|
||||
|
||||
$dummyImg.remove();
|
||||
clearTimeout(timer);
|
||||
});
|
||||
});
|
||||
_getSearchDomain: function () {
|
||||
const searchDomain = this._super.apply(this, arguments);
|
||||
const filterByBlogId = parseInt(this.$el.get(0).dataset.filterByBlogId);
|
||||
if (filterByBlogId >= 0) {
|
||||
searchDomain.push(['blog_id', '=', filterByBlogId]);
|
||||
}
|
||||
return searchDomain;
|
||||
},
|
||||
|
||||
});
|
||||
publicWidget.registry.blog_posts = DynamicSnippetBlogPosts;
|
||||
|
||||
return DynamicSnippetBlogPosts;
|
||||
});
|
||||
|
||||
@@ -1,58 +1,38 @@
|
||||
.s_latest_posts[data-vcss='001'] {
|
||||
.s_latest_posts_loader {
|
||||
@include o-position-absolute(0, 0, 0, 0);
|
||||
transform: translateZ(1px);
|
||||
|
||||
> div {
|
||||
animation: o-wblog-loader 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
max: {width: 30px; height: 30px;}
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_post_title {
|
||||
.s_dynamic {
|
||||
.s_blog_posts_post_title {
|
||||
font-weight: $headings-font-weight;
|
||||
// Tweek line-height to help fit multi-line titles.
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.s_latest_posts_post_subtitle {
|
||||
.s_blog_posts_post_subtitle {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
// The two following rules prevent having an empty spot on medium breakpoint
|
||||
// for non-list layouts (lg:3x1, md:2x2, sm: 1x3) list is always 1x3.
|
||||
.s_latest_posts_post:nth-child(4) {
|
||||
display: none !important;
|
||||
}
|
||||
@include media-breakpoint-only(md) {
|
||||
:not(.s_latest_posts_list) > .s_latest_posts_post:nth-child(4) {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_list {
|
||||
&.s_blog_post_list {
|
||||
// Set sizes relative to the container font-size.
|
||||
// (handle parents with, for example, '.small' or '.h1' classes)
|
||||
.s_latest_posts_post_cover {
|
||||
.s_blog_posts_post_cover {
|
||||
@include size(3.5em);
|
||||
max: {width: 85px; height: 85px}
|
||||
}
|
||||
|
||||
.s_latest_posts_post_title {
|
||||
.s_blog_posts_post_title {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_big_picture {
|
||||
.s_latest_posts_post {
|
||||
&.s_blog_post_big_picture {
|
||||
.s_blog_posts_post {
|
||||
min-height: 150px;
|
||||
|
||||
figcaption {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.s_latest_posts_post_cover {
|
||||
.s_blog_posts_post_cover {
|
||||
min-height: 100%;
|
||||
|
||||
.o_record_cover_container {
|
||||
@@ -60,153 +40,154 @@
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_post_title {
|
||||
.s_blog_posts_post_title {
|
||||
@include font-size($h3-font-size);
|
||||
margin-bottom: 0.5em;
|
||||
word-spacing: -0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
&.js_get_posts {
|
||||
.s_latest_posts_post_subtitle {
|
||||
.row {
|
||||
align-items: stretch;
|
||||
.s_blog_posts_post_subtitle {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.s_latest_posts_effect_marley {
|
||||
figcaption {
|
||||
text-align: right;
|
||||
.s_latest_posts_post_title, .s_latest_posts_post_subtitle {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.s_latest_posts_post_subtitle {
|
||||
bottom: 30px;
|
||||
line-height: 1.5;
|
||||
transform: translate3d(0,100%,0);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
}
|
||||
.s_latest_posts_post_title {
|
||||
top: 30px;
|
||||
transition: transform 0.35s;
|
||||
transform: translate3d(0,20px,0);
|
||||
&:after {
|
||||
@include o-position-absolute(100%, auto, auto, 0);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #fff;
|
||||
content: "";
|
||||
transform: translate3d(0,40px,0);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
}
|
||||
}
|
||||
&.s_blog_posts_effect_marley {
|
||||
figcaption {
|
||||
text-align: right;
|
||||
.s_blog_posts_post_title, .s_blog_posts_post_subtitle {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.s_latest_posts_post:hover figcaption {
|
||||
.s_latest_posts_post_title {
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
.s_latest_posts_post_title::after, .s_latest_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
.s_blog_posts_post_subtitle {
|
||||
bottom: 30px;
|
||||
line-height: 1.5;
|
||||
transform: translate3d(0,100%,0);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
}
|
||||
}
|
||||
&.s_latest_posts_effect_dexter .s_latest_posts_post {
|
||||
&::before {
|
||||
content: "";
|
||||
@include o-position-absolute(0, $grid-gutter-width/2, 0, $grid-gutter-width/2);
|
||||
background: linear-gradient(to bottom, darken(theme-color('secondary'), 10%) 0%, darken(theme-color('secondary'), 30%) 100%);
|
||||
}
|
||||
.o_record_cover_container {
|
||||
transition: opacity 0.35s;
|
||||
}
|
||||
figcaption {
|
||||
padding: 3em;
|
||||
text-align: left;
|
||||
.s_blog_posts_post_title {
|
||||
top: 30px;
|
||||
transition: transform 0.35s;
|
||||
transform: translate3d(0,20px,0);
|
||||
&:after {
|
||||
@include o-position-absolute(10px, 10px, 10px, 10px);
|
||||
border: 2px solid #fff;
|
||||
border-top-width: 4px;
|
||||
border-bottom-width: 4px;
|
||||
@include o-position-absolute(100%, auto, auto, 0);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #fff;
|
||||
content: "";
|
||||
transition: transform-origin 0.35s;
|
||||
transform: scaleY(0.5);
|
||||
transform-origin: top;
|
||||
}
|
||||
}
|
||||
.s_latest_posts_post_subtitle {
|
||||
@include o-position-absolute(auto, 20px, 20px, 20px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s linear, transform 0.35s;
|
||||
transform: translate3d(0,-100px,0);
|
||||
}
|
||||
.s_latest_posts_post_title {
|
||||
@include o-position-absolute(20px, 20px, auto, 20px);
|
||||
}
|
||||
&:hover {
|
||||
.o_record_cover_container {
|
||||
opacity: 0.4 !important;
|
||||
}
|
||||
figcaption::after {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
.s_latest_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.s_latest_posts_effect_chico {
|
||||
.o_record_cover_image {
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
transform: scale(1.12);
|
||||
}
|
||||
.s_latest_posts_post figcaption {
|
||||
&::before {
|
||||
@include o-position-absolute(15px,15px,15px,15px);
|
||||
border: 1px solid #fff;
|
||||
content: "";
|
||||
transform: scale(1.1);
|
||||
transform: translate3d(0,40px,0);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
}
|
||||
}
|
||||
.s_latest_posts_post_subtitle {
|
||||
}
|
||||
.s_blog_posts_post:hover figcaption {
|
||||
.s_blog_posts_post_title {
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
.s_blog_posts_post_title::after, .s_blog_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.s_blog_posts_effect_dexter .s_blog_posts_post {
|
||||
&::before {
|
||||
content: "";
|
||||
@include o-position-absolute(0, $grid-gutter-width/2, 0, $grid-gutter-width/2);
|
||||
background: linear-gradient(to bottom, darken(theme-color('secondary'), 10%) 0%, darken(theme-color('secondary'), 30%) 100%);
|
||||
}
|
||||
.o_record_cover_container {
|
||||
transition: opacity 0.35s;
|
||||
}
|
||||
figcaption {
|
||||
padding: 3em;
|
||||
text-align: left;
|
||||
&:after {
|
||||
@include o-position-absolute(10px, 10px, 10px, 10px);
|
||||
border: 2px solid #fff;
|
||||
border-top-width: 4px;
|
||||
border-bottom-width: 4px;
|
||||
content: "";
|
||||
transition: transform-origin 0.35s;
|
||||
transform: scaleY(0.5);
|
||||
transform-origin: top;
|
||||
}
|
||||
}
|
||||
.s_blog_posts_post_subtitle {
|
||||
@include o-position-absolute(auto, 20px, 20px, 20px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s linear, transform 0.35s;
|
||||
transform: translate3d(0,-100px,0);
|
||||
}
|
||||
.s_blog_posts_post_title {
|
||||
@include o-position-absolute(20px, 20px, auto, 20px);
|
||||
}
|
||||
&:hover {
|
||||
.o_record_cover_container {
|
||||
opacity: 0.4 !important;
|
||||
}
|
||||
figcaption::after {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
.s_blog_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.s_blog_posts_effect_chico {
|
||||
.o_record_cover_image {
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
transform: scale(1.12);
|
||||
}
|
||||
.s_blog_posts_post figcaption {
|
||||
&::before {
|
||||
@include o-position-absolute(15px,15px,15px,15px);
|
||||
border: 1px solid #fff;
|
||||
content: "";
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 200px;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
.s_latest_posts_post_title {
|
||||
padding: 0;
|
||||
}
|
||||
.s_blog_posts_post_subtitle {
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s, transform 0.35s;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 200px;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
.s_blog_posts_post_title {
|
||||
padding: 0;
|
||||
}
|
||||
.s_blog_posts_post:hover {
|
||||
.o_record_cover_image {
|
||||
transform: scale(1);
|
||||
}
|
||||
.s_latest_posts_post:hover {
|
||||
.o_record_cover_image {
|
||||
transform: scale(1);
|
||||
}
|
||||
figcaption::before, .s_latest_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
figcaption::before, .s_blog_posts_post_subtitle {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_horizontal {
|
||||
&.s_blog_post_horizontal {
|
||||
.o_record_cover_container {
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.js_get_posts {
|
||||
.row {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
text-align: left;
|
||||
.s_latest_posts_post {
|
||||
.s_blog_posts_post {
|
||||
position: relative;
|
||||
figcaption:after {
|
||||
position: relative;
|
||||
@@ -275,12 +256,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.s_latest_posts_card {
|
||||
&.s_blog_post_card {
|
||||
.card {
|
||||
height: 100%;
|
||||
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
|
||||
|
||||
.s_latest_posts_post_cover {
|
||||
.s_blog_posts_post_cover {
|
||||
height: 170px;
|
||||
|
||||
.o_record_cover_container {
|
||||
@@ -288,6 +269,7 @@
|
||||
|
||||
.o_record_cover_image {
|
||||
@extend .card-img-top;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,90 @@
|
||||
odoo.define('website_blog.s_latest_posts_editor', function (require) {
|
||||
odoo.define('website_blog.s_blog_posts_options', function (require) {
|
||||
'use strict';
|
||||
|
||||
var sOptions = require('web_editor.snippets.options');
|
||||
const options = require('web_editor.snippets.options');
|
||||
const dynamicSnippetOptions = require('website.s_dynamic_snippet_options');
|
||||
|
||||
var wUtils = require('website.utils');
|
||||
|
||||
sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
|
||||
const dynamicSnippetBlogPostsOptions = dynamicSnippetOptions.extend({
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.modelNameFilter = 'blog.post';
|
||||
this.blogs = {};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderCustomXML: function (uiFragment) {
|
||||
_computeWidgetVisibility: function (widgetName, params) {
|
||||
if (widgetName === 'hover_effect_opt') {
|
||||
return this.$target.get(0).dataset.templateKey === 'website_blog.dynamic_filter_template_blog_post_big_picture';
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Fetches blogs.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_fetchBlogs: function () {
|
||||
return this._rpc({
|
||||
model: 'blog.blog',
|
||||
method: 'search_read',
|
||||
args: [wUtils.websiteDomain(this), ['name']],
|
||||
}).then(blogs => {
|
||||
const menuEl = uiFragment.querySelector('[name="blog_selection"]');
|
||||
for (const blog of blogs) {
|
||||
const el = document.createElement('we-button');
|
||||
el.dataset.selectDataAttribute = blog.id;
|
||||
el.textContent = blog.name;
|
||||
menuEl.appendChild(el);
|
||||
kwargs: {
|
||||
domain: wUtils.websiteDomain(this),
|
||||
fields: ['id', 'name'],
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderCustomXML: async function (uiFragment) {
|
||||
await this._super.apply(this, arguments);
|
||||
await this._renderBlogSelector(uiFragment);
|
||||
},
|
||||
/**
|
||||
* Renders the blog option selector content into the provided uiFragment.
|
||||
* @private
|
||||
* @param {HTMLElement} uiFragment
|
||||
*/
|
||||
_renderBlogSelector: async function (uiFragment) {
|
||||
if (!Object.keys(this.blogs).length) {
|
||||
const blogsList = await this._fetchBlogs();
|
||||
this.blogs = {};
|
||||
for (let index in blogsList) {
|
||||
this.blogs[blogsList[index].id] = blogsList[index];
|
||||
}
|
||||
}
|
||||
const blogSelectorEl = uiFragment.querySelector('[data-name="blog_opt"]');
|
||||
return this._renderSelectUserValueWidgetButtons(blogSelectorEl, this.blogs);
|
||||
},
|
||||
/**
|
||||
* Sets default options values.
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_setOptionsDefaultValues: function () {
|
||||
this._setOptionValue('numberOfElements', 3);
|
||||
this._setOptionValue('filterByBlogId', -1);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.dynamic_snippet_blog_posts = dynamicSnippetBlogPostsOptions;
|
||||
|
||||
return dynamicSnippetBlogPostsOptions;
|
||||
});
|
||||
|
||||
@@ -1,147 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Snippet -->
|
||||
<template id="s_latest_posts" name="Blog Posts">
|
||||
<section class="s_latest_posts pt16 pb16" data-vcss="001">
|
||||
<div class="container">
|
||||
<div class="row s_col_no_bgcolor s_nb_column_fixed js_get_posts s_latest_posts_big_picture s_latest_posts_effect_marley"
|
||||
data-loading="true"
|
||||
data-template="website_blog.s_latest_posts_big_picture_template"
|
||||
data-filter-by-blog-id="0"
|
||||
data-order="published_date desc"/>
|
||||
</div>
|
||||
</section>
|
||||
<template id="s_blog_posts" name="Blog Posts">
|
||||
<t t-call="website.s_dynamic_snippet_template">
|
||||
<t t-set="snippet_name" t-value="'s_blog_posts'"/>
|
||||
<t t-set="snippet_classes" t-value="'s_dynamic_snippet_blog_posts s_blog_post_big_picture s_blog_posts_effect_marley'"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Load-time templates (rendered in JS on page load) -->
|
||||
<!-- List layout -->
|
||||
<template id="s_latest_posts_list_template">
|
||||
<div t-foreach="posts" t-as="p" class="d-flex col-12 mt-3 s_latest_posts_post">
|
||||
<a class="s_latest_posts_post_cover flex-grow-0 flex-shrink-0 align-self-baseline position-relative" t-attf-href="/blog/#{p.blog_id.id}/#{p.id}" t-att-title="'Read' + p.name">
|
||||
<template id="dynamic_filter_template_blog_post_list" name="List layout">
|
||||
<div t-foreach="records" t-as="data" class="d-flex mt-3 s_blog_posts_post">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<a class="s_blog_posts_post_cover flex-grow-0 flex-shrink-0 align-self-baseline position-relative" t-att-href="data['call_to_action_url']" t-att-title="'Read' + data['name']">
|
||||
<t t-call="website.record_cover">
|
||||
<t t-set="_record" t-value="p"/>
|
||||
<t t-set="_record" t-value="record"/>
|
||||
<t t-set="_resize_height" t-value="128"/>
|
||||
<t t-set="_resize_width" t-value="128"/>
|
||||
<t t-set="additionnal_classes" t-value="'w-100 h-100 bg-200 position-absolute'"/>
|
||||
|
||||
<div class="s_latest_posts_loader d-flex align-items-center justify-content-center bg-200">
|
||||
<div class="d-none rounded-circle bg-black-25 w-25 h-25"/>
|
||||
</div>
|
||||
</t>
|
||||
</a>
|
||||
<div class="pl-2">
|
||||
<a class="" t-att-title="'Read' + p.name" t-attf-href="/blog/#{p.blog_id.id}/#{p.id}">
|
||||
<div class="s_latest_posts_post_title mb-1" t-field="p.name"/>
|
||||
<a class="" t-att-title="'Read' + data['name']" t-att-href="data['call_to_action_url']">
|
||||
<div class="s_blog_posts_post_title mb-1">
|
||||
<span t-if="is_sample" class="bg-primary text-uppercase px-1">Sample</span>
|
||||
<span t-field="record.name"/>
|
||||
</div>
|
||||
</a>
|
||||
<div class="s_latest_posts_post_subtitle mb-1 d-none d-sm-block" t-field="p.subtitle"/>
|
||||
<div class="s_blog_posts_post_subtitle mb-1 d-none d-sm-block" t-field="record.subtitle"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Big picture layout -->
|
||||
<template id="s_latest_posts_big_picture_template">
|
||||
<figure t-foreach="posts" t-as="p" class="col-md-6 col-lg-4 my-3 s_latest_posts_post">
|
||||
<a class="s_latest_posts_post_cover position-relative d-flex flex-column shadow-sm overflow-hidden rounded text-decoration-none" t-attf-href="/blog/#{p.blog_id.id}/#{p.id}">
|
||||
<template id="dynamic_filter_template_blog_post_big_picture" name="Big picture layout">
|
||||
<figure t-foreach="records" t-as="data" class="my-3 w-100 s_blog_posts_post">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<a class="s_blog_posts_post_cover position-relative d-flex flex-column shadow-sm overflow-hidden rounded text-decoration-none" t-att-href="data['call_to_action_url']">
|
||||
<t t-call="website.record_cover">
|
||||
<t t-set="_record" t-value="p"/>
|
||||
<t t-set="_record" t-value="record"/>
|
||||
<t t-set="_resize_height" t-value="512"/>
|
||||
<t t-set="_resize_width" t-value="512"/>
|
||||
<t t-set="use_filters" t-value="True"/>
|
||||
<t t-set="additionnal_classes" t-value="'h-100 w-100 bg-600 position-absolute'"/>
|
||||
</t>
|
||||
|
||||
<figcaption class="text-center w-100 h-100 px-3 d-flex flex-column flex-grow-1">
|
||||
<div class="s_latest_posts_post_title text-white" t-field="p.name"/>
|
||||
<div class="s_latest_posts_post_subtitle text-white" t-field="p.subtitle"/>
|
||||
<div t-if="is_sample" class="h5 o_ribbon_right bg-primary text-uppercase">Sample</div>
|
||||
<div class="s_blog_posts_post_title text-white" t-field="record.name"/>
|
||||
<div class="s_blog_posts_post_subtitle text-white" t-field="record.subtitle"/>
|
||||
</figcaption>
|
||||
|
||||
<div class="s_latest_posts_loader d-flex align-items-center justify-content-center bg-200">
|
||||
<div class="d-none rounded-circle bg-black-25 w-25 h-25"/>
|
||||
</div>
|
||||
</a>
|
||||
</figure>
|
||||
</template>
|
||||
<!-- Horizontal layout -->
|
||||
<template id="s_latest_posts_horizontal_template">
|
||||
<figure t-foreach="posts" t-as="p" class="post s_latest_posts_post col-md-6 col-lg-4">
|
||||
<template id="dynamic_filter_template_blog_post_horizontal" name="Horizontal layout">
|
||||
<figure t-foreach="records" t-as="data" class="post s_blog_posts_post w-100">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<span t-if="is_sample" class="h5 float-right bg-primary text-uppercase rounded-circle px-2 py-2">Sample</span>
|
||||
<figcaption>
|
||||
<h4 class="mb0"><a t-att-href="'/blog/%s/%s' % (p.blog_id.id, p.id)"><t t-esc="p.name"/></a></h4>
|
||||
<h5 class="mt0 mb4" t-field="p.post_date" t-options='{"format": "dd/MM"}' />
|
||||
<h4 class="mb0">
|
||||
<a t-att-href="data['call_to_action_url']" t-field="record.name"/>
|
||||
</h4>
|
||||
<h5 class="mt0 mb4" t-field="record.post_date" t-options='{"format": "dd/MM"}'/>
|
||||
</figcaption>
|
||||
<a t-att-href="'/blog/%s/%s' % (p.blog_id.id, p.id)">
|
||||
<a t-att-href="data['call_to_action_url']">
|
||||
<t t-call="website.record_cover">
|
||||
<t t-set="_record" t-value="p"/>
|
||||
<t t-set="_record" t-value="record"/>
|
||||
<t t-set="_resize_height" t-value="512"/>
|
||||
<t t-set="_resize_width" t-value="512"/>
|
||||
<t t-set="additionnal_classes" t-value="'loading_container thumb'"/>
|
||||
</t>
|
||||
</a>
|
||||
</figure>
|
||||
</template>
|
||||
<!-- Card layout -->
|
||||
<template id="s_latest_posts_card_template">
|
||||
<div t-foreach="posts" t-as="p" class="col-md-6 col-lg-4 s_latest_posts_post pb32">
|
||||
<template id="dynamic_filter_template_blog_post_card" name="Card layout">
|
||||
<div t-foreach="records" t-as="data" class="s_blog_posts_post pb32 w-100">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<div class="card">
|
||||
<a class="s_latest_posts_post_cover" t-att-href="'/blog/%s/%s' % (p.blog_id.id, p.id)">
|
||||
<a class="s_blog_posts_post_cover" t-att-href="data['call_to_action_url']">
|
||||
<t t-call="website.record_cover">
|
||||
<t t-set="_record" t-value="p"/>
|
||||
<t t-set="_record" t-value="record"/>
|
||||
<t t-set="_resize_height" t-value="256"/>
|
||||
<t t-set="_resize_width" t-value="256"/>
|
||||
<t t-set="additionnal_classes" t-value="'loading_container thumb'"/>
|
||||
</t>
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<a t-att-href="'/blog/%s/%s' % (p.blog_id.id, p.id)"><h4 class="mb-0"><t t-esc="p.name"/></h4></a>
|
||||
<div t-if="is_sample" class="h5 o_ribbon_right bg-primary text-uppercase">Sample</div>
|
||||
<a t-att-href="data['call_to_action_url']"><h4 class="mb-0" t-field="record.name"/></a>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between">
|
||||
<span class="text-muted mb-0" t-field="p.post_date" t-options='{"format": "MMM d, yyyy"}' />
|
||||
<span class="text-muted mb-0">In <a class="font-weight-bold" t-esc="p.blog_id.name" t-att-href="'/blog/%s' % (p.blog_id.id)" /></span>
|
||||
<span class="text-muted mb-0" t-field="record.post_date" t-options='{"format": "MMM d, yyyy"}' />
|
||||
<span class="text-muted mb-0">In <a class="font-weight-bold" t-field="record.blog_id.name" t-att-href="'/blog/%s' % record.blog_id.id" />
|
||||
<a t-if="is_sample" class="font-weight-bold" href="#">Sample</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Options -->
|
||||
<template id="s_latest_posts_options" inherit_id="website.snippet_options">
|
||||
<xpath expr="//div[@data-js='Box']" position="before">
|
||||
<div data-js="js_get_posts_selectBlog" data-selector=".s_latest_posts" data-target=".js_get_posts" data-no-check="true">
|
||||
<we-select string="Choose a blog" data-no-preview="true" name="blog_selection" data-attribute-name="filterByBlogId">
|
||||
<we-button data-select-data-attribute="0">All blogs</we-button>
|
||||
<!-- the blog list will be generated in js -->
|
||||
</we-select>
|
||||
<we-select string="Posts" data-no-preview="true" data-attribute-name="order" class="o_we_inline">
|
||||
<we-button data-select-data-attribute="published_date desc">Latest</we-button>
|
||||
<we-button data-select-data-attribute="visits desc">Most viewed</we-button>
|
||||
</we-select>
|
||||
<we-select string="Layout" data-attribute-name="template">
|
||||
<we-button data-select-data-attribute="website_blog.s_latest_posts_list_template" data-select-class="s_latest_posts_list">List</we-button>
|
||||
<we-button data-select-data-attribute="website_blog.s_latest_posts_big_picture_template"
|
||||
data-select-class="s_latest_posts_big_picture"
|
||||
data-name="big_picture_opt">Big Pictures</we-button>
|
||||
<we-button data-select-data-attribute="website_blog.s_latest_posts_horizontal_template"
|
||||
data-select-class="s_latest_posts_horizontal">Horizontal</we-button>
|
||||
<we-button data-select-data-attribute="website_blog.s_latest_posts_card_template"
|
||||
data-select-class="s_latest_posts_card">Cards</we-button>
|
||||
</we-select>
|
||||
<we-select string="Hover effect" data-no-widget-refresh="true" data-dependencies="big_picture_opt" class="o_we_inline">
|
||||
<we-button data-select-class="">None</we-button>
|
||||
<we-button data-select-class="s_latest_posts_effect_marley">Marley</we-button>
|
||||
<we-button data-select-class="s_latest_posts_effect_dexter">Dexter</we-button>
|
||||
<we-button data-select-class="s_latest_posts_effect_chico">Silly-Chico</we-button>
|
||||
</we-select>
|
||||
</div>
|
||||
<template id="s_blog_posts_options" inherit_id="website.snippet_options">
|
||||
<xpath expr="." position="inside">
|
||||
<t t-call="website_blog.s_dynamic_snippet_options_template">
|
||||
<t t-set="snippet_name" t-value="'dynamic_snippet_blog_posts'"/>
|
||||
<t t-set="snippet_selector" t-value="'.s_dynamic_snippet_blog_posts'"/>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[@data-js='layout_column']" position="attributes">
|
||||
<attribute name="data-exclude" add=".s_latest_posts, .s_latest_posts_big_picture" separator=","/>
|
||||
<attribute name="data-exclude" add=".s_blog_posts, .s_blog_posts_big_picture" separator=","/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="s_dynamic_snippet_options_template" inherit_id="website.s_dynamic_snippet_options_template">
|
||||
<xpath expr="//we-select[@data-name='filter_opt']" position="after">
|
||||
<we-select t-if="snippet_name == 'dynamic_snippet_blog_posts'" string="Blog" data-no-preview="true" data-name="blog_opt" data-attribute-name="filterByBlogId">
|
||||
<we-button data-select-data-attribute="-1">All blogs</we-button>
|
||||
<!-- the blog list will be generated in js -->
|
||||
</we-select>
|
||||
</xpath>
|
||||
<xpath expr="//we-select[@data-name='template_opt']" position="after">
|
||||
<we-select t-if="snippet_name == 'dynamic_snippet_blog_posts'" string="⌙ Hover effect" data-no-widget-refresh="true" data-name="hover_effect_opt" class="o_we_inline">
|
||||
<we-button data-select-class="">None</we-button>
|
||||
<we-button data-select-class="s_blog_posts_effect_marley">Marley</we-button>
|
||||
<we-button data-select-class="s_blog_posts_effect_dexter">Dexter</we-button>
|
||||
<we-button data-select-class="s_blog_posts_effect_chico">Silly-Chico</we-button>
|
||||
</we-select>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Assets -->
|
||||
<template id="assets_snippet_s_latest_posts_css_000" inherit_id="website.assets_frontend" active="False">
|
||||
<template id="assets_snippet_s_blog_posts_css_000" inherit_id="website.assets_frontend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_blog/static/src/snippets/s_latest_posts/000.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="assets_snippet_s_latest_posts_css_001" inherit_id="website.assets_frontend">
|
||||
<xpath expr="//link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_blog/static/src/snippets/s_latest_posts/001.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_blog/static/src/snippets/s_blog_posts/000.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="assets_snippet_s_latest_posts_js_000" inherit_id="website.assets_frontend">
|
||||
<template id="assets_snippet_s_blog_posts_js_000" inherit_id="website.assets_frontend">
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_blog/static/src/snippets/s_latest_posts/000.js"/>
|
||||
<script type="text/javascript" src="/website_blog/static/src/snippets/s_blog_posts/000.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<template id="website_blog" inherit_id="website.snippets" name="Snippet Blog">
|
||||
<xpath expr="//t[@id='blog_posts_hook']" position="replace">
|
||||
<t t-snippet="website_blog.s_latest_posts" t-thumbnail="/website_blog/static/src/img/s_latest_posts.svg"/>
|
||||
<t t-snippet="website_blog.s_blog_posts" t-thumbnail="/website_blog/static/src/img/s_blog_posts.svg"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<template id="assets_wysiwyg" inherit_id="website.assets_wysiwyg" name="Blog posts snippet options">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/website_blog/static/src/snippets/s_latest_posts/options.js"/>
|
||||
<script type="text/javascript" src="/website_blog/static/src/snippets/s_blog_posts/options.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user