From f615cfda89dae2e9a550d25868b11bc06b97cfcb Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Fri, 18 Oct 2019 09:06:09 +0000 Subject: [PATCH] [IMP] website, *: rework snippet anchor system * web_editor Previously, the user could choose their anchor names using a modal window, anchor names also have weird rules, this was not a great user experience. This commit makes the anchor button generate anchors automatically based on the first title in a section, or the snippet name if there are none. If the anchor name is already taken, it is suffixed by a number. After it has been generated, it is also copied to the clipboard and the user is shown a toast to tell them the anchor has been generated and copied. The anchor is displayed in the toast along with an edit button that will open the same edition modal as previously, but it is now less restrictive. Part of https://github.com/odoo/odoo/pull/38959 maintask-2066614 task-2088270 --- .../static/src/js/editor/snippets.editor.js | 4 +- .../static/src/js/editor/snippets.options.js | 73 ++++++++++++++----- .../website/static/src/xml/website.editor.xml | 1 - addons/website/views/snippets.xml | 5 +- 4 files changed, 60 insertions(+), 23 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 3b3b6f8fd86..417d0daabb2 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -453,7 +453,7 @@ var SnippetEditor = Widget.extend({ this, val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target, this.$el, - val.data, + _.extend({snippetName: this._getName()}, val.data), this.options ); var key = optionName || _.uniqueId('option'); @@ -813,7 +813,7 @@ var SnippetsMenu = Widget.extend({ if (!$target.closest('body > *').length) { return; } - if ($target.closest('#web_editor-top-edit, #oe_snippets, #oe_manipulators, .o_technical_modal, .oe_drop_zone').length) { + if ($target.closest('#web_editor-top-edit, #oe_snippets, #oe_manipulators, .o_technical_modal, .oe_drop_zone, .o_notification_manager').length) { return; } this._activateSnippet($target); diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index c3ea729d28d..cb94093c731 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -1547,13 +1547,31 @@ options.registry.topMenuColor = options.registry.colorpicker.extend({ /** * Handles the edition of snippet's anchor name. */ -options.registry.anchorName = options.Class.extend({ +options.registry.anchor = options.Class.extend({ xmlDependencies: ['/website/static/src/xml/website.editor.xml'], //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- + /** + * @override + */ + start: function () { + // Generate anchor and copy it to clipboard on click, show the tooltip on success + this.$button = this.$el.find('we-button'); + const clipboard = new ClipboardJS(this.$button[0], {text: () => this._getAnchorLink()}); + clipboard.on('success', () => { + const anchor = decodeURIComponent(this._getAnchorLink()); + this.displayNotification({ + title: _t("Copied !"), + message: _.str.sprintf(_t("The anchor has been copied to your clipboard.
Link: %s"), anchor), + buttons: [{text: _t("edit"), click: () => this.openAnchorDialog()}], + }); + }); + + return this._super.apply(this, arguments); + }, /** * @override */ @@ -1568,20 +1586,19 @@ options.registry.anchorName = options.Class.extend({ }, //-------------------------------------------------------------------------- - // Options + // Private //-------------------------------------------------------------------------- - /** * @see this.selectClass for parameters */ openAnchorDialog: function (previewMode, value, $opt) { var self = this; var buttons = [{ - text: _t("Save"), + text: _t("Save & copy"), classes: 'btn-primary', click: function () { var $input = this.$('.o_input_anchor_name'); - var anchorName = $input.val().trim().replace(/\s/g, '_'); + var anchorName = self._text2Anchor($input.val()); if (self.$target[0].id === anchorName) { // If the chosen anchor name is already the one used by the // element, close the dialog and do nothing else @@ -1589,15 +1606,13 @@ options.registry.anchorName = options.Class.extend({ return; } - var isValid = /^[\w-]+$/.test(anchorName); - var alreadyExists = isValid && $('#' + anchorName).length > 0; - var anchorOK = isValid && !alreadyExists; - this.$('.o_anchor_not_valid').toggleClass('d-none', isValid); + const alreadyExists = !!document.getElementById(anchorName); this.$('.o_anchor_already_exists').toggleClass('d-none', !alreadyExists); - $input.toggleClass('is-invalid', !anchorOK); - if (anchorOK) { + $input.toggleClass('is-invalid', alreadyExists); + if (!alreadyExists) { self._setAnchorName(anchorName); this.close(); + self.$button[0].click(); } }, }, { @@ -1618,16 +1633,11 @@ options.registry.anchorName = options.Class.extend({ new Dialog(this, { title: _t("Link Anchor"), $content: $(qweb.render('website.dialog.anchorName', { - currentAnchor: this.$target.attr('id'), + currentAnchor: decodeURIComponent(this.$target.attr('id')), })), buttons: buttons, }).open(); }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - /** * @private * @param {String} value @@ -1643,6 +1653,35 @@ options.registry.anchorName = options.Class.extend({ } this.$target.trigger('content_changed'); }, + /** + * Returns anchor text. + * + * @private + * @returns {string} + */ + _getAnchorLink: function () { + if (!this.$target[0].id) { + const $titles = this.$target.find('h1, h2, h3, h4, h5, h6'); + const title = $titles.length > 0 ? $titles[0].innerText : this.data.snippetName; + const anchorName = this._text2Anchor(title); + let n = ''; + while (document.getElementById(anchorName + n)) { + n = (n || 1) + 1; + } + this._setAnchorName(anchorName + n); + } + return `#${this.$target[0].id}`; + }, + /** + * Creates a safe id/anchor from text. + * + * @private + * @param {string} text + * @returns {string} + */ + _text2Anchor: function (text) { + return encodeURIComponent(text.trim().replace(/\s+/g, '-')); + }, }); /** diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 9f8a2e302fc..31ffb28704d 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -115,7 +115,6 @@
-

The chosen name is not valid (use only a-Z A-Z 0-9 - _)

The chosen name already exists

diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index e496d088f50..955babf1637 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -1352,13 +1352,12 @@ -
+ data-no-preview="true"/>