[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
This commit is contained in:
Samuel Degueldre
2019-11-18 09:56:08 +00:00
committed by qsm-odoo
parent bf5b4b6933
commit f615cfda89
4 changed files with 60 additions and 23 deletions
@@ -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);
@@ -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.<br>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, '-'));
},
});
/**
@@ -115,7 +115,6 @@
<div class="col-md-9">
<input type="text" class="form-control o_input_anchor_name" id="anchorName" t-attf-value="#{currentAnchor}" placeholder="Anchor name"/>
<div class="invalid-feedback">
<p class="d-none o_anchor_not_valid">The chosen name is not valid (use only a-Z A-Z 0-9 - _)</p>
<p class="d-none o_anchor_already_exists">The chosen name already exists</p>
</div>
</div>
+2 -3
View File
@@ -1352,13 +1352,12 @@
</div>
<!-- Anchor Name -->
<div data-js="anchorName"
<div data-js="anchor"
data-selector=":not(p).oe_structure > *, :not(p)[data-oe-type=html] > *"
data-exclude=".modal *">
<we-button class="fa fa-fw fa-link"
title="Create a link to target this section"
data-no-preview="true"
data-open-anchor-dialog=""/>
data-no-preview="true"/>
</div>
<!-- Mega Menu settings -->