summaryrefslogtreecommitdiff
path: root/addons/website_blog/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_blog/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_blog/static')
-rw-r--r--addons/website_blog/static/description/icon.pngbin0 -> 8114 bytes
-rw-r--r--addons/website_blog/static/description/icon.svg1
-rw-r--r--addons/website_blog/static/src/img/anonymous.pngbin0 -> 1586 bytes
-rw-r--r--addons/website_blog/static/src/img/blog_1.jpegbin0 -> 207998 bytes
-rw-r--r--addons/website_blog/static/src/img/blog_2.jpegbin0 -> 299468 bytes
-rw-r--r--addons/website_blog/static/src/img/content_1_1.jpgbin0 -> 86060 bytes
-rw-r--r--addons/website_blog/static/src/img/content_1_2.jpgbin0 -> 27970 bytes
-rw-r--r--addons/website_blog/static/src/img/content_2_1.jpgbin0 -> 34785 bytes
-rw-r--r--addons/website_blog/static/src/img/content_2_2.jpgbin0 -> 42220 bytes
-rw-r--r--addons/website_blog/static/src/img/content_2_3.jpgbin0 -> 39228 bytes
-rw-r--r--addons/website_blog/static/src/img/content_3_1.jpgbin0 -> 47213 bytes
-rw-r--r--addons/website_blog/static/src/img/content_4_1.jpgbin0 -> 37297 bytes
-rw-r--r--addons/website_blog/static/src/img/content_5_1.jpgbin0 -> 33128 bytes
-rw-r--r--addons/website_blog/static/src/img/content_6_1.jpgbin0 -> 42088 bytes
-rw-r--r--addons/website_blog/static/src/img/content_7_1.jpgbin0 -> 18100 bytes
-rw-r--r--addons/website_blog/static/src/img/content_7_2.jpgbin0 -> 36334 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_1.jpgbin0 -> 61581 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_2.jpgbin0 -> 132920 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_3.jpgbin0 -> 85059 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_4.jpgbin0 -> 43318 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_5.jpgbin0 -> 73735 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_6.jpgbin0 -> 81689 bytes
-rw-r--r--addons/website_blog/static/src/img/cover_7.jpgbin0 -> 101070 bytes
-rw-r--r--addons/website_blog/static/src/img/s_latest_posts.svg64
-rw-r--r--addons/website_blog/static/src/js/contentshare.js110
-rw-r--r--addons/website_blog/static/src/js/tours/website_blog.js79
-rw-r--r--addons/website_blog/static/src/js/website_blog.editor.js380
-rw-r--r--addons/website_blog/static/src/js/website_blog.js102
-rw-r--r--addons/website_blog/static/src/scss/website_blog.scss304
-rw-r--r--addons/website_blog/static/src/snippets/s_latest_posts/000.js135
-rw-r--r--addons/website_blog/static/src/snippets/s_latest_posts/000.scss335
-rw-r--r--addons/website_blog/static/src/snippets/s_latest_posts/001.scss315
-rw-r--r--addons/website_blog/static/src/snippets/s_latest_posts/options.js32
-rw-r--r--addons/website_blog/static/src/xml/website_blog_tag.xml19
34 files changed, 1876 insertions, 0 deletions
diff --git a/addons/website_blog/static/description/icon.png b/addons/website_blog/static/description/icon.png
new file mode 100644
index 00000000..36d47e8a
--- /dev/null
+++ b/addons/website_blog/static/description/icon.png
Binary files differ
diff --git a/addons/website_blog/static/description/icon.svg b/addons/website_blog/static/description/icon.svg
new file mode 100644
index 00000000..b04e930c
--- /dev/null
+++ b/addons/website_blog/static/description/icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#269396"/><stop offset="100%" stop-color="#218689"/></linearGradient><path id="d" d="M26.971 49.017c0 2.879-2.4 5.212-5.36 5.212-2.961 0-5.361-2.333-5.361-5.212 0-2.878 2.4-5.211 5.36-5.211 2.961 0 5.361 2.333 5.361 5.211zm14.704 3.846c-.7-12.582-11.065-22.672-24.02-23.352-.764-.04-1.405.557-1.405 1.301v3.912c0 .685.543 1.26 1.246 1.304 9.361.592 16.864 7.87 17.475 16.99.046.683.637 1.211 1.341 1.211h4.024c.766 0 1.38-.623 1.339-1.366zm12.074.023c-.702-19.028-16.45-34.428-36.118-35.114-.755-.027-1.381.567-1.381 1.302v3.912c0 .702.572 1.273 1.293 1.302 16.003.638 28.848 13.128 29.504 28.684.03.701.617 1.257 1.339 1.257h4.023c.757 0 1.367-.608 1.34-1.343z"/><path id="e" d="M26.971 47.017c0 2.879-2.4 5.212-5.36 5.212-2.961 0-5.361-2.333-5.361-5.212 0-2.878 2.4-5.211 5.36-5.211 2.961 0 5.361 2.333 5.361 5.211zm14.704 3.846c-.7-12.582-11.065-22.672-24.02-23.352-.764-.04-1.405.557-1.405 1.301v3.912c0 .685.543 1.26 1.246 1.304 9.361.592 16.864 7.87 17.475 16.99.046.683.637 1.211 1.341 1.211h4.024c.766 0 1.38-.623 1.339-1.366zm12.074.023c-.702-19.028-16.45-34.428-36.118-35.114-.755-.027-1.381.567-1.381 1.302v3.912c0 .702.572 1.273 1.293 1.302 16.003.638 28.848 13.128 29.504 28.684.03.701.617 1.257 1.339 1.257h4.023c.757 0 1.367-.608 1.34-1.343z"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M42.103 69H4c-2 0-4-1-4-4V34.165l16.641-18.024L44 29l9.485 22.679L42.103 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg> \ No newline at end of file
diff --git a/addons/website_blog/static/src/img/anonymous.png b/addons/website_blog/static/src/img/anonymous.png
new file mode 100644
index 00000000..6461bf73
--- /dev/null
+++ b/addons/website_blog/static/src/img/anonymous.png
Binary files differ
diff --git a/addons/website_blog/static/src/img/blog_1.jpeg b/addons/website_blog/static/src/img/blog_1.jpeg
new file mode 100644
index 00000000..cd737525
--- /dev/null
+++ b/addons/website_blog/static/src/img/blog_1.jpeg
Binary files differ
diff --git a/addons/website_blog/static/src/img/blog_2.jpeg b/addons/website_blog/static/src/img/blog_2.jpeg
new file mode 100644
index 00000000..d1e6ebb3
--- /dev/null
+++ b/addons/website_blog/static/src/img/blog_2.jpeg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_1_1.jpg b/addons/website_blog/static/src/img/content_1_1.jpg
new file mode 100644
index 00000000..dd02d230
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_1_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_1_2.jpg b/addons/website_blog/static/src/img/content_1_2.jpg
new file mode 100644
index 00000000..5bd85240
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_1_2.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_2_1.jpg b/addons/website_blog/static/src/img/content_2_1.jpg
new file mode 100644
index 00000000..e08effcb
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_2_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_2_2.jpg b/addons/website_blog/static/src/img/content_2_2.jpg
new file mode 100644
index 00000000..71f9c99f
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_2_2.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_2_3.jpg b/addons/website_blog/static/src/img/content_2_3.jpg
new file mode 100644
index 00000000..11e8fbee
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_2_3.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_3_1.jpg b/addons/website_blog/static/src/img/content_3_1.jpg
new file mode 100644
index 00000000..d0b1f9f5
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_3_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_4_1.jpg b/addons/website_blog/static/src/img/content_4_1.jpg
new file mode 100644
index 00000000..2b432877
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_4_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_5_1.jpg b/addons/website_blog/static/src/img/content_5_1.jpg
new file mode 100644
index 00000000..8bd94d06
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_5_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_6_1.jpg b/addons/website_blog/static/src/img/content_6_1.jpg
new file mode 100644
index 00000000..9999994d
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_6_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_7_1.jpg b/addons/website_blog/static/src/img/content_7_1.jpg
new file mode 100644
index 00000000..08c3e5d1
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_7_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/content_7_2.jpg b/addons/website_blog/static/src/img/content_7_2.jpg
new file mode 100644
index 00000000..a67409f1
--- /dev/null
+++ b/addons/website_blog/static/src/img/content_7_2.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_1.jpg b/addons/website_blog/static/src/img/cover_1.jpg
new file mode 100644
index 00000000..b7e39c27
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_1.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_2.jpg b/addons/website_blog/static/src/img/cover_2.jpg
new file mode 100644
index 00000000..8c0d2b77
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_2.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_3.jpg b/addons/website_blog/static/src/img/cover_3.jpg
new file mode 100644
index 00000000..39912642
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_3.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_4.jpg b/addons/website_blog/static/src/img/cover_4.jpg
new file mode 100644
index 00000000..98bb6ec2
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_4.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_5.jpg b/addons/website_blog/static/src/img/cover_5.jpg
new file mode 100644
index 00000000..28b98247
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_5.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_6.jpg b/addons/website_blog/static/src/img/cover_6.jpg
new file mode 100644
index 00000000..2dd8f9a5
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_6.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/cover_7.jpg b/addons/website_blog/static/src/img/cover_7.jpg
new file mode 100644
index 00000000..8a04795b
--- /dev/null
+++ b/addons/website_blog/static/src/img/cover_7.jpg
Binary files differ
diff --git a/addons/website_blog/static/src/img/s_latest_posts.svg b/addons/website_blog/static/src/img/s_latest_posts.svg
new file mode 100644
index 00000000..9f98059a
--- /dev/null
+++ b/addons/website_blog/static/src/img/s_latest_posts.svg
@@ -0,0 +1,64 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="82" height="60" viewBox="0 0 82 60">
+ <defs>
+ <rect id="path-1" width="82" height="55" x="0" y="0"/>
+ <linearGradient id="linearGradient-3" x1="72.875%" x2="40.332%" y1="46.753%" y2="35.353%">
+ <stop offset="0%" stop-color="#008374"/>
+ <stop offset="100%" stop-color="#006A59"/>
+ </linearGradient>
+ <linearGradient id="linearGradient-4" x1="88.517%" x2="50%" y1="41.799%" y2="50%">
+ <stop offset="0%" stop-color="#00AA89"/>
+ <stop offset="100%" stop-color="#009989"/>
+ </linearGradient>
+ <rect id="path-5" width="22" height="3" x="14" y="21"/>
+ <filter id="filter-6" width="104.5%" height="166.7%" x="-2.3%" y="-16.7%" filterUnits="objectBoundingBox">
+ <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
+ <feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
+ <feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/>
+ </filter>
+ <path id="path-7" d="M24 31v1H14v-1h10zm9-3v1H14v-1h19z"/>
+ <filter id="filter-8" width="105.3%" height="150%" x="-2.6%" y="-12.5%" filterUnits="objectBoundingBox">
+ <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
+ <feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
+ <feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/>
+ </filter>
+ <path id="path-9" d="M59 32.5a2.41 2.41 0 0 1-.73 1.77 2.41 2.41 0 0 1-1.77.73 2.41 2.41 0 0 1-1.77-.73A2.41 2.41 0 0 1 54 32.5c0-.694.243-1.285.73-1.77A2.41 2.41 0 0 1 56.5 30a2.41 2.41 0 0 1 1.77.73A2.41 2.41 0 0 1 59 32.5zm5.998 1.653a.743.743 0 0 1-.209.59.723.723 0 0 1-.577.257h-1.657a.754.754 0 0 1-.528-.203.747.747 0 0 1-.245-.51c-.18-1.873-.935-3.475-2.265-4.805-1.33-1.33-2.931-2.085-4.805-2.265a.747.747 0 0 1-.51-.246.754.754 0 0 1-.202-.528v-1.657c0-.238.086-.43.258-.577a.715.715 0 0 1 .528-.209h.06c1.31.106 2.562.436 3.757.988a10.853 10.853 0 0 1 3.179 2.229 10.856 10.856 0 0 1 2.228 3.18 11.01 11.01 0 0 1 .988 3.756zm6 .038a.698.698 0 0 1-.217.568.714.714 0 0 1-.556.241H68.5a.754.754 0 0 1-.537-.211.722.722 0 0 1-.236-.513 13.5 13.5 0 0 0-1.22-4.933c-.715-1.557-1.647-2.91-2.794-4.056-1.147-1.147-2.499-2.08-4.056-2.796a13.672 13.672 0 0 0-4.932-1.231.722.722 0 0 1-.513-.235.74.74 0 0 1-.211-.526v-1.726c0-.226.08-.41.241-.556a.723.723 0 0 1 .532-.217h.036a16.75 16.75 0 0 1 6.054 1.449A16.924 16.924 0 0 1 66 22.999a16.926 16.926 0 0 1 3.55 5.137 16.755 16.755 0 0 1 1.448 6.055z"/>
+ <filter id="filter-11" width="105.9%" height="111.8%" x="-2.9%" y="-2.9%" filterUnits="objectBoundingBox">
+ <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
+ <feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
+ <feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/>
+ </filter>
+ </defs>
+ <g fill="none" fill-rule="evenodd" class="snippets_thumbs">
+ <g class="s_latest_posts">
+ <rect width="82" height="60" class="bg"/>
+ <g class="group_2">
+ <g class="group" opacity=".5">
+ <g class="oval___oval_mask">
+ <mask id="mask-2" fill="#fff">
+ <use xlink:href="#path-1"/>
+ </mask>
+ <use fill="#79D1F2" class="mask" xlink:href="#path-1"/>
+ <circle cx="65.5" cy="11.5" r="7.5" fill="#F3EC60" class="oval" mask="url(#mask-2)"/>
+ <ellipse cx="63" cy="55.5" fill="url(#linearGradient-3)" class="oval" mask="url(#mask-2)" rx="28" ry="16.5"/>
+ <ellipse cx="6.5" cy="52.5" fill="url(#linearGradient-4)" class="oval" mask="url(#mask-2)" rx="42.5" ry="22.5"/>
+ </g>
+ </g>
+ <g class="rectangle">
+ <use fill="#000" filter="url(#filter-6)" xlink:href="#path-5"/>
+ <use fill="#FFF" fill-opacity=".95" xlink:href="#path-5"/>
+ </g>
+ <g class="combined_shape">
+ <use fill="#000" filter="url(#filter-8)" xlink:href="#path-7"/>
+ <use fill="#FFF" fill-opacity=".8" xlink:href="#path-7"/>
+ </g>
+ <mask id="mask-10" fill="#fff">
+ <use xlink:href="#path-9"/>
+ </mask>
+ <g class="rss">
+ <use fill="#000" filter="url(#filter-11)" xlink:href="#path-9"/>
+ <use fill="#FFF" fill-opacity=".95" xlink:href="#path-9"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/addons/website_blog/static/src/js/contentshare.js b/addons/website_blog/static/src/js/contentshare.js
new file mode 100644
index 00000000..2e6c9cd4
--- /dev/null
+++ b/addons/website_blog/static/src/js/contentshare.js
@@ -0,0 +1,110 @@
+odoo.define('website_blog.contentshare', function (require) {
+'use strict';
+
+const dom = require('web.dom');
+
+$.fn.share = function (options) {
+ var option = $.extend($.fn.share.defaults, options);
+ var selected_text = "";
+ $.extend($.fn.share, {
+ init: function (shareable) {
+ var self = this;
+ $.fn.share.defaults.shareable = shareable;
+ $.fn.share.defaults.shareable.on('mouseup', function () {
+ if ($(this).parents('body.editor_enable').length === 0) {
+ self.popOver();
+ }
+ });
+ $.fn.share.defaults.shareable.on('mousedown', function () {
+ self.destroy();
+ });
+ },
+ getContent: function () {
+ var $popover_content = $('<div class="h4 m-0"/>');
+ if ($('.o_wblog_title, .o_wblog_post_content_field').hasClass('js_comment')) {
+ selected_text = this.getSelection('string');
+ var $btn_c = $('<a class="o_share_comment btn btn-link px-2" href="#"/>').append($('<i class="fa fa-lg fa-comment"/>'));
+ $popover_content.append($btn_c);
+ }
+ if ($('.o_wblog_title, .o_wblog_post_content_field').hasClass('js_tweet')) {
+ var tweet = '"%s" - %s';
+ var baseLength = tweet.replace(/%s/g, '').length;
+ // Shorten the selected text to match the tweet max length
+ // Note: all (non-localhost) urls in a tweet have 23 characters https://support.twitter.com/articles/78124
+ var selectedText = this.getSelection('string').substring(0, option.maxLength - baseLength - 23);
+
+ var text = window.btoa(encodeURIComponent(_.str.sprintf(tweet, selectedText, window.location.href)));
+ $popover_content.append(_.str.sprintf(
+ "<a onclick=\"window.open('%s' + atob('%s'), '_%s','location=yes,height=570,width=520,scrollbars=yes,status=yes')\"><i class=\"ml4 mr4 fa fa-twitter fa-lg\"/></a>",
+ option.shareLink, text, option.target));
+ }
+ return $popover_content;
+ },
+ commentEdition: function () {
+ $(".o_portal_chatter_composer_form textarea").val('"' + selected_text + '" ').focus();
+ const commentsEl = $('#o_wblog_post_comments')[0];
+ if (commentsEl) {
+ dom.scrollTo(commentsEl).then(() => {
+ window.location.hash = 'blog_post_comment_quote';
+ });
+ }
+ },
+ getSelection: function (share) {
+ if (window.getSelection) {
+ var selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) {
+ return "";
+ }
+ if (share === 'string') {
+ return String(selection.getRangeAt(0)).replace(/\s{2,}/g, ' ');
+ } else {
+ return selection.getRangeAt(0);
+ }
+ } else if (document.selection) {
+ if (share === 'string') {
+ return document.selection.createRange().text.replace(/\s{2,}/g, ' ');
+ } else {
+ return document.selection.createRange();
+ }
+ }
+ },
+ popOver: function () {
+ this.destroy();
+ if (this.getSelection('string').length < option.minLength) {
+ return;
+ }
+ var data = this.getContent();
+ var range = this.getSelection();
+
+ var newNode = document.createElement("span");
+ range.insertNode(newNode);
+ newNode.className = option.className;
+ var $pop = $(newNode);
+ $pop.popover({
+ trigger: 'manual',
+ placement: option.placement,
+ html: true,
+ content: function () {
+ return data;
+ }
+ }).popover('show');
+ $('.o_share_comment').on('click', this.commentEdition);
+ },
+ destroy: function () {
+ var $span = $('span.' + option.className);
+ $span.popover('hide');
+ $span.remove();
+ }
+ });
+ $.fn.share.init(this);
+};
+
+$.fn.share.defaults = {
+ shareLink: "http://twitter.com/intent/tweet?text=",
+ minLength: 5,
+ maxLength: 140,
+ target: "blank",
+ className: "share",
+ placement: "top",
+};
+});
diff --git a/addons/website_blog/static/src/js/tours/website_blog.js b/addons/website_blog/static/src/js/tours/website_blog.js
new file mode 100644
index 00000000..23b59149
--- /dev/null
+++ b/addons/website_blog/static/src/js/tours/website_blog.js
@@ -0,0 +1,79 @@
+odoo.define("website_blog.tour", function (require) {
+ "use strict";
+
+ var core = require("web.core");
+ var tour = require("web_tour.tour");
+
+ var _t = core._t;
+
+ tour.register("blog", {
+ url: "/",
+ }, [{
+ trigger: '#new-content-menu > a',
+ content: _t("Click here to add new content to your website."),
+ position: 'bottom',
+
+ }, {
+ trigger: "a[data-action=new_blog_post]",
+ content: _t("Select this menu item to create a new blog post."),
+ position: "bottom",
+ }, {
+ trigger: "button.btn-continue",
+ extra_trigger: "form[id=\"editor_new_blog\"]",
+ content: _t("Select the blog you want to add the post to."),
+ }, {
+ trigger: "div[data-oe-expression=\"blog_post.name\"]",
+ extra_trigger: "#oe_snippets.o_loaded",
+ content: _t("Write a title, the subtitle is optional."),
+ position: "top",
+ run: "text",
+ }, {
+ trigger: "we-button[data-background]:nth(1)",
+ extra_trigger: "#wrap div[data-oe-expression=\"blog_post.name\"]:not(:containsExact(\"\"))",
+ content: _t("Set a blog post <b>cover</b>."),
+ position: "right",
+ }, {
+ trigger: ".o_select_media_dialog .o_we_search",
+ content: _t("Search for an image. (eg: type \"business\")"),
+ position: "top",
+ }, {
+ trigger: ".o_select_media_dialog .o_existing_attachment_cell:first img",
+ extra_trigger: '.modal:has(.o_existing_attachment_cell:first)',
+ content: _t("Choose an image from the library."),
+ position: "top",
+ }, {
+ trigger: "#o_wblog_post_content",
+ content: _t("<b>Write your story here.</b> Use the top toolbar to style your text: add an image or table, set bold or italic, etc. Drag and drop building blocks for more graphical blogs."),
+ position: "top",
+ run: function (actions) {
+ actions.auto();
+ actions.text("Blog content", this.$anchor.find("p"));
+ },
+ }, {
+ trigger: "button[data-action=save]",
+ extra_trigger: "#o_wblog_post_content .o_wblog_post_content_field p:first:not(:containsExact(" + _t("Start writing here...") + "))",
+ content: _t("<b>Click on Save</b> to record your changes."),
+ position: "bottom",
+ }, {
+ trigger: "a[data-action=show-mobile-preview]",
+ extra_trigger: "body:not(.editor_enable)",
+ content: _t("Use this icon to preview your blog post on <b>mobile devices</b>."),
+ position: "bottom",
+ }, {
+ trigger: "button[data-dismiss=modal]",
+ extra_trigger: '.modal:has(#mobile-viewport)',
+ content: _t("Once you have reviewed the content on mobile, close the preview."),
+ position: "right",
+ }, {
+ trigger: ".js_publish_management .js_publish_btn",
+ extra_trigger: "body:not(.editor_enable)",
+ position: "bottom",
+ content: _t("<b>Publish your blog post</b> to make it visible to your visitors."),
+ }, {
+ trigger: "#customize-menu > a",
+ extra_trigger: ".js_publish_management .js_publish_btn .css_unpublish:visible",
+ content: _t("<b>That's it, your blog post is published!</b> Discover more features through the <i>Customize</i> menu."),
+ position: "bottom",
+ width: 500,
+ }]);
+});
diff --git a/addons/website_blog/static/src/js/website_blog.editor.js b/addons/website_blog/static/src/js/website_blog.editor.js
new file mode 100644
index 00000000..95daa0fb
--- /dev/null
+++ b/addons/website_blog/static/src/js/website_blog.editor.js
@@ -0,0 +1,380 @@
+odoo.define('website_blog.new_blog_post', function (require) {
+'use strict';
+
+var core = require('web.core');
+var wUtils = require('website.utils');
+var WebsiteNewMenu = require('website.newMenu');
+
+var _t = core._t;
+
+WebsiteNewMenu.include({
+ actions: _.extend({}, WebsiteNewMenu.prototype.actions || {}, {
+ new_blog_post: '_createNewBlogPost',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Actions
+ //--------------------------------------------------------------------------
+
+ /**
+ * Asks the user information about a new blog post to create, then creates
+ * it and redirects the user to this new post.
+ *
+ * @private
+ * @returns {Promise} Unresolved if there is a redirection
+ */
+ _createNewBlogPost: function () {
+ return this._rpc({
+ model: 'blog.blog',
+ method: 'search_read',
+ args: [wUtils.websiteDomain(this), ['name']],
+ }).then(function (blogs) {
+ if (blogs.length === 1) {
+ document.location = '/blog/' + blogs[0]['id'] + '/post/new';
+ return new Promise(function () {});
+ } else if (blogs.length > 1) {
+ return wUtils.prompt({
+ id: 'editor_new_blog',
+ window_title: _t("New Blog Post"),
+ select: _t("Select Blog"),
+ init: function (field) {
+ return _.map(blogs, function (blog) {
+ return [blog['id'], blog['name']];
+ });
+ },
+ }).then(function (result) {
+ var blog_id = result.val;
+ if (!blog_id) {
+ return;
+ }
+ document.location = '/blog/' + blog_id + '/post/new';
+ return new Promise(function () {});
+ });
+ }
+ });
+ },
+});
+});
+
+//==============================================================================
+
+odoo.define('website_blog.editor', function (require) {
+'use strict';
+
+require('web.dom_ready');
+const {qweb, _t} = require('web.core');
+const options = require('web_editor.snippets.options');
+var WysiwygMultizone = require('web_editor.wysiwyg.multizone');
+
+if (!$('.website_blog').length) {
+ return Promise.reject("DOM doesn't contain '.website_blog'");
+}
+
+const NEW_TAG_PREFIX = 'new-blog-tag-';
+
+WysiwygMultizone.include({
+ custom_events: Object.assign({}, WysiwygMultizone.prototype.custom_events, {
+ 'set_blog_post_updated_tags': '_onSetBlogPostUpdatedTags',
+ }),
+
+ /**
+ * @override
+ */
+ init() {
+ this._super(...arguments);
+ this.blogTagsPerBlogPost = {};
+ },
+ /**
+ * @override
+ */
+ async start() {
+ await this._super(...arguments);
+ $('.js_tweet, .js_comment').off('mouseup').trigger('mousedown');
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ async save() {
+ const ret = await this._super(...arguments);
+ await this._saveBlogTags(); // Note: important to be called after save otherwise cleanForSave is not called before
+ return ret;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Saves the blog tags in the database.
+ *
+ * @private
+ */
+ async _saveBlogTags() {
+ for (const [key, tags] of Object.entries(this.blogTagsPerBlogPost)) {
+ const proms = tags.filter(tag => typeof tag.id === 'string').map(tag => {
+ return this._rpc({
+ model: 'blog.tag',
+ method: 'create',
+ args: [{
+ 'name': tag.name,
+ }],
+ });
+ });
+ const createdIDs = await Promise.all(proms);
+
+ await this._rpc({
+ model: 'blog.post',
+ method: 'write',
+ args: [parseInt(key), {
+ 'tag_ids': [[6, 0, tags.filter(tag => typeof tag.id === 'number').map(tag => tag.id).concat(createdIDs)]],
+ }],
+ });
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSetBlogPostUpdatedTags: function (ev) {
+ this.blogTagsPerBlogPost[ev.data.blogPostID] = ev.data.tags;
+ },
+});
+
+options.registry.many2one.include({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _selectRecord: function ($opt) {
+ var self = this;
+ this._super.apply(this, arguments);
+ if (this.$target.data('oe-field') === 'author_id') {
+ var $nodes = $('[data-oe-model="blog.post"][data-oe-id="'+this.$target.data('oe-id')+'"][data-oe-field="author_avatar"]');
+ $nodes.each(function () {
+ var $img = $(this).find('img');
+ var css = window.getComputedStyle($img[0]);
+ $img.css({ width: css.width, height: css.height });
+ $img.attr('src', '/web/image/res.partner/'+self.ID+'/image_1024');
+ });
+ setTimeout(function () { $nodes.removeClass('o_dirty'); },0);
+ }
+ }
+});
+
+options.registry.CoverProperties.include({
+ /**
+ * @override
+ */
+ updateUI: async function () {
+ await this._super(...arguments);
+ var isRegularCover = this.$target.is('.o_wblog_post_page_cover_regular');
+ var $coverFull = this.$el.find('[data-select-class*="o_full_screen_height"]');
+ var $coverMid = this.$el.find('[data-select-class*="o_half_screen_height"]');
+ var $coverAuto = this.$el.find('[data-select-class*="cover_auto"]');
+ this._coverFullOriginalLabel = this._coverFullOriginalLabel || $coverFull.text();
+ this._coverMidOriginalLabel = this._coverMidOriginalLabel || $coverMid.text();
+ this._coverAutoOriginalLabel = this._coverAutoOriginalLabel || $coverAuto.text();
+ $coverFull.children('div').text(isRegularCover ? _t("Large") : this._coverFullOriginalLabel);
+ $coverMid.children('div').text(isRegularCover ? _t("Medium") : this._coverMidOriginalLabel);
+ $coverAuto.children('div').text(isRegularCover ? _t("Tiny") : this._coverAutoOriginalLabel);
+ },
+});
+
+options.registry.BlogPostTagSelection = options.Class.extend({
+ xmlDependencies: (options.Class.prototype.xmlDependencies || [])
+ .concat(['/website_blog/static/src/xml/website_blog_tag.xml']),
+
+ /**
+ * @override
+ */
+ async willStart() {
+ const _super = this._super.bind(this);
+
+ this.blogPostID = parseInt(this.$target[0].dataset.blogId);
+ this.isEditingTags = false;
+ const tags = await this._rpc({
+ model: 'blog.tag',
+ method: 'search_read',
+ args: [[], ['id', 'name', 'post_ids']],
+ });
+ this.allTagsByID = {};
+ this.tagIDs = [];
+ for (const tag of tags) {
+ this.allTagsByID[tag.id] = tag;
+ if (tag['post_ids'].includes(this.blogPostID)) {
+ this.tagIDs.push(tag.id);
+ }
+ }
+
+ return _super(...arguments);
+ },
+ /**
+ * @override
+ */
+ cleanForSave() {
+ if (this.isEditingTags) {
+ this._notifyUpdatedTags();
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * @see this.selectClass for params
+ */
+ editTagList(previewMode, widgetValue, params) {
+ this.isEditingTags = true;
+ this.rerender = true;
+ },
+ /**
+ * Send changes that will be saved in the database.
+ *
+ * @see this.selectClass for params
+ */
+ saveTagList(previewMode, widgetValue, params) {
+ this.isEditingTags = false;
+ this.rerender = true;
+ this._notifyUpdatedTags();
+ },
+ /**
+ * @see this.selectClass for params
+ */
+ setNewTagName(previewMode, widgetValue, params) {
+ this.newTagName = widgetValue;
+ },
+ /**
+ * @see this.selectClass for params
+ */
+ confirmNew(previewMode, widgetValue, params) {
+ if (!this.newTagName) {
+ return;
+ }
+ const existing = Object.values(this.allTagsByID).some(tag => tag.name.toLowerCase() === this.newTagName.toLowerCase());
+ if (existing) {
+ return this.displayNotification({
+ type: 'warning',
+ message: _t("This tag already exists"),
+ });
+ }
+ const newTagID = _.uniqueId(NEW_TAG_PREFIX);
+ this.allTagsByID[newTagID] = {
+ 'id': newTagID,
+ 'name': this.newTagName,
+ };
+ this.tagIDs.push(newTagID);
+ this.newTagName = '';
+ this.rerender = true;
+ },
+ /**
+ * @see this.selectClass for params
+ */
+ addTag(previewMode, widgetValue, params) {
+ const tagID = parseInt(widgetValue);
+ this.tagIDs.push(tagID);
+ this.rerender = true;
+ },
+ /**
+ * @see this.selectClass for params
+ */
+ removeTag(previewMode, widgetValue, params) {
+ this.tagIDs = this.tagIDs.filter(tagID => (`${tagID}` !== widgetValue));
+ if (widgetValue.startsWith(NEW_TAG_PREFIX)) {
+ delete this.allTagsByID[widgetValue];
+ }
+ this.rerender = true;
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ async updateUI() {
+ if (this.rerender) {
+ this.rerender = false;
+ await this._rerenderXML();
+ return;
+ }
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ async _computeWidgetVisibility(widgetName, params) {
+ if (['blog_existing_tag_opt', 'new_tag_input_opt', 'new_tag_button_opt', 'save_tags_opt'].includes(widgetName)) {
+ return this.isEditingTags;
+ }
+ if (widgetName === 'edit_tags_opt') {
+ return !this.isEditingTags;
+ }
+ if (params.optionsPossibleValues['removeTag']) {
+ return this.isEditingTags;
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ async _computeWidgetState(methodName, params) {
+ if (methodName === 'addTag') {
+ // The related widget allows to select a value but then resets its state to a non-selected value
+ return '';
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @private
+ */
+ _notifyUpdatedTags() {
+ this.trigger_up('set_blog_post_updated_tags', {
+ blogPostID: this.blogPostID,
+ tags: this.tagIDs.map(tagID => this.allTagsByID[tagID]),
+ });
+ },
+ /**
+ * @override
+ */
+ async _renderCustomXML(uiFragment) {
+ const $tagList = $(uiFragment.querySelector('.o_wblog_tag_list'));
+ for (const tagID of this.tagIDs) {
+ const tag = this.allTagsByID[tagID];
+ $tagList.append(qweb.render('website_blog.TagListItem', {
+ tag: tag,
+ }));
+ }
+ const $select = $(uiFragment.querySelector('we-select[data-name="blog_existing_tag_opt"]'));
+ for (const [key, tag] of Object.entries(this.allTagsByID)) {
+ if (this.tagIDs.includes(parseInt(key)) || this.tagIDs.includes(key)) {
+ // saved tag keys are numbers, new tag keys are strings
+ continue;
+ }
+ $select.prepend(qweb.render('website_blog.TagSelectItem', {
+ tag: tag,
+ }));
+ }
+ },
+});
+});
diff --git a/addons/website_blog/static/src/js/website_blog.js b/addons/website_blog/static/src/js/website_blog.js
new file mode 100644
index 00000000..7131f275
--- /dev/null
+++ b/addons/website_blog/static/src/js/website_blog.js
@@ -0,0 +1,102 @@
+odoo.define('website_blog.website_blog', function (require) {
+'use strict';
+var core = require('web.core');
+
+const dom = require('web.dom');
+const publicWidget = require('web.public.widget');
+
+publicWidget.registry.websiteBlog = publicWidget.Widget.extend({
+ selector: '.website_blog',
+ events: {
+ 'click #o_wblog_next_container': '_onNextBlogClick',
+ 'click #o_wblog_post_content_jump': '_onContentAnchorClick',
+ 'click .o_twitter, .o_facebook, .o_linkedin, .o_google, .o_twitter_complete, .o_facebook_complete, .o_linkedin_complete, .o_google_complete': '_onShareArticle',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ $('.js_tweet, .js_comment').share({});
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onNextBlogClick: function (ev) {
+ ev.preventDefault();
+ var self = this;
+ var $el = $(ev.currentTarget);
+ var nexInfo = $el.find('#o_wblog_next_post_info').data();
+ $el.find('.o_record_cover_container').addClass(nexInfo.size + ' ' + nexInfo.text).end()
+ .find('.o_wblog_toggle').toggleClass('d-none');
+ // Appending a placeholder so that the cover can scroll to the top of the
+ // screen, regardless of its height.
+ const placeholder = document.createElement('div');
+ placeholder.style.minHeight = '100vh';
+ this.$('#o_wblog_next_container').append(placeholder);
+
+ // Use _.defer to calculate the 'offset()'' only after that size classes
+ // have been applyed and that $el has been resized.
+ _.defer(function () {
+ self._forumScrollAction($el, 300, function () {
+ window.location.href = nexInfo.url;
+ });
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onContentAnchorClick: function (ev) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+ var $el = $(ev.currentTarget.hash);
+
+ this._forumScrollAction($el, 500, function () {
+ window.location.hash = 'blog_content';
+ });
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onShareArticle: function (ev) {
+ ev.preventDefault();
+ var url = '';
+ var $element = $(ev.currentTarget);
+ var blogPostTitle = encodeURIComponent($('#o_wblog_post_name').html() || '');
+ var articleURL = encodeURIComponent(window.location.href);
+ if ($element.hasClass('o_twitter')) {
+ var twitterText = core._t("Amazing blog article: %s! Check it live: %s");
+ var tweetText = _.string.sprintf(twitterText, blogPostTitle, articleURL);
+ url = 'https://twitter.com/intent/tweet?tw_p=tweetbutton&text=' + tweetText;
+ } else if ($element.hasClass('o_facebook')) {
+ url = 'https://www.facebook.com/sharer/sharer.php?u=' + articleURL;
+ } else if ($element.hasClass('o_linkedin')) {
+ url = 'https://www.linkedin.com/sharing/share-offsite/?url=' + articleURL;
+ }
+ window.open(url, '', 'menubar=no, width=500, height=400');
+ },
+
+ //--------------------------------------------------------------------------
+ // Utils
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {JQuery} $el - the element we are scrolling to
+ * @param {Integer} duration - scroll animation duration
+ * @param {Function} callback - to be executed after the scroll is performed
+ */
+ _forumScrollAction: function ($el, duration, callback) {
+ dom.scrollTo($el[0], {duration: duration}).then(() => callback());
+ },
+});
+});
diff --git a/addons/website_blog/static/src/scss/website_blog.scss b/addons/website_blog/static/src/scss/website_blog.scss
new file mode 100644
index 00000000..e62f5065
--- /dev/null
+++ b/addons/website_blog/static/src/scss/website_blog.scss
@@ -0,0 +1,304 @@
+// ======= VARIABLES =======
+$o-wblog-sidebar-width: 280px;
+$o-wblog-loader-size: 50px;
+
+// ======= ANIMATIONS =======
+// Generic fade-in
+@keyframes o-wblog-fade-In {
+ 0% { opacity: 0; }
+ 100% { opacity: 1; }
+}
+
+@keyframes o-wblog-loader {
+ 0%, 100% {
+ animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5);
+ }
+ 0% {
+ transform: rotateY(0deg);
+ }
+ 50% {
+ transform: rotateY(1800deg);
+ animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1);
+ }
+ 100% {
+ transform: rotateY(3600deg);
+ }
+}
+
+
+// ======== MIXINS =========
+// Create easing-linear-gradients. Compared to the standards liner-gradients
+// these are smoother and blends into their context increasing readability when
+// acts as background for text placed over images.
+// based on: https://css-tricks.com/easing-linear-gradients/
+@mixin o-wbblog-scrim-gradient($colorFrom, $colorTo) {
+ $scrimStops: 0% 0%, 26.2% 19%, 45.9% 34%, 61.8% 47%, 72.2% 56.5%, 80.6% 65%, 87.4% 73%, 92.5% 80.2%, 95.8% 86.1%, 97.9% 91%, 99.2% 95.2%, 99.8% 98.2%, 100% 100%;
+ $stops: ();
+ @each $scrimStop in $scrimStops {
+ $stops: append($stops, mix($colorTo, $colorFrom, nth($scrimStop, 1)) nth($scrimStop, 2), comma)
+ }
+
+ background-image: linear-gradient(#{$stops});
+}
+
+//------------------------------------------------------------------------------
+// Website Blog
+//------------------------------------------------------------------------------
+.website_blog {
+ .css_website_mail {
+ .o_has_error {
+ border-color: red;
+ }
+ .css_nav_month {
+ display: none;
+ &:first-of-type {
+ display: block;
+ }
+ }
+ }
+
+ .o_wblog_page_cards_bg {
+ // To be applied to the main container when 'card design' option is
+ // enabled. Provide a slight darker bg that will blend the background
+ // color chosen by the user, visually increasing the contrast in case of
+ // brights (or white) backgrounds.
+ // It shouldn't be noticeable on dark backgrounds.
+ background-color: rgba(black, 0.005);
+ }
+
+ .o_wblog_read_with_sidebar {
+ max-width: map-get($container-max-widths, md) + $o-wblog-sidebar-width;
+ }
+
+ // This option class is assigned to the post's content using the "Customize"
+ // menu. The aim is to be able to write simple articles on the fly,
+ // achieving a good design without being forced to use snippets.
+ .o_wblog_read_text {
+ font-size: 18px;
+ line-height: 28px;
+ font-weight: 300;
+
+ p, ul, ol {
+ margin-bottom: 1.55em;
+ }
+
+ li {
+ margin-bottom: 1em;
+ }
+
+ .lead {
+ font-size: 26px;
+ line-height: 34px;
+ margin-bottom: 40px;
+ }
+ }
+
+ .o_wblog_author_avatar {
+ width: 1.2em;
+ height: 1.2em;
+ max-width: none;
+ object-fit: cover;
+ }
+
+ .o_wblog_author_avatar_date {
+ // Resize in order to double the current font-size and accommodate
+ // two text lines, name and date.
+ width: 2em;
+ height: 2em;
+ max-width: none;
+ object-fit: cover;
+ }
+
+ .o_wblog_social_links > a {
+ @include size(3em);
+ > i {
+ font-size: 1.3em;
+ }
+ }
+
+ // Blog Post Page
+ // ==============================================
+ #o_wblog_post_content {
+ min-height: 350px !important;
+
+ a.oe_mail_expand:after {
+ content: " →";
+ }
+ a.oe_mail_expand {
+ font-weight: bold;
+ }
+ }
+
+ #o_wblog_post_comments {
+ .o_portal_chatter > hr {
+ display: none;
+ }
+ }
+
+ // Blog Post Page Cover
+ // ==============================================
+ .o_wblog_post_page_cover {
+
+ // Cover sizes
+ // ==============================================
+ &.cover_auto {
+ padding: 3rem 0;
+ // The actual height will always be 'auto'. The following
+ // min-height rule is set to trigger the page transition only.
+ min-height: 1px;
+ }
+
+ // "Regular Cover" design sizes
+ // ==============================================
+ &.o_wblog_post_page_cover_regular {
+ &.o_full_screen_height {
+ min-height: 70vh !important;
+ }
+
+ &.o_half_screen_height {
+ min-height: 40vh !important;
+ }
+
+ &.cover_auto {
+ min-height: 150px;
+ padding: 0;
+ }
+ }
+
+ // If a cover image is defined, adapt inner typography
+ &.o_record_has_cover {
+ .o_record_cover_image:after {
+ content: "";
+ display: block;
+ @include o-position-absolute(0,0,50%,0);
+ @include o-wbblog-scrim-gradient(rgba(black, 0.5), transparent);
+ }
+
+ .o_wblog_post_title {
+ color: white;
+ }
+
+ &.o_wblog_post_page_cover_regular {
+ .o_record_cover_image:after {
+ visibility: hidden;
+ }
+ }
+ }
+
+ }
+
+ // Blog Post Specific
+ // ==============================================
+ .o_wblog_post_title {
+ #o_wblog_post_name {
+ font-weight: $display4-weight;
+ line-height: $display-line-height;
+ // Default font-size.
+ @include font-size($display4-size);
+ }
+
+ #o_wblog_post_subtitle {
+ font-weight: $lead-font-weight;
+ // Default font-size.
+ @include font-size($lead-font-size);
+ }
+ }
+
+ // Refine multi-lines titles when using 'regular' cover
+ .o_wblog_regular_cover_container #o_wblog_post_name {
+ line-height: 1;
+ padding-bottom: 0.5rem;
+ }
+
+ .o_wblog_post_page_cover_footer {
+ min-height: 33vh;
+ // Emulate native jQuery 'swing' easing to match js code
+ transition: all 0.3s cubic-bezier(.02, .01, .47, 1);
+ }
+
+ #o_wblog_next_container {
+ background-color: $body-bg;
+ cursor: pointer;
+
+ .o_wblog_next_loader {
+ transform: translateZ(1px);
+
+ div {
+ @include size($o-wblog-loader-size);
+ background: rgba(black, 0.15);
+ animation: o-wblog-loader 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite;
+ }
+ }
+
+ .o_wblog_next_fake_btn {
+ height: $o-wblog-loader-size;
+ }
+ }
+
+ #o_wblog_post_main.o_wblog_post_main_transition {
+ animation: o-wblog-fade-In 1s cubic-bezier(.02, .01, .47, 1);
+ }
+
+ // Blog Index Pages
+ // ==============================================
+ #o_wblog_posts_loop {
+ .o_record_cover_container {
+ box-shadow: inset 0 0 0 1px rgba(white, 0.3);
+
+ padding-top: 33%;
+ height: auto!important;
+ // This is mandatory as we do not want the CoverProperties
+ // sizing classes to be applied here
+ min-height: auto!important;
+
+ &:hover .o_record_cover_image {
+ opacity: 0.8;
+ }
+ }
+
+ .o_wblog_normalize_font {
+ // normalize fonts
+ font-size: 13px;
+ line-height: 1.45;
+ }
+
+ .o_wblog_post_list_author {
+ @include o-wbblog-scrim-gradient(transparent, rgba(black, 0.5));
+ }
+
+ &.o_wblog_list_view {
+ .o_record_cover_container {
+ padding-top: 20%;
+ }
+ .o_wblog_post_cover_nocard .o_record_cover_container {
+ padding-top: 33%;
+ }
+ }
+
+ }
+
+ // Editor Helpers
+ // ==============================================
+ body.editor_enable & {
+ // Make empty covers visible in edit mode
+ .o_record_cover_container.o_wblog_post_page_cover:not(.o_record_has_cover) {
+ padding: 30px 0;
+ }
+
+ // Hide the big "DRAG BUILDING BLOCKS HERE" box when inside a sidebar.
+ // The purple lines are enough to help the user dropping snippets.
+ #o_wblog_sidebar .oe_structure:empty {
+ display: none;
+ }
+ }
+}
+
+#o_wblog_post_content_jump {
+ @extend .o_scroll_button;
+ @include size($o-wblog-loader-size);
+ background-color: rgba(black, 0.5);
+
+ &:hover {
+ background-color: rgba(black, 0.7);
+ }
+}
diff --git a/addons/website_blog/static/src/snippets/s_latest_posts/000.js b/addons/website_blog/static/src/snippets/s_latest_posts/000.js
new file mode 100644
index 00000000..3ef418c8
--- /dev/null
+++ b/addons/website_blog/static/src/snippets/s_latest_posts/000.js
@@ -0,0 +1,135 @@
+odoo.define('website_blog.s_latest_posts_frontend', function (require) {
+'use strict';
+
+var core = require('web.core');
+var wUtils = require('website.utils');
+var publicWidget = require('web.public.widget');
+
+var _t = core._t;
+
+publicWidget.registry.js_get_posts = publicWidget.Widget.extend({
+ selector: '.js_get_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
+ //--------------------------------------------------------------------------
+
+ /**
+ * @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);
+ });
+ });
+ },
+});
+});
diff --git a/addons/website_blog/static/src/snippets/s_latest_posts/000.scss b/addons/website_blog/static/src/snippets/s_latest_posts/000.scss
new file mode 100644
index 00000000..b43eddec
--- /dev/null
+++ b/addons/website_blog/static/src/snippets/s_latest_posts/000.scss
@@ -0,0 +1,335 @@
+.s_latest_posts:not([data-vcss]), .s_latest_posts_big_picture:not([data-vcss]) {
+ .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 {
+ font-weight: $headings-font-weight;
+ // Tweek line-height to help fit multi-line titles.
+ line-height: 1;
+ }
+
+ .s_latest_posts_post_subtitle {
+ font-size: 1em;
+ }
+}
+
+.s_latest_posts:not([data-vcss]) {
+ // Set sizes relative to the container font-size.
+ // (handle parents with, for example, '.small' or '.h1' classes)
+ .s_latest_posts_post_cover {
+ @include size(3.5em);
+ max: {width: 85px; height: 85px}
+ }
+
+ .s_latest_posts_post_title {
+ font-size: 1.25em;
+ }
+}
+
+.s_latest_posts_big_picture {
+ .s_latest_posts_post {
+ min-height: 150px;
+
+ figcaption {
+ position: relative;
+ justify-content: center;
+ }
+
+ .s_latest_posts_post_cover {
+ min-height: 100%;
+
+ .o_record_cover_container {
+ top: 0;
+ }
+ }
+
+ .s_latest_posts_post_title {
+ @include font-size($h3-font-size);
+ margin-bottom: 0.5em;
+ word-spacing: -0.15em;
+ }
+ }
+
+ .js_get_posts {
+ .s_latest_posts_post > div:not(.o_record_cover_container):not(.js-loading) p {
+ margin: 0;
+ }
+
+ &.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%);
+ }
+ .thumb {
+ transition: opacity 0.35s;
+ }
+ > div:not(.o_record_cover_container):not(.js-loading) {
+ padding: 3em;
+ text-align: left;
+ &:after {
+ @include o-position-absolute(auto, 2em, 2em, 2em);
+ height: calc(50% - 2em);
+ border: 2px solid #fff;
+ content: "";
+ transition: transform 0.35s;
+ transform: translate3d(0, -100%, 0);
+ }
+ }
+ p {
+ @include o-position-absolute(auto, 60px, 60px, 60px);
+ opacity: 0;
+ transition: e( "opacity 0.35s linear 0s, transform 0.35s linear 0s");
+ -webkit-transform: translate3d(0,-100px,0);
+ transform: translate3d(0,-100px,0);
+ }
+ &:hover {
+ .thumb {
+ opacity: 0.4!important;
+ }
+ > div:not(.o_record_cover_container):not(.js-loading)::after {
+ transform: translate3d(0, 0, 0);
+ }
+ p {
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+ }
+ }
+ }
+ &.effect-chico {
+ .o_record_cover_image {
+ transition: opacity 0.35s, transform 0.35s;
+ transform: scale(1.12);
+ }
+ .s_latest_posts_post > div:not(.o_record_cover_container):not(.js-loading) {
+ padding: 3em;
+ &::before {
+ @include o-position-absolute(7%, 7%, 7%, 7%);
+ border: 1px solid #fff;
+ content: "";
+ transform: scale(1.1);
+ opacity: 0;
+ transition: opacity 0.35s, transform 0.35s;
+ }
+ }
+ p {
+ opacity: 0;
+ transition: opacity 0.35s, transform 0.35s;
+ margin: 10% auto 0 !important;
+ max-width: 200px;
+ transform: scale(1.5);
+ }
+ h2 {
+ padding: 0;
+ }
+ .s_latest_posts_post:hover {
+ .o_record_cover_image {
+ transform: scale(1);
+ }
+ > div:not(.o_record_cover_container):not(.js-loading)::before, p {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+ }
+ &.effect-marley {
+ .s_latest_posts_post > div:not(.o_record_cover_container):not(.js-loading) {
+ text-align: right;
+ h2, p {
+ @include o-position-absolute(auto, 30px, auto, 30px);
+ padding: 10px 0;
+ }
+ p {
+ bottom: 30px;
+ line-height: 1.5;
+ -webkit-transform: translate3d(0,100%,0);
+ transform: translate3d(0,100%,0);
+ opacity: 0;
+ -webkit-transition: opacity 0.35s, -webkit-transform 0.35s;
+ transition: opacity 0.35s, transform 0.35s;
+ }
+ h2 {
+ top: 30px;
+ -webkit-transition: -webkit-transform 0.35s;
+ transition: transform 0.35s;
+ -webkit-transform: translate3d(0,20px,0);
+ transform: translate3d(0,20px,0);
+ &:after {
+ @include o-position-absolute(100%, auto, auto, 0);
+ width: 100%;
+ height: 4px;
+ background: #fff;
+ content: "";
+ -webkit-transform: translate3d(0,40px,0);
+ transform: translate3d(0,40px,0);
+ opacity: 0;
+ -webkit-transition: opacity 0.35s, -webkit-transform 0.35s;
+ transition: opacity 0.35s, transform 0.35s;
+ }
+ }
+ }
+ .s_latest_posts_post:hover > div:not(.o_record_cover_container):not(.js-loading) {
+ h2 {
+ -webkit-transform: translate3d(0,0,0);
+ transform: translate3d(0,0,0);
+ }
+ h2::after, p {
+ opacity: 1;
+ -webkit-transform: translate3d(0,0,0);
+ transform: translate3d(0,0,0);
+ }
+ }
+ }
+ &.effect-steve .s_latest_posts_post {
+ z-index: auto;
+ &::before {
+ content: "";
+ @include o-position-absolute(0, $grid-gutter-width/2, 0, $grid-gutter-width/2);
+ background: #000;
+ }
+ > div:not(.o_record_cover_container):not(.js-loading) {
+ z-index: 1;
+ }
+ .thumb {
+ opacity: 1;
+ transition: transform 0.35s;
+ transform: perspective(1000px) translate3d(0,0,0);
+ }
+ h2, p {
+ background: #fff;
+ color: #2d434e;
+ }
+ h2 {
+ position: relative;
+ margin-top: 0.1em;
+ padding: 0.25em;
+ &:before {
+ box-shadow: 0 1px 10px rgba(0,0,0,0.5);
+ }
+ }
+ p {
+ margin-top: 1em !important;
+ padding: 0.5em;
+ font-weight: 800;
+ opacity: 0;
+ transition: opacity 0.35s, transform 0.35s;
+ transform: scale3d(0.9,0.9,1);
+ }
+ &:hover {
+ h2:before {
+ opacity: 0;
+ }
+ .thumb {
+ transform: perspective(1000px) translate3d(0,0,21px);
+ }
+ p {
+ opacity: 1;
+ transform: scale3d(1,1,1);
+ }
+ }
+ }
+
+ &.first_is_big .s_latest_posts_post:first-child {
+ flex: 0 0 100%;
+ max-width: 100%;
+ }
+ }
+}
+
+.s_latest_posts_big_orizontal {
+ .o_record_cover_container {
+ width: auto;
+ height: auto;
+ padding: 0;
+ }
+
+ .js_get_posts {
+ position: relative;
+ width: 100%;
+ display: flex;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ text-align: left;
+ .s_latest_posts_post {
+ //display: table-cell;
+ flex-grow: 1;
+ position: relative;
+ figcaption:after {
+ position: relative;
+ width: 100%;
+ height: 150px;
+ content: "";
+ display: block;
+ }
+ h4 {
+ position: relative;
+ text-align: left;
+ padding-right: 5%;
+ &:before {
+ content: "";
+ z-index: 0;
+ display: inline;
+ float: left;
+ width: 20%;
+ position: absolute;
+ top: 49%;
+ left: 0;
+ border-bottom: 1px solid $body-color;
+ }
+ a {
+ z-index: 1;
+ display: block;
+ line-height: 1;
+ padding-left: 25%;
+ position: relative;
+ }
+ }
+ h5 {
+ padding-left: 24%;
+ }
+ > a {
+ bottom: 0;
+ display: block;
+ background: theme-color('primary');
+ width: 100%;
+ height: 150px;
+ position: absolute;
+ overflow: hidden;
+ > div {
+ height: 100%;
+ width: 100%;
+ background-size: cover;
+ background-position: center;
+ opacity: 1;
+ transform: scale(1);
+ transform-origin: 50%;
+ transition: all 400ms;
+ backface-visibility: hidden;
+ &:hover {
+ opacity: 0.8;
+ transform: scale(1.1);
+ }
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ display: inline-block;
+ margin-bottom: 2em;
+ width: 50%;
+ }
+ @media only screen and (max-width : 480px) { // FIXME
+ width: 100%;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ display: block;
+ }
+ }
+}
diff --git a/addons/website_blog/static/src/snippets/s_latest_posts/001.scss b/addons/website_blog/static/src/snippets/s_latest_posts/001.scss
new file mode 100644
index 00000000..d36d12f3
--- /dev/null
+++ b/addons/website_blog/static/src/snippets/s_latest_posts/001.scss
@@ -0,0 +1,315 @@
+.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 {
+ font-weight: $headings-font-weight;
+ // Tweek line-height to help fit multi-line titles.
+ line-height: 1;
+ }
+
+ .s_latest_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 {
+ // Set sizes relative to the container font-size.
+ // (handle parents with, for example, '.small' or '.h1' classes)
+ .s_latest_posts_post_cover {
+ @include size(3.5em);
+ max: {width: 85px; height: 85px}
+ }
+
+ .s_latest_posts_post_title {
+ font-size: 1.25em;
+ }
+ }
+
+ .s_latest_posts_big_picture {
+ .s_latest_posts_post {
+ min-height: 150px;
+
+ figcaption {
+ position: relative;
+ justify-content: center;
+ }
+
+ .s_latest_posts_post_cover {
+ min-height: 100%;
+
+ .o_record_cover_container {
+ top: 0;
+ }
+ }
+
+ .s_latest_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 {
+ 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_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_latest_posts_effect_dexter .s_latest_posts_post {
+ .o_record_cover_container {
+ transition: opacity 0.35s;
+ }
+ figcaption {
+ &::before {
+ content: "";
+ @include o-position-absolute(0, 0, 0, 0);
+ background: linear-gradient(to bottom, darken(theme-color('secondary'), 10%) 0%, darken(theme-color('secondary'), 30%) 100%);
+ z-index: -1;
+ }
+ 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_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);
+ opacity: 0;
+ transition: opacity 0.35s, transform 0.35s;
+ }
+ }
+ .s_latest_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_latest_posts_post_title {
+ padding: 0;
+ }
+ .s_latest_posts_post:hover {
+ .o_record_cover_image {
+ transform: scale(1);
+ }
+ figcaption::before, .s_latest_posts_post_subtitle {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+ }
+ }
+ }
+
+ .s_latest_posts_horizontal {
+ .o_record_cover_container {
+ width: auto;
+ height: auto;
+ padding: 0;
+ }
+
+ &.js_get_posts {
+ position: relative;
+ overflow: visible;
+ text-align: left;
+ .s_latest_posts_post {
+ position: relative;
+ figcaption:after {
+ position: relative;
+ width: 100%;
+ height: 150px;
+ content: "";
+ display: block;
+ }
+ h4 {
+ position: relative;
+ text-align: left;
+ padding-right: 5%;
+ &:before {
+ content: "";
+ z-index: 0;
+ display: inline;
+ float: left;
+ width: 20%;
+ position: absolute;
+ top: 49%;
+ left: 0;
+ border-bottom: 1px solid $body-color;
+ }
+ a {
+ z-index: 1;
+ display: block;
+ line-height: 1;
+ padding-left: 25%;
+ position: relative;
+ }
+ }
+ h5 {
+ padding-left: 24%;
+ }
+ > a {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ display: block;
+ background: theme-color('primary');
+ width: 100%;
+ height: 150px;
+ overflow: hidden;
+ > div {
+ height: 100%;
+ width: 100%;
+ background-size: cover;
+ background-position: center;
+ opacity: 1;
+ transform-origin: 50%;
+ transition: all 400ms;
+ backface-visibility: hidden;
+ &:hover {
+ opacity: 0.8;
+ transform: scale(1.1);
+ }
+ }
+ }
+ @media only screen and (max-width : 480px) { // FIXME
+ width: 100%;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ display: block;
+ }
+ }
+ }
+
+ .s_latest_posts_card {
+ .card {
+ height: 100%;
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.17);
+
+ .s_latest_posts_post_cover {
+ height: 170px;
+
+ .o_record_cover_container {
+ background-color: transparent !important;
+
+ .o_record_cover_image {
+ @extend .card-img-top;
+ }
+ }
+ }
+
+ a:hover {
+ text-decoration: none;
+ }
+
+ h4 {
+ font-size: 19px;
+ font-weight: 600;
+ }
+
+ .card-footer {
+ background-color: transparent;
+ border-top: 2px solid rgba(0, 0, 0, 0.06);
+
+ .text-muted {
+ color: rgba(52, 58, 64, 0.4) !important;
+ }
+ }
+ }
+ }
+}
diff --git a/addons/website_blog/static/src/snippets/s_latest_posts/options.js b/addons/website_blog/static/src/snippets/s_latest_posts/options.js
new file mode 100644
index 00000000..e70c4041
--- /dev/null
+++ b/addons/website_blog/static/src/snippets/s_latest_posts/options.js
@@ -0,0 +1,32 @@
+odoo.define('website_blog.s_latest_posts_editor', function (require) {
+'use strict';
+
+var sOptions = require('web_editor.snippets.options');
+var wUtils = require('website.utils');
+
+sOptions.registry.js_get_posts_selectBlog = sOptions.Class.extend({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _renderCustomXML: function (uiFragment) {
+ 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);
+ }
+ });
+ },
+});
+});
diff --git a/addons/website_blog/static/src/xml/website_blog_tag.xml b/addons/website_blog/static/src/xml/website_blog_tag.xml
new file mode 100644
index 00000000..e3e720ae
--- /dev/null
+++ b/addons/website_blog/static/src/xml/website_blog_tag.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<t t-name="website_blog.TagListItem">
+ <div class="o_we_tag_wrapper">
+ <span t-raw="tag.name" class="o_we_tag"/>
+ <we-button class="fa fa-minus o_we_text_danger o_we_link"
+ t-att-data-remove-tag="tag.id"
+ data-no-preview="true"/>
+ </div>
+</t>
+
+<t t-name="website_blog.TagSelectItem">
+ <we-button t-att-data-add-tag="tag.id" data-no-preview="true">
+ <t t-raw="tag.name"/>
+ </we-button>
+</t>
+
+</templates>