diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/website_blog/static | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_blog/static')
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 Binary files differnew file mode 100644 index 00000000..36d47e8a --- /dev/null +++ b/addons/website_blog/static/description/icon.png 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 Binary files differnew file mode 100644 index 00000000..6461bf73 --- /dev/null +++ b/addons/website_blog/static/src/img/anonymous.png diff --git a/addons/website_blog/static/src/img/blog_1.jpeg b/addons/website_blog/static/src/img/blog_1.jpeg Binary files differnew file mode 100644 index 00000000..cd737525 --- /dev/null +++ b/addons/website_blog/static/src/img/blog_1.jpeg diff --git a/addons/website_blog/static/src/img/blog_2.jpeg b/addons/website_blog/static/src/img/blog_2.jpeg Binary files differnew file mode 100644 index 00000000..d1e6ebb3 --- /dev/null +++ b/addons/website_blog/static/src/img/blog_2.jpeg diff --git a/addons/website_blog/static/src/img/content_1_1.jpg b/addons/website_blog/static/src/img/content_1_1.jpg Binary files differnew file mode 100644 index 00000000..dd02d230 --- /dev/null +++ b/addons/website_blog/static/src/img/content_1_1.jpg diff --git a/addons/website_blog/static/src/img/content_1_2.jpg b/addons/website_blog/static/src/img/content_1_2.jpg Binary files differnew file mode 100644 index 00000000..5bd85240 --- /dev/null +++ b/addons/website_blog/static/src/img/content_1_2.jpg diff --git a/addons/website_blog/static/src/img/content_2_1.jpg b/addons/website_blog/static/src/img/content_2_1.jpg Binary files differnew file mode 100644 index 00000000..e08effcb --- /dev/null +++ b/addons/website_blog/static/src/img/content_2_1.jpg diff --git a/addons/website_blog/static/src/img/content_2_2.jpg b/addons/website_blog/static/src/img/content_2_2.jpg Binary files differnew file mode 100644 index 00000000..71f9c99f --- /dev/null +++ b/addons/website_blog/static/src/img/content_2_2.jpg diff --git a/addons/website_blog/static/src/img/content_2_3.jpg b/addons/website_blog/static/src/img/content_2_3.jpg Binary files differnew file mode 100644 index 00000000..11e8fbee --- /dev/null +++ b/addons/website_blog/static/src/img/content_2_3.jpg diff --git a/addons/website_blog/static/src/img/content_3_1.jpg b/addons/website_blog/static/src/img/content_3_1.jpg Binary files differnew file mode 100644 index 00000000..d0b1f9f5 --- /dev/null +++ b/addons/website_blog/static/src/img/content_3_1.jpg diff --git a/addons/website_blog/static/src/img/content_4_1.jpg b/addons/website_blog/static/src/img/content_4_1.jpg Binary files differnew file mode 100644 index 00000000..2b432877 --- /dev/null +++ b/addons/website_blog/static/src/img/content_4_1.jpg diff --git a/addons/website_blog/static/src/img/content_5_1.jpg b/addons/website_blog/static/src/img/content_5_1.jpg Binary files differnew file mode 100644 index 00000000..8bd94d06 --- /dev/null +++ b/addons/website_blog/static/src/img/content_5_1.jpg diff --git a/addons/website_blog/static/src/img/content_6_1.jpg b/addons/website_blog/static/src/img/content_6_1.jpg Binary files differnew file mode 100644 index 00000000..9999994d --- /dev/null +++ b/addons/website_blog/static/src/img/content_6_1.jpg diff --git a/addons/website_blog/static/src/img/content_7_1.jpg b/addons/website_blog/static/src/img/content_7_1.jpg Binary files differnew file mode 100644 index 00000000..08c3e5d1 --- /dev/null +++ b/addons/website_blog/static/src/img/content_7_1.jpg diff --git a/addons/website_blog/static/src/img/content_7_2.jpg b/addons/website_blog/static/src/img/content_7_2.jpg Binary files differnew file mode 100644 index 00000000..a67409f1 --- /dev/null +++ b/addons/website_blog/static/src/img/content_7_2.jpg diff --git a/addons/website_blog/static/src/img/cover_1.jpg b/addons/website_blog/static/src/img/cover_1.jpg Binary files differnew file mode 100644 index 00000000..b7e39c27 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_1.jpg diff --git a/addons/website_blog/static/src/img/cover_2.jpg b/addons/website_blog/static/src/img/cover_2.jpg Binary files differnew file mode 100644 index 00000000..8c0d2b77 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_2.jpg diff --git a/addons/website_blog/static/src/img/cover_3.jpg b/addons/website_blog/static/src/img/cover_3.jpg Binary files differnew file mode 100644 index 00000000..39912642 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_3.jpg diff --git a/addons/website_blog/static/src/img/cover_4.jpg b/addons/website_blog/static/src/img/cover_4.jpg Binary files differnew file mode 100644 index 00000000..98bb6ec2 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_4.jpg diff --git a/addons/website_blog/static/src/img/cover_5.jpg b/addons/website_blog/static/src/img/cover_5.jpg Binary files differnew file mode 100644 index 00000000..28b98247 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_5.jpg diff --git a/addons/website_blog/static/src/img/cover_6.jpg b/addons/website_blog/static/src/img/cover_6.jpg Binary files differnew file mode 100644 index 00000000..2dd8f9a5 --- /dev/null +++ b/addons/website_blog/static/src/img/cover_6.jpg diff --git a/addons/website_blog/static/src/img/cover_7.jpg b/addons/website_blog/static/src/img/cover_7.jpg Binary files differnew file mode 100644 index 00000000..8a04795b --- /dev/null +++ b/addons/website_blog/static/src/img/cover_7.jpg 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> |
