summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/website.editor.js
diff options
context:
space:
mode:
authorQuentin (OpenERP) <qdp-launchpad@openerp.com>2014-03-26 16:27:51 +0100
committerQuentin (OpenERP) <qdp-launchpad@openerp.com>2014-03-26 16:27:51 +0100
commite0b27b4ee8e2d26eeae98a4dbc69a3485f39a8a8 (patch)
treea3887e48d1bdeaf27b04c5c4e636e677b9e1c586 /addons/website/static/src/js/website.editor.js
parent78457a17ea7175c02baf823c296b5cefe1a52a03 (diff)
parent0d4d9af5f4b7ca695e33af370848350b437c132e (diff)
[MERGE] merged trunk up to revision 9251
bzr revid: qdp-launchpad@openerp.com-20140326152751-b6bo0fawrzmd71f0
Diffstat (limited to 'addons/website/static/src/js/website.editor.js')
-rw-r--r--addons/website/static/src/js/website.editor.js191
1 files changed, 167 insertions, 24 deletions
diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js
index dc82c0c17b9..ae126ad16ab 100644
--- a/addons/website/static/src/js/website.editor.js
+++ b/addons/website/static/src/js/website.editor.js
@@ -20,6 +20,10 @@
website.form(this.pathname, 'POST');
});
+ $(document).on('click', '.cke_editable label', function (ev) {
+ ev.preventDefault();
+ });
+
$(document).on('submit', '.cke_editable form', function (ev) {
// Disable form submition in editable mode
ev.preventDefault();
@@ -66,8 +70,10 @@
// only enable editors manually
CKEDITOR.disableAutoInline = true;
// EDIT ALL THE THINGS
- CKEDITOR.dtd.$editable = $.extend(
- {}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline);
+ CKEDITOR.dtd.$editable = _.omit(
+ $.extend({}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline),
+ // well maybe not *all* the things
+ 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td');
// Disable removal of empty elements on CKEDITOR activation. Empty
// elements are used for e.g. support of FontAwesome icons
CKEDITOR.dtd.$removeEmpty = {};
@@ -100,6 +106,7 @@
},
canUndo: false,
editorFocus: true,
+ context: 'a',
});
//noinspection JSValidateTypes
editor.addCommand('image', {
@@ -109,6 +116,7 @@
},
canUndo: false,
editorFocus: true,
+ context: 'img',
});
editor.ui.addButton('Link', {
@@ -199,8 +207,9 @@
icon: '/website/static/src/img/bglink.png',
modes: { wysiwyg: true },
editorFocus: true,
+ context: 'a',
panel: {
- css: '/website/static/lib/bootstrap/css/bootstrap.css',
+ css: '/web/static/lib/bootstrap/css/bootstrap.css',
attributes: { 'role': 'listbox', 'aria-label': label },
},
@@ -340,14 +349,40 @@
requires: 'widget',
init: function (editor) {
+ var specials = {
+ // Can't find the correct ACL rule to only allow img tags
+ image: { content: '*' },
+ html: { text: '*' },
+ monetary: {
+ text: {
+ selector: 'span.oe_currency_value',
+ allowedContent: { }
+ }
+ }
+ };
+ _(specials).each(function (editable, type) {
+ editor.widgets.add(type, {
+ draggable: false,
+ editables: editable,
+ upcast: function (el) {
+ return el.attributes['data-oe-type'] === type;
+
+ }
+ });
+ });
editor.widgets.add('oeref', {
- editables: { text: '*' },
draggable: false,
-
+ editables: {
+ text: {
+ selector: '*',
+ allowedContent: { }
+ },
+ },
upcast: function (el) {
- var matches = el.attributes['data-oe-type'] && el.attributes['data-oe-type'] !== 'monetary';
- if (!matches) { return false; }
-
+ var type = el.attributes['data-oe-type'];
+ if (!type || (type in specials)) {
+ return false;
+ }
if (el.attributes['data-oe-original']) {
while (el.children.length) {
el.children[0].remove();
@@ -357,16 +392,9 @@
));
}
return true;
- },
- });
- editor.widgets.add('monetary', {
- editables: { text: 'span.oe_currency_value' },
- draggable: false,
-
- upcast: function (el) {
- return el.attributes['data-oe-type'] === 'monetary';
}
});
+
editor.widgets.add('icons', {
draggable: false,
@@ -377,8 +405,11 @@
});
},
upcast: function (el) {
- return el.attributes['class']
- && (/\bfa\b/.test(el.attributes['class']));
+ return el.hasClass('fa')
+ // ignore ir.ui.view (other data-oe-model should
+ // already have been matched by oeref and
+ // monetary?
+ && !el.attributes['data-oe-model'];
}
});
}
@@ -625,11 +656,11 @@
var $link_button = this.make_hover_button(_t("Change"), function () {
var sel = new CKEDITOR.dom.element(previous);
editor.getSelection().selectElement(sel);
- if (previous.tagName.toUpperCase() === 'A') {
- link_dialog(editor);
- } else if(sel.hasClass('fa')) {
+ if(sel.hasClass('fa')) {
new website.editor.FontIconsDialog(editor, previous)
.appendTo(document.body);
+ } else if (previous.tagName.toUpperCase() === 'A') {
+ link_dialog(editor);
}
$link_button.hide();
previous = null;
@@ -640,6 +671,11 @@
previous = null;
}, 'btn-sm');
+ function is_icons_widget(element) {
+ var w = editor.widgets.getByElement(element);
+ return w && w.name === 'icons';
+ }
+
// previous is the state of the button-trigger: it's the
// currently-ish hovered element which can trigger a button showing.
// -ish, because when moving to the button itself ``previous`` is
@@ -649,8 +685,15 @@
// Back from edit button -> ignore
if (previous && previous === this) { return; }
+ // hover button should appear for "editable" links and images
+ // (img and a nodes whose *attributes* are editable, they
+ // can not be "editing hosts") *or* for non-editing-host
+ // elements bearing an ``fa`` class. These should have been
+ // made into CKE widgets which are editing hosts by
+ // definition, so instead check if the element has been
+ // converted/upcasted to an fa widget
var selected = new CKEDITOR.dom.element(this);
- if (!is_editable_node(selected) && !selected.hasClass('fa')) {
+ if (!(is_editable_node(selected) || is_icons_widget(selected))) {
return;
}
@@ -837,7 +880,6 @@
document.execCommand("enableInlineTableEditing", false, "false");
} catch (e) {}
-
// detect & setup any CKEDITOR widget within a newly dropped
// snippet. There does not seem to be a simple way to do it for
// HTML not inserted via ckeditor APIs:
@@ -1436,6 +1478,7 @@
this.page += $target.hasClass('previous') ? -1 : 1;
this.display_attachments();
},
+ 'click .existing-attachment-remove': 'try_remove',
}),
init: function (parent) {
this.image = null;
@@ -1458,7 +1501,7 @@
kwargs: {
fields: ['name', 'website_url'],
domain: [['res_model', '=', 'ir.ui.view']],
- order: 'name',
+ order: 'id desc',
context: website.get_context(),
}
});
@@ -1468,6 +1511,7 @@
this.display_attachments();
},
display_attachments: function () {
+ this.$('.help-block').empty();
var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
var from = this.page * per_screen;
@@ -1495,6 +1539,34 @@
}
this.close()
},
+
+ try_remove: function (e) {
+ var $help_block = this.$('.help-block').empty();
+ var self = this;
+ var id = parseInt($(e.target).data('id'), 10);
+ var attachment = _.findWhere(this.records, {id: id});
+
+ return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'ir.attachment',
+ method: 'try_remove',
+ args: [],
+ kwargs: {
+ ids: [id],
+ context: website.get_context()
+ }
+ }).then(function (prevented) {
+ if (_.isEmpty(prevented)) {
+ self.records = _.without(self.records, attachment);
+ self.display_attachments();
+ return;
+ }
+ $help_block.replaceWith(openerp.qweb.render(
+ 'website.editor.dialog.image.existing.error', {
+ views: prevented[id]
+ }
+ ));
+ });
+ },
});
function get_selected_link(editor) {
@@ -1684,6 +1756,9 @@
// ignore mutation if the *only* change is .cke_focus
return change.length !== 1 || change[0] === 'cke_focus';
case 'childList':
+ setTimeout(function () {
+ fixup_browser_crap(m.addedNodes);
+ }, 0);
// Remove ignorable nodes from addedNodes or removedNodes,
// if either set remains non-empty it's considered to be an
// impactful change. Otherwise it's ignored.
@@ -1714,6 +1789,9 @@
if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') {
// <br type="_moz"> appears when focusing RTE in FF, ignore
continue;
+ } else if (node.nodeName === 'DIV' && $(node).hasClass('oe_drop_zone')) {
+ // ignore dropzone inserted by snippets
+ continue
}
}
@@ -1721,4 +1799,69 @@
}
return output;
}
+
+ var programmatic_styles = {
+ float: 1,
+ display: 1,
+ position: 1,
+ top: 1,
+ left: 1,
+ right: 1,
+ bottom: 1,
+ };
+ function fixup_browser_crap(nodes) {
+ if (!nodes || !nodes.length) { return; }
+ /**
+ * Checks that the node only has a @style, not e.g. @class or whatever
+ */
+ function has_only_style(node) {
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ if (attr.attributeName !== 'style') {
+ return false;
+ }
+ }
+ return true;
+ }
+ function has_programmatic_style(node) {
+ for (var i = 0; i < node.style.length; i++) {
+ var style = node.style[i];
+ if (programmatic_styles[style]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ for (var i=0; i<nodes.length; ++i) {
+ var node = nodes[i];
+ if (node.nodeType !== document.ELEMENT_NODE) { continue; }
+
+ if (node.nodeName === 'SPAN'
+ && has_only_style(node)
+ && !has_programmatic_style(node)) {
+ // On backspace, webkit browsers create a <span> with a bunch of
+ // inline styles "remembering" where they come from. Refs:
+ // http://www.neotericdesign.com/blog/2013/3/working-around-chrome-s-contenteditable-span-bug
+ // https://code.google.com/p/chromium/issues/detail?id=226941
+ // https://bugs.webkit.org/show_bug.cgi?id=114791
+ // http://dev.ckeditor.com/ticket/9998
+ var child, parent = node.parentNode;
+ while (child = node.firstChild) {
+ parent.insertBefore(child, node);
+ }
+ parent.removeChild(node);
+ // chances are we had e.g.
+ // <p>foo</p>
+ // <p>bar</p>
+ // merged the lines getting this in webkit
+ // <p>foo<span>bar</span></p>
+ // after unwrapping the span, we have 2 text nodes
+ // <p>[foo][bar]</p>
+ // where we probably want only one. Normalize will merge
+ // adjacent text nodes. However, does not merge text and cdata
+ parent.normalize();
+ }
+ }
+ }
})();