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/src/js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_blog/static/src/js')
| -rw-r--r-- | addons/website_blog/static/src/js/contentshare.js | 110 | ||||
| -rw-r--r-- | addons/website_blog/static/src/js/tours/website_blog.js | 79 | ||||
| -rw-r--r-- | addons/website_blog/static/src/js/website_blog.editor.js | 380 | ||||
| -rw-r--r-- | addons/website_blog/static/src/js/website_blog.js | 102 |
4 files changed, 671 insertions, 0 deletions
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()); + }, +}); +}); |
