2013-08-29 10:29:56 +00:00
|
|
|
(function () {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var website = openerp.website;
|
2013-11-13 14:39:34 +00:00
|
|
|
var _t = openerp._t;
|
2013-08-29 10:29:56 +00:00
|
|
|
|
2013-10-29 16:11:28 +00:00
|
|
|
website.add_template_file('/website/static/src/xml/website.editor.xml');
|
2013-08-29 15:34:41 +00:00
|
|
|
website.dom_ready.done(function () {
|
|
|
|
var is_smartphone = $(document.body)[0].clientWidth < 767;
|
|
|
|
|
2013-10-09 13:55:04 +00:00
|
|
|
if (!is_smartphone) {
|
2013-08-29 15:34:41 +00:00
|
|
|
website.ready().then(website.init_editor);
|
2014-02-04 15:36:16 +00:00
|
|
|
} else {
|
|
|
|
// remove padding of fake editor bar
|
|
|
|
document.body.style.padding = 0;
|
2013-08-29 15:34:41 +00:00
|
|
|
}
|
2013-11-06 11:57:54 +00:00
|
|
|
|
2013-11-13 17:14:38 +00:00
|
|
|
$(document).on('click', 'a.js_link2post', function (ev) {
|
|
|
|
ev.preventDefault();
|
2013-11-19 11:06:12 +00:00
|
|
|
website.form(this.pathname, 'POST');
|
2013-11-13 17:14:38 +00:00
|
|
|
});
|
|
|
|
|
2014-02-27 14:00:43 +00:00
|
|
|
$(document).on('click', '.cke_editable label', function (ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
|
2013-12-04 10:42:25 +00:00
|
|
|
$(document).on('submit', '.cke_editable form', function (ev) {
|
|
|
|
// Disable form submition in editable mode
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
|
2013-11-06 11:57:54 +00:00
|
|
|
$(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
|
|
|
|
// Prevent dropdown closing when a contenteditable children is focused
|
|
|
|
if (ev.originalEvent
|
|
|
|
&& $(ev.target).has(ev.originalEvent.target).length
|
|
|
|
&& $(ev.originalEvent.target).is('[contenteditable]')) {
|
|
|
|
ev.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
2013-08-29 15:34:41 +00:00
|
|
|
});
|
|
|
|
|
2013-12-16 13:44:24 +00:00
|
|
|
/**
|
|
|
|
* An editing host is an HTML element with @contenteditable=true, or the
|
|
|
|
* child of a document in designMode=on (but that one's not supported)
|
|
|
|
*
|
|
|
|
* https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#editing-host
|
|
|
|
*/
|
|
|
|
function is_editing_host(element) {
|
|
|
|
return element.getAttribute('contentEditable') === 'true';
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Checks that both the element's content *and the element itself* are
|
|
|
|
* editable: an editing host is considered non-editable because its content
|
|
|
|
* is editable but its attributes should not be considered editable
|
|
|
|
*/
|
|
|
|
function is_editable_node(element) {
|
2014-03-11 13:08:52 +00:00
|
|
|
return !(element.data('oe-model') === 'ir.ui.view'
|
2013-12-16 13:44:24 +00:00
|
|
|
|| element.data('cke-realelement')
|
2014-01-15 14:17:56 +00:00
|
|
|
|| (is_editing_host(element) && element.getAttribute('attributeEditable') !== 'true')
|
2013-12-16 13:44:24 +00:00
|
|
|
|| element.isReadOnly());
|
|
|
|
}
|
|
|
|
|
2013-08-29 15:34:41 +00:00
|
|
|
function link_dialog(editor) {
|
2013-10-28 16:26:23 +00:00
|
|
|
return new website.editor.RTELinkDialog(editor).appendTo(document.body);
|
2013-08-29 15:34:41 +00:00
|
|
|
}
|
2013-11-21 11:07:48 +00:00
|
|
|
function image_dialog(editor, image) {
|
2014-03-26 15:58:22 +00:00
|
|
|
return new website.editor.MediaDialog(editor, image).appendTo(document.body);
|
2013-09-02 07:52:16 +00:00
|
|
|
}
|
2013-08-29 15:34:41 +00:00
|
|
|
|
2013-10-09 13:55:04 +00:00
|
|
|
// only enable editors manually
|
|
|
|
CKEDITOR.disableAutoInline = true;
|
|
|
|
// EDIT ALL THE THINGS
|
2014-02-25 13:12:28 +00:00
|
|
|
CKEDITOR.dtd.$editable = _.omit(
|
|
|
|
$.extend({}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline),
|
|
|
|
// well maybe not *all* the things
|
|
|
|
'ul', 'ol', 'li', 'table', 'tr', 'th', 'td');
|
2013-10-09 13:55:04 +00:00
|
|
|
// Disable removal of empty elements on CKEDITOR activation. Empty
|
|
|
|
// elements are used for e.g. support of FontAwesome icons
|
|
|
|
CKEDITOR.dtd.$removeEmpty = {};
|
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
website.init_editor = function () {
|
2013-08-29 15:34:41 +00:00
|
|
|
CKEDITOR.plugins.add('customdialogs', {
|
2014-03-26 15:58:22 +00:00
|
|
|
// requires: 'link,image',
|
2013-08-29 15:34:41 +00:00
|
|
|
init: function (editor) {
|
|
|
|
editor.on('doubleclick', function (evt) {
|
2013-09-16 09:03:53 +00:00
|
|
|
var element = evt.data.element;
|
2014-03-28 14:16:19 +00:00
|
|
|
if ((element.is('img') || element.$.className.indexOf(' fa-') != -1) && is_editable_node(element)) {
|
2013-11-21 11:07:48 +00:00
|
|
|
image_dialog(editor, element);
|
2013-09-16 09:03:53 +00:00
|
|
|
return;
|
2013-08-29 15:34:41 +00:00
|
|
|
}
|
2014-03-28 14:16:19 +00:00
|
|
|
var parent = new CKEDITOR.dom.element(element.$.parentNode);
|
|
|
|
if (parent.$.className.indexOf('media_iframe_video') != -1 && is_editable_node(parent)) {
|
|
|
|
image_dialog(editor, parent);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-16 09:03:53 +00:00
|
|
|
|
|
|
|
element = get_selected_link(editor) || evt.data.element;
|
2013-12-16 13:44:24 +00:00
|
|
|
if (!(element.is('a') && is_editable_node(element))) {
|
2013-09-20 09:42:07 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-09-16 09:03:53 +00:00
|
|
|
|
|
|
|
editor.getSelection().selectElement(element);
|
|
|
|
link_dialog(editor);
|
2013-08-29 15:34:41 +00:00
|
|
|
}, null, null, 500);
|
|
|
|
|
2013-09-09 10:42:38 +00:00
|
|
|
//noinspection JSValidateTypes
|
2013-08-29 15:34:41 +00:00
|
|
|
editor.addCommand('link', {
|
2013-10-22 15:11:44 +00:00
|
|
|
exec: function (editor) {
|
2013-08-29 15:34:41 +00:00
|
|
|
link_dialog(editor);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
canUndo: false,
|
|
|
|
editorFocus: true,
|
2014-03-13 09:37:07 +00:00
|
|
|
context: 'a',
|
2013-08-29 15:34:41 +00:00
|
|
|
});
|
2013-09-09 10:42:38 +00:00
|
|
|
//noinspection JSValidateTypes
|
2014-04-07 14:49:14 +00:00
|
|
|
editor.addCommand('cimage', {
|
2013-10-22 15:11:44 +00:00
|
|
|
exec: function (editor) {
|
2013-09-02 07:52:16 +00:00
|
|
|
image_dialog(editor);
|
2013-08-29 15:34:41 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
canUndo: false,
|
|
|
|
editorFocus: true,
|
2014-03-13 09:37:07 +00:00
|
|
|
context: 'img',
|
2013-08-29 15:34:41 +00:00
|
|
|
});
|
2013-09-16 09:03:53 +00:00
|
|
|
|
|
|
|
editor.ui.addButton('Link', {
|
|
|
|
label: 'Link',
|
|
|
|
command: 'link',
|
|
|
|
toolbar: 'links,10',
|
|
|
|
});
|
|
|
|
editor.ui.addButton('Image', {
|
|
|
|
label: 'Image',
|
2014-04-07 14:49:14 +00:00
|
|
|
command: 'cimage',
|
2013-09-16 09:03:53 +00:00
|
|
|
toolbar: 'insert,10',
|
|
|
|
});
|
2013-09-16 09:07:50 +00:00
|
|
|
|
|
|
|
editor.setKeystroke(CKEDITOR.CTRL + 76 /*L*/, 'link');
|
2013-09-06 08:50:45 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
CKEDITOR.plugins.add( 'tablebutton', {
|
|
|
|
requires: 'panelbutton,floatpanel',
|
|
|
|
init: function( editor ) {
|
|
|
|
var label = "Table";
|
|
|
|
|
|
|
|
editor.ui.add('TableButton', CKEDITOR.UI_PANELBUTTON, {
|
|
|
|
label: label,
|
|
|
|
title: label,
|
|
|
|
// use existing 'table' icon
|
|
|
|
icon: 'table',
|
|
|
|
modes: { wysiwyg: true },
|
|
|
|
editorFocus: true,
|
|
|
|
// panel opens in iframe, @css is CSS file <link>-ed within
|
|
|
|
// frame document, @attributes are set on iframe itself.
|
|
|
|
panel: {
|
|
|
|
css: '/website/static/src/css/editor.css',
|
|
|
|
attributes: { 'role': 'listbox', 'aria-label': label, },
|
|
|
|
},
|
|
|
|
|
|
|
|
onBlock: function (panel, block) {
|
|
|
|
block.autoSize = true;
|
|
|
|
block.element.setHtml(openerp.qweb.render('website.editor.table.panel', {
|
|
|
|
rows: 5,
|
|
|
|
cols: 5,
|
|
|
|
}));
|
|
|
|
|
|
|
|
var $table = $(block.element.$).on('mouseenter', 'td', function (e) {
|
|
|
|
var $e = $(e.target);
|
|
|
|
var y = $e.index() + 1;
|
|
|
|
var x = $e.closest('tr').index() + 1;
|
2013-08-29 15:34:41 +00:00
|
|
|
|
2013-09-06 08:50:45 +00:00
|
|
|
$table
|
|
|
|
.find('td').removeClass('selected').end()
|
|
|
|
.find('tr:lt(' + String(x) + ')')
|
|
|
|
.children().filter(function () { return $(this).index() < y; })
|
|
|
|
.addClass('selected');
|
|
|
|
}).on('click', 'td', function (e) {
|
|
|
|
var $e = $(e.target);
|
|
|
|
|
2013-09-09 10:42:38 +00:00
|
|
|
//noinspection JSPotentiallyInvalidConstructorUsage
|
2013-09-06 08:50:45 +00:00
|
|
|
var table = new CKEDITOR.dom.element(
|
|
|
|
$(openerp.qweb.render('website.editor.table', {
|
|
|
|
rows: $e.closest('tr').index() + 1,
|
|
|
|
cols: $e.index() + 1,
|
|
|
|
}))[0]);
|
|
|
|
|
|
|
|
editor.insertElement(table);
|
|
|
|
setTimeout(function () {
|
2013-09-09 10:42:38 +00:00
|
|
|
//noinspection JSPotentiallyInvalidConstructorUsage
|
2013-09-06 08:50:45 +00:00
|
|
|
var firstCell = new CKEDITOR.dom.element(table.$.rows[0].cells[0]);
|
|
|
|
var range = editor.createRange();
|
|
|
|
range.moveToPosition(firstCell, CKEDITOR.POSITION_AFTER_START);
|
|
|
|
range.select();
|
|
|
|
}, 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
block.element.getDocument().getBody().setStyle('overflow', 'hidden');
|
|
|
|
CKEDITOR.ui.fire('ready', this);
|
|
|
|
},
|
|
|
|
});
|
2013-08-29 15:34:41 +00:00
|
|
|
}
|
|
|
|
});
|
2013-09-06 08:50:45 +00:00
|
|
|
|
2013-11-22 15:11:24 +00:00
|
|
|
CKEDITOR.plugins.add('linkstyle', {
|
|
|
|
requires: 'panelbutton,floatpanel',
|
|
|
|
init: function (editor) {
|
|
|
|
var label = "Link Style";
|
|
|
|
|
|
|
|
editor.ui.add('LinkStyle', CKEDITOR.UI_PANELBUTTON, {
|
|
|
|
label: label,
|
|
|
|
title: label,
|
|
|
|
icon: '/website/static/src/img/bglink.png',
|
|
|
|
modes: { wysiwyg: true },
|
|
|
|
editorFocus: true,
|
2014-03-13 09:37:07 +00:00
|
|
|
context: 'a',
|
2013-11-22 15:11:24 +00:00
|
|
|
panel: {
|
2014-03-13 09:38:22 +00:00
|
|
|
css: '/web/static/lib/bootstrap/css/bootstrap.css',
|
2013-11-22 15:11:24 +00:00
|
|
|
attributes: { 'role': 'listbox', 'aria-label': label },
|
|
|
|
},
|
|
|
|
|
|
|
|
types: {
|
|
|
|
'btn-default': _t("Basic"),
|
|
|
|
'btn-primary': _t("Primary"),
|
|
|
|
'btn-success': _t("Success"),
|
|
|
|
'btn-info': _t("Info"),
|
|
|
|
'btn-warning': _t("Warning"),
|
|
|
|
'btn-danger': _t("Danger"),
|
|
|
|
},
|
|
|
|
sizes: {
|
|
|
|
'btn-xs': _t("Extra Small"),
|
|
|
|
'btn-sm': _t("Small"),
|
|
|
|
'': _t("Default"),
|
|
|
|
'btn-lg': _t("Large")
|
|
|
|
},
|
|
|
|
|
|
|
|
onRender: function () {
|
|
|
|
var self = this;
|
|
|
|
editor.on('selectionChange', function (e) {
|
|
|
|
var path = e.data.path, el;
|
|
|
|
|
|
|
|
if (!(e = path.contains('a')) || e.isReadOnly()) {
|
|
|
|
self.disable();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.enable();
|
|
|
|
});
|
|
|
|
// no hook where button is available, so wait
|
|
|
|
// "some time" after render.
|
|
|
|
setTimeout(function () {
|
|
|
|
self.disable();
|
|
|
|
}, 0)
|
|
|
|
},
|
|
|
|
enable: function () {
|
|
|
|
this.setState(CKEDITOR.TRISTATE_OFF);
|
|
|
|
},
|
|
|
|
disable: function () {
|
|
|
|
this.setState(CKEDITOR.TRISTATE_DISABLED);
|
|
|
|
},
|
|
|
|
|
|
|
|
onOpen: function () {
|
|
|
|
var link = get_selected_link(editor);
|
|
|
|
var id = this._.id;
|
|
|
|
var block = this._.panel._.panel._.blocks[id];
|
|
|
|
var $root = $(block.element.$);
|
|
|
|
$root.find('button').removeClass('active').removeProp('disabled');
|
|
|
|
|
|
|
|
// enable buttons matching link state
|
|
|
|
for (var type in this.types) {
|
|
|
|
if (!this.types.hasOwnProperty(type)) { continue; }
|
|
|
|
if (!link.hasClass(type)) { continue; }
|
|
|
|
|
|
|
|
$root.find('button[data-type=types].' + type)
|
|
|
|
.addClass('active');
|
|
|
|
}
|
|
|
|
var found;
|
|
|
|
for (var size in this.sizes) {
|
|
|
|
if (!this.sizes.hasOwnProperty(size)) { continue; }
|
|
|
|
if (!size || !link.hasClass(size)) { continue; }
|
|
|
|
found = true;
|
|
|
|
$root.find('button[data-type=sizes].' + size)
|
|
|
|
.addClass('active');
|
|
|
|
}
|
|
|
|
if (!found && link.hasClass('btn')) {
|
|
|
|
$root.find('button[data-type="sizes"][data-set-class=""]')
|
|
|
|
.addClass('active');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onBlock: function (panel, block) {
|
|
|
|
var self = this;
|
|
|
|
block.autoSize = true;
|
|
|
|
|
|
|
|
var html = ['<div style="padding: 5px">'];
|
|
|
|
html.push('<div style="white-space: nowrap">');
|
|
|
|
_(this.types).each(function (label, key) {
|
|
|
|
html.push(_.str.sprintf(
|
|
|
|
'<button type="button" class="btn %s" ' +
|
|
|
|
'data-type="types" data-set-class="%s">%s</button>',
|
|
|
|
key, key, label));
|
|
|
|
});
|
|
|
|
html.push('</div>');
|
|
|
|
html.push('<div style="white-space: nowrap; margin: 5px 0; text-align: center">');
|
|
|
|
_(this.sizes).each(function (label, key) {
|
|
|
|
html.push(_.str.sprintf(
|
|
|
|
'<button type="button" class="btn btn-default %s" ' +
|
|
|
|
'data-type="sizes" data-set-class="%s">%s</button>',
|
|
|
|
key, key, label));
|
|
|
|
});
|
|
|
|
html.push('</div>');
|
|
|
|
html.push('<button type="button" class="btn btn-link btn-block" ' +
|
|
|
|
'data-type="reset">Reset</button>');
|
|
|
|
html.push('</div>');
|
|
|
|
|
|
|
|
block.element.setHtml(html.join(' '));
|
|
|
|
var $panel = $(block.element.$);
|
|
|
|
$panel.on('click', 'button', function () {
|
|
|
|
self.clicked(this);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
clicked: function (button) {
|
|
|
|
editor.focus();
|
|
|
|
editor.fire('saveSnapshot');
|
|
|
|
|
|
|
|
var $button = $(button),
|
|
|
|
$link = $(get_selected_link(editor).$);
|
|
|
|
if (!$link.hasClass('btn')) {
|
|
|
|
$link.addClass('btn btn-default');
|
|
|
|
}
|
|
|
|
switch($button.data('type')) {
|
|
|
|
case 'reset':
|
|
|
|
$link.removeClass('btn')
|
|
|
|
.removeClass(_.keys(this.types).join(' '))
|
|
|
|
.removeClass(_.keys(this.sizes).join(' '));
|
|
|
|
break;
|
|
|
|
case 'types':
|
|
|
|
$link.removeClass(_.keys(this.types).join(' '))
|
|
|
|
.addClass($button.data('set-class'));
|
|
|
|
break;
|
|
|
|
case 'sizes':
|
|
|
|
$link.removeClass(_.keys(this.sizes).join(' '))
|
|
|
|
.addClass($button.data('set-class'));
|
|
|
|
}
|
|
|
|
this._.panel.hide();
|
|
|
|
|
|
|
|
editor.fire('saveSnapshot');
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-09-09 09:44:37 +00:00
|
|
|
CKEDITOR.plugins.add('oeref', {
|
|
|
|
requires: 'widget',
|
|
|
|
|
|
|
|
init: function (editor) {
|
2014-03-13 09:32:02 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2013-09-09 09:44:37 +00:00
|
|
|
editor.widgets.add('oeref', {
|
2013-12-04 16:28:05 +00:00
|
|
|
draggable: false,
|
2014-03-13 09:32:02 +00:00
|
|
|
editables: {
|
|
|
|
text: {
|
|
|
|
selector: '*',
|
|
|
|
allowedContent: { }
|
|
|
|
},
|
|
|
|
},
|
2013-09-09 09:44:37 +00:00
|
|
|
upcast: function (el) {
|
2014-03-13 09:32:02 +00:00
|
|
|
var type = el.attributes['data-oe-type'];
|
|
|
|
if (!type || (type in specials)) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-12-05 11:39:08 +00:00
|
|
|
if (el.attributes['data-oe-original']) {
|
|
|
|
while (el.children.length) {
|
|
|
|
el.children[0].remove();
|
|
|
|
}
|
|
|
|
el.add(new CKEDITOR.htmlParser.text(
|
|
|
|
el.attributes['data-oe-original']
|
|
|
|
));
|
|
|
|
}
|
|
|
|
return true;
|
2013-10-11 13:56:16 +00:00
|
|
|
}
|
|
|
|
});
|
2014-03-13 09:32:02 +00:00
|
|
|
|
2014-01-16 09:47:47 +00:00
|
|
|
editor.widgets.add('icons', {
|
|
|
|
draggable: false,
|
|
|
|
|
|
|
|
init: function () {
|
|
|
|
this.on('edit', function () {
|
2014-03-27 16:03:24 +00:00
|
|
|
new website.editor.MediaDialog(editor, this.element)
|
2014-01-16 09:47:47 +00:00
|
|
|
.appendTo(document.body);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
upcast: function (el) {
|
2014-03-12 14:19:10 +00:00
|
|
|
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'];
|
2014-01-16 09:47:47 +00:00
|
|
|
}
|
|
|
|
});
|
2013-09-09 09:44:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
var editor = new website.EditorBar();
|
|
|
|
var $body = $(document.body);
|
2013-09-10 12:26:46 +00:00
|
|
|
editor.prependTo($body).then(function () {
|
2013-10-23 09:11:52 +00:00
|
|
|
if (location.search.indexOf("enable_editor") >= 0) {
|
2013-09-10 12:26:46 +00:00
|
|
|
editor.edit();
|
|
|
|
}
|
|
|
|
});
|
2014-04-16 10:59:01 +00:00
|
|
|
website.editor_bar = editor;
|
2013-08-29 10:29:56 +00:00
|
|
|
};
|
2013-12-16 13:44:24 +00:00
|
|
|
|
|
|
|
/* ----- TOP EDITOR BAR FOR ADMIN ---- */
|
2013-08-29 10:29:56 +00:00
|
|
|
website.EditorBar = openerp.Widget.extend({
|
|
|
|
template: 'website.editorbar',
|
|
|
|
events: {
|
|
|
|
'click button[data-action=edit]': 'edit',
|
|
|
|
'click button[data-action=save]': 'save',
|
2013-12-11 16:33:05 +00:00
|
|
|
'click a[data-action=cancel]': 'cancel',
|
2013-08-29 10:29:56 +00:00
|
|
|
},
|
|
|
|
container: 'body',
|
|
|
|
customize_setup: function() {
|
|
|
|
var self = this;
|
|
|
|
var view_name = $(document.documentElement).data('view-xmlid');
|
2014-01-16 18:05:35 +00:00
|
|
|
if (!view_name) {
|
|
|
|
this.$('#customize-menu-button').addClass("hidden");
|
|
|
|
}
|
2013-08-29 10:29:56 +00:00
|
|
|
var menu = $('#customize-menu');
|
|
|
|
this.$('#customize-menu-button').click(function(event) {
|
|
|
|
menu.empty();
|
|
|
|
openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then(
|
|
|
|
function(result) {
|
|
|
|
_.each(result, function (item) {
|
2014-02-06 13:50:36 +00:00
|
|
|
if (item.xml_id === "website.debugger" && !window.location.search.match(/[&?]debug(&|$)/)) return;
|
2013-08-29 10:29:56 +00:00
|
|
|
if (item.header) {
|
2013-08-29 13:24:42 +00:00
|
|
|
menu.append('<li class="dropdown-header">' + item.name + '</li>');
|
2013-08-29 10:29:56 +00:00
|
|
|
} else {
|
2013-12-03 15:55:36 +00:00
|
|
|
menu.append(_.str.sprintf('<li role="presentation"><a href="#" data-view-id="%s" role="menuitem"><strong class="fa fa%s-square-o"></strong> %s</a></li>',
|
2013-12-04 16:17:54 +00:00
|
|
|
item.id, item.active ? '-check' : '', item.name));
|
2013-08-29 10:29:56 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
// Adding Static Menus
|
2013-11-09 14:27:51 +00:00
|
|
|
menu.append('<li class="divider"></li>');
|
2014-01-23 17:27:14 +00:00
|
|
|
menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>');
|
2013-11-09 14:27:51 +00:00
|
|
|
menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
|
2013-11-18 12:49:58 +00:00
|
|
|
menu.append('<li><a href="/web#return_label=Website&action=website.action_module_website">Install Apps</a></li>');
|
2013-10-09 16:09:49 +00:00
|
|
|
self.trigger('rte:customize_menu_ready');
|
2013-08-29 10:29:56 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
2013-09-09 21:46:26 +00:00
|
|
|
menu.on('click', 'a[data-action!=ace]', function (event) {
|
2013-09-02 15:11:40 +00:00
|
|
|
var view_id = $(event.currentTarget).data('view-id');
|
2014-05-27 09:54:01 +00:00
|
|
|
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
|
|
|
model: 'ir.ui.view',
|
|
|
|
method: 'toggle',
|
|
|
|
args: [],
|
|
|
|
kwargs: {
|
|
|
|
ids: [parseInt(view_id, 10)],
|
|
|
|
context: website.get_context()
|
|
|
|
}
|
2013-10-22 15:11:44 +00:00
|
|
|
}).then( function() {
|
2013-08-29 10:29:56 +00:00
|
|
|
window.location.reload();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
start: function() {
|
2014-02-04 15:36:16 +00:00
|
|
|
// remove placeholder editor bar
|
|
|
|
var fakebar = document.getElementById('website-top-navbar-placeholder');
|
2014-02-05 10:36:23 +00:00
|
|
|
if (fakebar) {
|
|
|
|
fakebar.parentNode.removeChild(fakebar);
|
|
|
|
}
|
2014-02-04 15:36:16 +00:00
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
var self = this;
|
|
|
|
this.saving_mutex = new openerp.Mutex();
|
|
|
|
|
|
|
|
this.$('#website-top-edit').hide();
|
|
|
|
this.$('#website-top-view').show();
|
|
|
|
|
|
|
|
$('.dropdown-toggle').dropdown();
|
|
|
|
this.customize_setup();
|
|
|
|
|
|
|
|
this.$buttons = {
|
|
|
|
edit: this.$('button[data-action=edit]'),
|
|
|
|
save: this.$('button[data-action=save]'),
|
|
|
|
cancel: this.$('button[data-action=cancel]'),
|
|
|
|
};
|
|
|
|
|
|
|
|
this.rte = new website.RTE(this);
|
|
|
|
this.rte.on('change', this, this.proxy('rte_changed'));
|
2013-09-23 13:03:21 +00:00
|
|
|
this.rte.on('rte:ready', this, function () {
|
2013-12-04 10:52:12 +00:00
|
|
|
self.setup_hover_buttons();
|
2013-09-23 13:03:21 +00:00
|
|
|
self.trigger('rte:ready');
|
2013-12-18 17:39:15 +00:00
|
|
|
self.check_height();
|
2013-09-18 15:48:31 +00:00
|
|
|
});
|
2013-08-29 10:29:56 +00:00
|
|
|
|
2013-12-18 17:39:15 +00:00
|
|
|
$(window).on('resize', _.debounce(this.check_height.bind(this), 50));
|
|
|
|
this.check_height();
|
|
|
|
|
2014-01-21 12:39:12 +00:00
|
|
|
if (website.is_editable_button) {
|
|
|
|
this.$("button[data-action=edit]").removeClass("hidden");
|
|
|
|
}
|
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
return $.when(
|
|
|
|
this._super.apply(this, arguments),
|
2013-09-16 14:42:35 +00:00
|
|
|
this.rte.appendTo(this.$('#website-top-edit .nav.pull-right'))
|
2013-12-18 17:39:15 +00:00
|
|
|
).then(function () {
|
|
|
|
self.check_height();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
check_height: function () {
|
|
|
|
var editor_height = this.$el.outerHeight();
|
2013-12-18 20:16:19 +00:00
|
|
|
if (this.get('height') != editor_height) {
|
|
|
|
$(document.body).css('padding-top', editor_height);
|
|
|
|
this.set('height', editor_height);
|
2013-12-18 17:39:15 +00:00
|
|
|
}
|
2013-08-29 10:29:56 +00:00
|
|
|
},
|
|
|
|
edit: function () {
|
|
|
|
this.$buttons.edit.prop('disabled', true);
|
|
|
|
this.$('#website-top-view').hide();
|
|
|
|
this.$('#website-top-edit').show();
|
2013-09-04 08:49:58 +00:00
|
|
|
$('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");
|
2013-08-29 10:29:56 +00:00
|
|
|
|
2013-12-18 17:39:15 +00:00
|
|
|
this.rte.start_edition().then(this.check_height.bind(this));
|
2014-01-30 21:00:01 +00:00
|
|
|
this.trigger('rte:called');
|
2013-08-29 10:29:56 +00:00
|
|
|
},
|
|
|
|
rte_changed: function () {
|
|
|
|
this.$buttons.save.prop('disabled', false);
|
|
|
|
},
|
|
|
|
save: function () {
|
|
|
|
var self = this;
|
2013-09-09 09:44:37 +00:00
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
observer.disconnect();
|
2013-09-17 08:57:53 +00:00
|
|
|
var editor = this.rte.editor;
|
2014-04-14 08:14:51 +00:00
|
|
|
var root = editor.element && editor.element.$;
|
2013-09-17 08:57:53 +00:00
|
|
|
editor.destroy();
|
|
|
|
// FIXME: select editables then filter by dirty?
|
|
|
|
var defs = this.rte.fetch_editables(root)
|
|
|
|
.filter('.oe_dirty')
|
2013-10-23 13:09:18 +00:00
|
|
|
.removeAttr('contentEditable')
|
|
|
|
.removeClass('oe_dirty oe_editable cke_focus oe_carlos_danger')
|
2013-09-17 08:57:53 +00:00
|
|
|
.map(function () {
|
|
|
|
var $el = $(this);
|
2013-09-09 09:44:37 +00:00
|
|
|
// TODO: Add a queue with concurrency limit in webclient
|
|
|
|
// https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js
|
|
|
|
return self.saving_mutex.exec(function () {
|
2013-09-17 08:57:53 +00:00
|
|
|
return self.saveElement($el)
|
2013-10-23 13:09:18 +00:00
|
|
|
.then(undefined, function (thing, response) {
|
|
|
|
// because ckeditor regenerates all the dom,
|
|
|
|
// we can't just setup the popover here as
|
|
|
|
// everything will be destroyed by the DOM
|
|
|
|
// regeneration. Add markings instead, and
|
|
|
|
// returns a new rejection with all relevant
|
|
|
|
// info
|
|
|
|
var id = _.uniqueId('carlos_danger_');
|
|
|
|
$el.addClass('oe_dirty oe_carlos_danger');
|
|
|
|
$el.addClass(id);
|
|
|
|
return $.Deferred().reject({
|
|
|
|
id: id,
|
|
|
|
error: response.data,
|
|
|
|
});
|
2013-09-09 09:44:37 +00:00
|
|
|
});
|
2013-08-29 10:29:56 +00:00
|
|
|
});
|
2013-09-17 08:57:53 +00:00
|
|
|
}).get();
|
2013-08-29 10:29:56 +00:00
|
|
|
return $.when.apply(null, defs).then(function () {
|
2013-09-26 13:17:08 +00:00
|
|
|
website.reload();
|
2013-10-23 13:09:18 +00:00
|
|
|
}, function (failed) {
|
|
|
|
// If there were errors, re-enable edition
|
|
|
|
self.rte.start_edition(true).then(function () {
|
|
|
|
// jquery's deferred being a pain in the ass
|
|
|
|
if (!_.isArray(failed)) { failed = [failed]; }
|
|
|
|
|
|
|
|
_(failed).each(function (failure) {
|
2013-11-19 14:32:59 +00:00
|
|
|
var html = failure.error.exception_type === "except_osv";
|
|
|
|
if (html) {
|
|
|
|
var msg = $("<div/>").text(failure.error.message).html();
|
|
|
|
var data = msg.substring(3,msg.length-2).split(/', u'/);
|
|
|
|
failure.error.message = '<b>' + data[0] + '</b><br/>' + data[1];
|
|
|
|
}
|
2013-10-23 13:09:18 +00:00
|
|
|
$(root).find('.' + failure.id)
|
|
|
|
.removeClass(failure.id)
|
|
|
|
.popover({
|
2013-11-19 14:32:59 +00:00
|
|
|
html: html,
|
2013-10-23 13:09:18 +00:00
|
|
|
trigger: 'hover',
|
|
|
|
content: failure.error.message,
|
|
|
|
placement: 'auto top',
|
|
|
|
})
|
|
|
|
// Force-show popovers so users will notice them.
|
|
|
|
.popover('show');
|
2013-11-19 14:32:59 +00:00
|
|
|
});
|
2013-10-23 13:09:18 +00:00
|
|
|
});
|
2013-08-29 10:29:56 +00:00
|
|
|
});
|
|
|
|
},
|
2013-09-09 09:44:37 +00:00
|
|
|
/**
|
|
|
|
* Saves an RTE content, which always corresponds to a view section (?).
|
|
|
|
*/
|
2013-09-17 08:57:53 +00:00
|
|
|
saveElement: function ($el) {
|
|
|
|
var markup = $el.prop('outerHTML');
|
2013-09-09 09:51:12 +00:00
|
|
|
return openerp.jsonRpc('/web/dataset/call', 'call', {
|
|
|
|
model: 'ir.ui.view',
|
|
|
|
method: 'save',
|
2013-09-17 08:57:53 +00:00
|
|
|
args: [$el.data('oe-id'), markup,
|
|
|
|
$el.data('oe-xpath') || null,
|
|
|
|
website.get_context()],
|
2013-08-29 10:29:56 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
cancel: function () {
|
2013-12-03 11:13:23 +00:00
|
|
|
new $.Deferred(function (d) {
|
|
|
|
var $dialog = $(openerp.qweb.render('website.editor.discard')).appendTo(document.body);
|
|
|
|
$dialog.on('click', '.btn-danger', function () {
|
|
|
|
d.resolve();
|
|
|
|
}).on('hidden.bs.modal', function () {
|
|
|
|
d.reject();
|
|
|
|
});
|
|
|
|
d.always(function () {
|
|
|
|
$dialog.remove();
|
|
|
|
});
|
|
|
|
$dialog.modal('show');
|
|
|
|
}).then(function () {
|
|
|
|
website.reload();
|
|
|
|
})
|
2013-08-29 10:29:56 +00:00
|
|
|
},
|
2013-12-04 10:52:12 +00:00
|
|
|
|
|
|
|
/**
|
2014-03-27 10:51:03 +00:00
|
|
|
* Creates a "hover" button for link edition
|
2013-12-04 10:52:12 +00:00
|
|
|
*
|
|
|
|
* @param {Function} editfn edition function, called when clicking the button
|
|
|
|
* @returns {jQuery}
|
|
|
|
*/
|
2014-03-27 10:51:03 +00:00
|
|
|
make_hover_button_link: function (editfn) {
|
|
|
|
return $(openerp.qweb.render('website.editor.hoverbutton.link', {}))
|
|
|
|
.hide()
|
|
|
|
.click(function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
editfn.call(this, e);
|
|
|
|
})
|
|
|
|
.appendTo(document.body);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a "hover" button for image
|
|
|
|
*
|
|
|
|
* @param {Function} editfn edition function, called when clicking the button
|
|
|
|
* @param {Function} stylefn edition style function, called when clicking the button
|
|
|
|
* @returns {jQuery}
|
|
|
|
*/
|
|
|
|
make_hover_button_image: function (editfn, stylefn) {
|
2014-03-27 17:14:17 +00:00
|
|
|
var $div = $(openerp.qweb.render('website.editor.hoverbutton.media', {}))
|
2014-03-27 10:51:03 +00:00
|
|
|
.hide()
|
|
|
|
.appendTo(document.body);
|
|
|
|
|
|
|
|
$div.find('[data-toggle="dropdown"]').dropdown();
|
|
|
|
$div.find(".hover-edition-button").click(function (e) {
|
2013-12-04 10:52:12 +00:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
editfn.call(this, e);
|
|
|
|
});
|
2014-03-27 10:51:03 +00:00
|
|
|
if (stylefn) {
|
|
|
|
$div.find(".hover-style-button").click(function (e) {
|
2014-03-26 14:16:29 +00:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2014-03-27 10:51:03 +00:00
|
|
|
stylefn.call(this, e);
|
2014-03-26 14:16:29 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return $div;
|
2013-12-04 10:52:12 +00:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* For UI clarity, during RTE edition when the user hovers links and
|
|
|
|
* images a small button should appear to make the capability clear,
|
|
|
|
* as not all users think of double-clicking the image or link.
|
|
|
|
*/
|
|
|
|
setup_hover_buttons: function () {
|
|
|
|
var editor = this.rte.editor;
|
2014-03-27 10:51:03 +00:00
|
|
|
var $link_button = this.make_hover_button_link(function () {
|
2013-12-04 10:52:12 +00:00
|
|
|
var sel = new CKEDITOR.dom.element(previous);
|
|
|
|
editor.getSelection().selectElement(sel);
|
2014-03-11 13:08:52 +00:00
|
|
|
if(sel.hasClass('fa')) {
|
2014-03-27 16:03:24 +00:00
|
|
|
new website.editor.MediaDialog(editor, previous)
|
2014-01-21 15:08:43 +00:00
|
|
|
.appendTo(document.body);
|
2014-03-11 13:08:52 +00:00
|
|
|
} else if (previous.tagName.toUpperCase() === 'A') {
|
|
|
|
link_dialog(editor);
|
2014-01-21 15:08:43 +00:00
|
|
|
}
|
2013-12-04 10:52:12 +00:00
|
|
|
$link_button.hide();
|
|
|
|
previous = null;
|
2014-03-27 10:51:03 +00:00
|
|
|
});
|
2014-03-26 14:16:29 +00:00
|
|
|
|
2014-03-12 14:19:10 +00:00
|
|
|
function is_icons_widget(element) {
|
|
|
|
var w = editor.widgets.getByElement(element);
|
|
|
|
return w && w.name === 'icons';
|
|
|
|
}
|
|
|
|
|
2013-12-04 10:52:12 +00:00
|
|
|
// 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
|
|
|
|
// still set to the element having triggered showing the button.
|
|
|
|
var previous;
|
2014-03-28 14:16:19 +00:00
|
|
|
$(editor.element.$).on('mouseover', 'a', function () {
|
2013-12-04 10:52:12 +00:00
|
|
|
// Back from edit button -> ignore
|
|
|
|
if (previous && previous === this) { return; }
|
|
|
|
|
2014-03-12 14:19:10 +00:00
|
|
|
// 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
|
2013-12-04 10:52:12 +00:00
|
|
|
var selected = new CKEDITOR.dom.element(this);
|
2014-03-12 14:19:10 +00:00
|
|
|
if (!(is_editable_node(selected) || is_icons_widget(selected))) {
|
2013-12-04 10:52:12 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
previous = this;
|
|
|
|
var $selected = $(this);
|
|
|
|
var position = $selected.offset();
|
2014-03-28 14:16:19 +00:00
|
|
|
$link_button.show().offset({
|
|
|
|
top: $selected.outerHeight()
|
|
|
|
+ position.top,
|
|
|
|
left: $selected.outerWidth() / 2
|
|
|
|
+ position.left
|
|
|
|
- $link_button.outerWidth() / 2
|
|
|
|
})
|
2014-01-21 15:08:43 +00:00
|
|
|
}).on('mouseleave', 'a, img, .fa', function (e) {
|
2013-12-04 10:52:12 +00:00
|
|
|
var current = document.elementFromPoint(e.clientX, e.clientY);
|
2014-03-28 14:16:19 +00:00
|
|
|
if (current === $link_button[0] || $(current).parent()[0] === $link_button[0]) {
|
2013-12-04 10:52:12 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-04-07 10:24:19 +00:00
|
|
|
$link_button.hide();
|
2013-12-04 10:52:12 +00:00
|
|
|
previous = null;
|
|
|
|
});
|
|
|
|
}
|
2013-08-29 10:29:56 +00:00
|
|
|
});
|
|
|
|
|
2013-10-17 13:31:33 +00:00
|
|
|
var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(',');
|
2013-08-29 10:29:56 +00:00
|
|
|
/* ----- RICH TEXT EDITOR ---- */
|
|
|
|
website.RTE = openerp.Widget.extend({
|
|
|
|
tagName: 'li',
|
|
|
|
id: 'oe_rte_toolbar',
|
|
|
|
className: 'oe_right oe_rte_toolbar',
|
|
|
|
// editor.ui.items -> possible commands &al
|
|
|
|
// editor.applyStyle(new CKEDITOR.style({element: "span",styles: {color: "#(color)"},overrides: [{element: "font",attributes: {color: null}}]}, {color: '#ff0000'}));
|
|
|
|
|
2013-09-06 15:04:08 +00:00
|
|
|
init: function (EditorBar) {
|
|
|
|
this.EditorBar = EditorBar;
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
},
|
|
|
|
|
2013-10-17 13:31:33 +00:00
|
|
|
/**
|
|
|
|
* In Webkit-based browsers, triple-click will select a paragraph up to
|
|
|
|
* the start of the next "paragraph" including any empty space
|
|
|
|
* inbetween. When said paragraph is removed or altered, it nukes
|
|
|
|
* the empty space and brings part of the content of the next
|
|
|
|
* "paragraph" (which may well be e.g. an image) into the current one,
|
|
|
|
* completely fucking up layouts and breaking snippets.
|
|
|
|
*
|
|
|
|
* Try to fuck around with selections on triple-click to attempt to
|
|
|
|
* fix this garbage behavior.
|
|
|
|
*
|
|
|
|
* Note: for consistent behavior we may actually want to take over
|
|
|
|
* triple-clicks, in all browsers in order to ensure consistent cross-
|
|
|
|
* platform behavior instead of being at the mercy of rendering engines
|
|
|
|
* & platform selection quirks?
|
|
|
|
*/
|
|
|
|
webkitSelectionFixer: function (root) {
|
|
|
|
root.addEventListener('click', function (e) {
|
|
|
|
// only webkit seems to have a fucked up behavior, ignore others
|
|
|
|
// FIXME: $.browser goes away in jquery 1.9...
|
|
|
|
if (!$.browser.webkit) { return; }
|
|
|
|
// http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mouseevents
|
|
|
|
// The detail attribute indicates the number of times a mouse button has been pressed
|
|
|
|
// we just want the triple click
|
|
|
|
if (e.detail !== 3) { return; }
|
2013-10-22 11:00:15 +00:00
|
|
|
e.preventDefault();
|
2013-10-17 13:31:33 +00:00
|
|
|
|
|
|
|
// Get closest block-level element to the triple-clicked
|
|
|
|
// element (using ckeditor's block list because why not)
|
|
|
|
var $closest_block = $(e.target).closest(blocks_selector);
|
|
|
|
|
|
|
|
// manually set selection range to the content of the
|
|
|
|
// triple-clicked block-level element, to avoid crossing over
|
|
|
|
// between block-level elements
|
|
|
|
document.getSelection().selectAllChildren($closest_block[0]);
|
|
|
|
});
|
|
|
|
},
|
2013-10-18 10:35:40 +00:00
|
|
|
tableNavigation: function (root) {
|
|
|
|
var self = this;
|
|
|
|
$(root).on('keydown', function (e) {
|
|
|
|
// ignore non-TAB
|
|
|
|
if (e.which !== 9) { return; }
|
|
|
|
|
|
|
|
if (self.handleTab(e)) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Performs whatever operation is necessary on a [TAB] hit, returns
|
|
|
|
* ``true`` if the event's default should be cancelled (if the TAB was
|
|
|
|
* handled by the function)
|
|
|
|
*/
|
|
|
|
handleTab: function (event) {
|
|
|
|
var forward = !event.shiftKey;
|
|
|
|
|
|
|
|
var root = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
|
|
|
var $cell = $(root).closest('td,th');
|
|
|
|
|
|
|
|
if (!$cell.length) { return false; }
|
|
|
|
|
|
|
|
var cell = $cell[0];
|
|
|
|
|
|
|
|
// find cell in same row
|
|
|
|
var row = cell.parentNode;
|
|
|
|
var sibling = row.cells[cell.cellIndex + (forward ? 1 : -1)];
|
|
|
|
if (sibling) {
|
|
|
|
document.getSelection().selectAllChildren(sibling);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find cell in previous/next row
|
|
|
|
var table = row.parentNode;
|
|
|
|
var sibling_row = table.rows[row.rowIndex + (forward ? 1 : -1)];
|
|
|
|
if (sibling_row) {
|
|
|
|
var new_cell = sibling_row.cells[forward ? 0 : sibling_row.cells.length - 1];
|
|
|
|
document.getSelection().selectAllChildren(new_cell);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// at edge cells, copy word/openoffice behavior: if going backwards
|
|
|
|
// from first cell do nothing, if going forwards from last cell add
|
|
|
|
// a row
|
|
|
|
if (forward) {
|
|
|
|
var row_size = row.cells.length;
|
|
|
|
var new_row = document.createElement('tr');
|
|
|
|
while(row_size--) {
|
|
|
|
var newcell = document.createElement('td');
|
|
|
|
// zero-width space
|
|
|
|
newcell.textContent = '\u200B';
|
|
|
|
new_row.appendChild(newcell);
|
|
|
|
}
|
|
|
|
table.appendChild(new_row);
|
|
|
|
document.getSelection().selectAllChildren(new_row.cells[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
2013-10-23 13:09:18 +00:00
|
|
|
/**
|
|
|
|
* Makes the page editable
|
|
|
|
*
|
|
|
|
* @param {Boolean} [restart=false] in case the edition was already set
|
|
|
|
* up once and is being re-enabled.
|
|
|
|
* @returns {$.Deferred} deferred indicating when the RTE is ready
|
|
|
|
*/
|
|
|
|
start_edition: function (restart) {
|
2013-08-29 10:29:56 +00:00
|
|
|
var self = this;
|
2013-09-17 08:57:53 +00:00
|
|
|
// create a single editor for the whole page
|
2013-09-18 14:50:28 +00:00
|
|
|
var root = document.getElementById('wrapwrap');
|
2013-10-23 13:09:18 +00:00
|
|
|
if (!restart) {
|
|
|
|
$(root).on('dragstart', 'img', function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
this.webkitSelectionFixer(root);
|
|
|
|
this.tableNavigation(root);
|
|
|
|
}
|
|
|
|
var def = $.Deferred();
|
2013-09-24 11:06:01 +00:00
|
|
|
var editor = this.editor = CKEDITOR.inline(root, self._config());
|
|
|
|
editor.on('instanceReady', function () {
|
|
|
|
editor.setReadOnly(false);
|
2013-09-17 08:57:53 +00:00
|
|
|
// ckeditor set root to editable, disable it (only inner
|
|
|
|
// sections are editable)
|
|
|
|
// FIXME: are there cases where the whole editor is editable?
|
2013-09-24 11:06:01 +00:00
|
|
|
editor.editable().setReadOnly(true);
|
2013-09-17 08:57:53 +00:00
|
|
|
|
|
|
|
self.setup_editables(root);
|
2013-09-23 13:03:21 +00:00
|
|
|
|
2013-11-19 13:57:04 +00:00
|
|
|
try {
|
|
|
|
// disable firefox's broken table resizing thing
|
|
|
|
document.execCommand("enableObjectResizing", false, "false");
|
|
|
|
document.execCommand("enableInlineTableEditing", false, "false");
|
|
|
|
} catch (e) {}
|
2013-10-18 10:35:40 +00:00
|
|
|
|
2014-01-21 13:18:41 +00:00
|
|
|
// 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:
|
|
|
|
// https://dev.ckeditor.com/ticket/11472
|
|
|
|
$(document.body)
|
|
|
|
.off('snippet-dropped')
|
|
|
|
.on('snippet-dropped', function (e, el) {
|
|
|
|
// CKEDITOR data processor extended by widgets plugin
|
|
|
|
// to add wrappers around upcasting elements
|
2014-01-22 13:21:59 +00:00
|
|
|
el.innerHTML = editor.dataProcessor.toHtml(el.innerHTML, {
|
2014-01-21 13:18:41 +00:00
|
|
|
fixForBody: false,
|
|
|
|
dontFilter: true,
|
|
|
|
});
|
|
|
|
// then repository.initOnAll() handles the conversion
|
|
|
|
// from wrapper to actual widget instance (or something
|
|
|
|
// like that).
|
|
|
|
setTimeout(function () {
|
|
|
|
editor.widgets.initOnAll();
|
|
|
|
}, 0);
|
|
|
|
});
|
|
|
|
|
2013-09-23 13:03:21 +00:00
|
|
|
self.trigger('rte:ready');
|
2013-10-23 13:09:18 +00:00
|
|
|
def.resolve();
|
2013-09-17 08:57:53 +00:00
|
|
|
});
|
2013-10-23 13:09:18 +00:00
|
|
|
return def;
|
2013-09-17 08:57:53 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
setup_editables: function (root) {
|
|
|
|
// selection of editable sub-items was previously in
|
|
|
|
// EditorBar#edit, but for some unknown reason the elements were
|
|
|
|
// apparently removed and recreated (?) at editor initalization,
|
|
|
|
// and observer setup was lost.
|
|
|
|
var self = this;
|
|
|
|
// setup dirty-marking for each editable element
|
|
|
|
this.fetch_editables(root)
|
|
|
|
.addClass('oe_editable')
|
2013-08-29 10:29:56 +00:00
|
|
|
.each(function () {
|
|
|
|
var node = this;
|
|
|
|
var $node = $(node);
|
2013-10-11 13:56:16 +00:00
|
|
|
// only explicitly set contenteditable on view sections,
|
|
|
|
// cke widgets system will do the widgets themselves
|
|
|
|
if ($node.data('oe-model') === 'ir.ui.view') {
|
|
|
|
node.contentEditable = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
observer.observe(node, OBSERVER_CONFIG);
|
2013-08-29 10:29:56 +00:00
|
|
|
$node.one('content_changed', function () {
|
|
|
|
$node.addClass('oe_dirty');
|
|
|
|
self.trigger('change');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-09-17 08:57:53 +00:00
|
|
|
fetch_editables: function (root) {
|
|
|
|
return $(root).find('[data-oe-model]')
|
|
|
|
.not('link, script')
|
|
|
|
.not('.oe_snippet_editor')
|
|
|
|
.filter(function () {
|
|
|
|
var $this = $(this);
|
|
|
|
// keep view sections and fields which are *not* in
|
2013-10-22 15:11:44 +00:00
|
|
|
// view sections for top-level editables
|
2013-09-17 08:57:53 +00:00
|
|
|
return $this.data('oe-model') === 'ir.ui.view'
|
|
|
|
|| !$this.closest('[data-oe-model = "ir.ui.view"]').length;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-08-29 10:29:56 +00:00
|
|
|
_current_editor: function () {
|
|
|
|
return CKEDITOR.currentInstance;
|
|
|
|
},
|
|
|
|
_config: function () {
|
2013-09-10 06:57:56 +00:00
|
|
|
// base plugins minus
|
|
|
|
// - magicline (captures mousein/mouseout -> breaks draggable)
|
|
|
|
// - contextmenu & tabletools (disable contextual menu)
|
|
|
|
// - bunch of unused plugins
|
|
|
|
var plugins = [
|
2013-10-16 13:34:32 +00:00
|
|
|
'a11yhelp', 'basicstyles', 'blockquote',
|
2013-09-10 06:57:56 +00:00
|
|
|
'clipboard', 'colorbutton', 'colordialog', 'dialogadvtab',
|
2013-12-17 15:37:51 +00:00
|
|
|
'elementspath', /*'enterkey',*/ 'entities', 'filebrowser',
|
2013-09-10 06:57:56 +00:00
|
|
|
'find', 'floatingspace','format', 'htmlwriter', 'iframe',
|
2013-09-16 09:03:53 +00:00
|
|
|
'indentblock', 'indentlist', 'justify',
|
2013-09-10 06:57:56 +00:00
|
|
|
'list', 'pastefromword', 'pastetext', 'preview',
|
|
|
|
'removeformat', 'resize', 'save', 'selectall', 'stylescombo',
|
2013-10-18 10:35:40 +00:00
|
|
|
'table', 'templates', 'toolbar', 'undo', 'wysiwygarea'
|
2013-08-29 10:29:56 +00:00
|
|
|
];
|
|
|
|
return {
|
2013-09-12 15:49:57 +00:00
|
|
|
// FIXME
|
|
|
|
language: 'en',
|
2013-08-29 10:29:56 +00:00
|
|
|
// Disable auto-generated titles
|
|
|
|
// FIXME: accessibility, need to generate user-sensible title, used for @title and @aria-label
|
|
|
|
title: false,
|
2013-09-10 06:57:56 +00:00
|
|
|
plugins: plugins.join(','),
|
2013-08-29 10:29:56 +00:00
|
|
|
uiColor: '',
|
2013-09-09 10:42:38 +00:00
|
|
|
// FIXME: currently breaks RTE?
|
2013-09-10 06:57:56 +00:00
|
|
|
// Ensure no config file is loaded
|
|
|
|
customConfig: '',
|
2013-08-29 10:29:56 +00:00
|
|
|
// Disable ACF
|
|
|
|
allowedContent: true,
|
|
|
|
// Don't insert paragraphs around content in e.g. <li>
|
|
|
|
autoParagraph: false,
|
2013-09-23 13:49:14 +00:00
|
|
|
// Don't automatically add or <br> in empty block-level
|
|
|
|
// elements when edition starts
|
|
|
|
fillEmptyBlocks: false,
|
2013-08-29 10:29:56 +00:00
|
|
|
filebrowserImageUploadUrl: "/website/attach",
|
|
|
|
// Support for sharedSpaces in 4.x
|
2013-11-22 15:11:24 +00:00
|
|
|
extraPlugins: 'sharedspace,customdialogs,tablebutton,oeref,linkstyle',
|
2013-08-29 10:29:56 +00:00
|
|
|
// Place toolbar in controlled location
|
|
|
|
sharedSpaces: { top: 'oe_rte_toolbar' },
|
2013-09-11 11:59:37 +00:00
|
|
|
toolbar: [{
|
|
|
|
name: 'basicstyles', items: [
|
2013-08-29 10:29:56 +00:00
|
|
|
"Bold", "Italic", "Underline", "Strike", "Subscript",
|
|
|
|
"Superscript", "TextColor", "BGColor", "RemoveFormat"
|
|
|
|
]},{
|
|
|
|
name: 'span', items: [
|
2013-11-22 15:11:24 +00:00
|
|
|
"Link", "LinkStyle", "Blockquote", "BulletedList",
|
2013-08-29 10:29:56 +00:00
|
|
|
"NumberedList", "Indent", "Outdent"
|
|
|
|
]},{
|
|
|
|
name: 'justify', items: [
|
|
|
|
"JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock"
|
|
|
|
]},{
|
|
|
|
name: 'special', items: [
|
2013-09-06 08:50:45 +00:00
|
|
|
"Image", "TableButton"
|
2013-08-29 10:29:56 +00:00
|
|
|
]},{
|
|
|
|
name: 'styles', items: [
|
2013-11-21 15:45:01 +00:00
|
|
|
"Styles"
|
2013-08-29 10:29:56 +00:00
|
|
|
]}
|
|
|
|
],
|
|
|
|
// styles dropdown in toolbar
|
|
|
|
stylesSet: [
|
2013-09-04 13:17:45 +00:00
|
|
|
{name: "Normal", element: 'p'},
|
|
|
|
{name: "Heading 1", element: 'h1'},
|
|
|
|
{name: "Heading 2", element: 'h2'},
|
|
|
|
{name: "Heading 3", element: 'h3'},
|
|
|
|
{name: "Heading 4", element: 'h4'},
|
|
|
|
{name: "Heading 5", element: 'h5'},
|
|
|
|
{name: "Heading 6", element: 'h6'},
|
|
|
|
{name: "Formatted", element: 'pre'},
|
2013-10-28 15:23:42 +00:00
|
|
|
{name: "Address", element: 'address'}
|
2013-08-29 10:29:56 +00:00
|
|
|
],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2013-08-29 15:34:41 +00:00
|
|
|
website.editor = { };
|
|
|
|
website.editor.Dialog = openerp.Widget.extend({
|
|
|
|
events: {
|
2013-08-30 09:32:11 +00:00
|
|
|
'hidden.bs.modal': 'destroy',
|
2013-09-02 07:52:16 +00:00
|
|
|
'click button.save': 'save',
|
2013-11-20 11:52:50 +00:00
|
|
|
'click button[data-dismiss="modal"]': 'cancel',
|
2013-08-29 15:34:41 +00:00
|
|
|
},
|
|
|
|
init: function (editor) {
|
|
|
|
this._super();
|
|
|
|
this.editor = editor;
|
|
|
|
},
|
|
|
|
start: function () {
|
|
|
|
var sup = this._super();
|
2013-10-14 10:09:44 +00:00
|
|
|
this.$el.modal({backdrop: 'static'});
|
2014-02-19 09:24:02 +00:00
|
|
|
this.$('input:first').focus();
|
2013-08-29 15:34:41 +00:00
|
|
|
return sup;
|
|
|
|
},
|
2013-09-02 07:52:16 +00:00
|
|
|
save: function () {
|
2013-09-13 09:55:32 +00:00
|
|
|
this.close();
|
2014-04-07 10:24:19 +00:00
|
|
|
this.trigger("saved");
|
2013-09-13 09:55:32 +00:00
|
|
|
},
|
2013-11-20 11:52:50 +00:00
|
|
|
cancel: function () {
|
2014-05-05 15:07:22 +00:00
|
|
|
this.trigger("cancel");
|
2013-11-20 11:52:50 +00:00
|
|
|
},
|
2013-09-13 09:55:32 +00:00
|
|
|
close: function () {
|
2013-09-02 07:52:16 +00:00
|
|
|
this.$el.modal('hide');
|
|
|
|
},
|
2013-08-29 15:34:41 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
website.editor.LinkDialog = website.editor.Dialog.extend({
|
|
|
|
template: 'website.editor.dialog.link',
|
|
|
|
events: _.extend({}, website.editor.Dialog.prototype.events, {
|
2013-11-13 14:39:34 +00:00
|
|
|
'change :input.url-source': function (e) { this.changed($(e.target)); },
|
2013-09-13 09:15:32 +00:00
|
|
|
'mousedown': function (e) {
|
|
|
|
var $target = $(e.target).closest('.list-group-item');
|
|
|
|
if (!$target.length || $target.hasClass('active')) {
|
|
|
|
// clicked outside groups, or clicked in active groups
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-13 14:39:34 +00:00
|
|
|
this.changed($target.find('.url-source').filter(':input'));
|
2013-09-13 09:55:32 +00:00
|
|
|
},
|
|
|
|
'click button.remove': 'remove_link',
|
2013-09-24 13:51:20 +00:00
|
|
|
'change input#link-text': function (e) {
|
|
|
|
this.text = $(e.target).val()
|
|
|
|
},
|
2013-08-29 15:34:41 +00:00
|
|
|
}),
|
|
|
|
init: function (editor) {
|
|
|
|
this._super(editor);
|
2013-09-24 13:51:20 +00:00
|
|
|
this.text = null;
|
2013-11-13 15:18:32 +00:00
|
|
|
// Store last-performed request to be able to cancel/abort it.
|
2013-11-26 16:01:48 +00:00
|
|
|
this.page_exists_req = null;
|
|
|
|
this.search_pages_req = null;
|
2013-08-29 15:34:41 +00:00
|
|
|
},
|
|
|
|
start: function () {
|
2013-10-31 16:51:17 +00:00
|
|
|
var self = this;
|
2014-02-19 10:55:02 +00:00
|
|
|
var last;
|
2013-11-13 14:39:34 +00:00
|
|
|
this.$('#link-page').select2({
|
2013-11-25 10:22:28 +00:00
|
|
|
minimumInputLength: 1,
|
2013-11-13 14:39:34 +00:00
|
|
|
placeholder: _t("New or existing page"),
|
|
|
|
query: function (q) {
|
2014-02-19 10:55:02 +00:00
|
|
|
if (q.term == last) return;
|
|
|
|
last = q.term;
|
2013-11-26 16:01:48 +00:00
|
|
|
$.when(
|
|
|
|
self.page_exists(q.term),
|
|
|
|
self.fetch_pages(q.term)
|
|
|
|
).then(function (exists, results) {
|
2013-11-13 14:39:34 +00:00
|
|
|
var rs = _.map(results, function (r) {
|
|
|
|
return { id: r.url, text: r.name, };
|
|
|
|
});
|
2013-11-26 16:01:48 +00:00
|
|
|
if (!exists) {
|
|
|
|
rs.push({
|
|
|
|
create: true,
|
|
|
|
id: q.term,
|
|
|
|
text: _.str.sprintf(_t("Create page '%s'"), q.term),
|
|
|
|
});
|
|
|
|
}
|
2013-11-13 14:39:34 +00:00
|
|
|
q.callback({
|
|
|
|
more: false,
|
|
|
|
results: rs
|
|
|
|
});
|
2013-11-25 10:18:19 +00:00
|
|
|
}, function () {
|
|
|
|
q.callback({more: false, results: []});
|
2013-11-13 14:39:34 +00:00
|
|
|
});
|
|
|
|
},
|
2013-10-31 16:51:17 +00:00
|
|
|
});
|
2013-11-13 14:39:34 +00:00
|
|
|
return this._super().then(this.proxy('bind_data'));
|
2013-08-29 15:34:41 +00:00
|
|
|
},
|
|
|
|
save: function () {
|
2013-09-03 14:32:37 +00:00
|
|
|
var self = this, _super = this._super.bind(this);
|
2013-11-13 14:39:34 +00:00
|
|
|
var $e = this.$('.list-group-item.active .url-source').filter(':input');
|
2013-09-13 09:15:32 +00:00
|
|
|
var val = $e.val();
|
2013-09-23 09:49:26 +00:00
|
|
|
if (!val || !$e[0].checkValidity()) {
|
|
|
|
// FIXME: error message
|
2013-09-13 09:15:32 +00:00
|
|
|
$e.closest('.form-group').addClass('has-error');
|
2013-11-04 11:34:12 +00:00
|
|
|
$e.focus();
|
2013-09-13 09:15:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-08-29 15:34:41 +00:00
|
|
|
|
2013-09-13 09:15:32 +00:00
|
|
|
var done = $.when();
|
2013-09-13 08:40:13 +00:00
|
|
|
if ($e.hasClass('email-address')) {
|
2013-08-29 15:34:41 +00:00
|
|
|
this.make_link('mailto:' + val, false, val);
|
2013-11-13 14:39:34 +00:00
|
|
|
} else if ($e.hasClass('page')) {
|
|
|
|
var data = $e.select2('data');
|
|
|
|
if (!data.create) {
|
|
|
|
self.make_link(data.id, false, data.text);
|
|
|
|
} else {
|
|
|
|
// Create the page, get the URL back
|
|
|
|
done = $.get(_.str.sprintf(
|
2014-02-07 14:51:49 +00:00
|
|
|
'/website/add/%s?noredirect=1', encodeURI(data.id)))
|
2013-11-13 14:39:34 +00:00
|
|
|
.then(function (response) {
|
|
|
|
self.make_link(response, false, data.id);
|
|
|
|
});
|
|
|
|
}
|
2013-08-29 15:34:41 +00:00
|
|
|
} else {
|
|
|
|
this.make_link(val, this.$('input.window-new').prop('checked'));
|
|
|
|
}
|
2013-09-03 14:32:37 +00:00
|
|
|
done.then(_super);
|
2013-08-29 15:34:41 +00:00
|
|
|
},
|
2013-10-28 16:26:23 +00:00
|
|
|
make_link: function (url, new_window, label) {
|
|
|
|
},
|
2013-10-31 16:51:17 +00:00
|
|
|
bind_data: function (text, href, new_window) {
|
|
|
|
href = href || this.element && (this.element.data( 'cke-saved-href')
|
2013-08-29 15:34:41 +00:00
|
|
|
|| this.element.getAttribute('href'));
|
|
|
|
|
2013-10-31 16:51:17 +00:00
|
|
|
if (new_window === undefined) {
|
2014-01-21 15:13:10 +00:00
|
|
|
new_window = this.element
|
|
|
|
? this.element.getAttribute('target') === '_blank'
|
|
|
|
: false;
|
2013-10-31 16:51:17 +00:00
|
|
|
}
|
|
|
|
if (text === undefined) {
|
2014-01-21 15:13:10 +00:00
|
|
|
text = this.element ? this.element.getText() : '';
|
2013-10-31 16:51:17 +00:00
|
|
|
}
|
|
|
|
|
2014-01-21 15:13:10 +00:00
|
|
|
this.$('input#link-text').val(text);
|
|
|
|
this.$('input.window-new').prop('checked', new_window);
|
|
|
|
|
|
|
|
if (!href) { return; }
|
2013-08-29 15:34:41 +00:00
|
|
|
var match, $control;
|
2013-10-31 16:51:17 +00:00
|
|
|
if ((match = /mailto:(.+)/.exec(href))) {
|
2013-09-23 11:06:18 +00:00
|
|
|
$control = this.$('input.email-address').val(match[1]);
|
2013-09-13 08:40:13 +00:00
|
|
|
}
|
|
|
|
if (!$control) {
|
|
|
|
$control = this.$('input.url').val(href);
|
2013-08-29 15:34:41 +00:00
|
|
|
}
|
|
|
|
|
2013-09-13 08:40:13 +00:00
|
|
|
this.changed($control);
|
2013-08-29 15:34:41 +00:00
|
|
|
},
|
2013-09-13 08:40:13 +00:00
|
|
|
changed: function ($e) {
|
2013-11-13 14:39:34 +00:00
|
|
|
this.$('.url-source').filter(':input').not($e).val('')
|
|
|
|
.filter(function () { return !!$(this).data('select2'); })
|
|
|
|
.select2('data', null);
|
2013-09-23 10:02:41 +00:00
|
|
|
$e.closest('.list-group-item')
|
|
|
|
.addClass('active')
|
|
|
|
.siblings().removeClass('active')
|
|
|
|
.addBack().removeClass('has-error');
|
2013-09-13 08:40:13 +00:00
|
|
|
},
|
2013-11-26 16:01:48 +00:00
|
|
|
call: function (method, args, kwargs) {
|
2013-11-13 15:18:32 +00:00
|
|
|
var self = this;
|
2013-11-26 16:01:48 +00:00
|
|
|
var req = method + '_req';
|
|
|
|
|
|
|
|
if (this[req]) { this[req].abort(); }
|
|
|
|
|
|
|
|
return this[req] = openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
2013-08-29 15:34:41 +00:00
|
|
|
model: 'website',
|
2013-11-26 16:01:48 +00:00
|
|
|
method: method,
|
|
|
|
args: args,
|
|
|
|
kwargs: kwargs,
|
2013-11-25 10:18:19 +00:00
|
|
|
}).always(function () {
|
2013-11-26 16:01:48 +00:00
|
|
|
self[req] = null;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
page_exists: function (term) {
|
|
|
|
return this.call('page_exists', [null, term], {
|
|
|
|
context: website.get_context(),
|
|
|
|
});
|
|
|
|
},
|
|
|
|
fetch_pages: function (term) {
|
|
|
|
return this.call('search_pages', [null, term], {
|
|
|
|
limit: 9,
|
|
|
|
context: website.get_context(),
|
2013-08-29 15:34:41 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
2013-10-28 16:26:23 +00:00
|
|
|
website.editor.RTELinkDialog = website.editor.LinkDialog.extend({
|
|
|
|
start: function () {
|
|
|
|
var element;
|
|
|
|
if ((element = this.get_selected_link()) && element.hasAttribute('href')) {
|
|
|
|
this.editor.getSelection().selectElement(element);
|
|
|
|
}
|
|
|
|
this.element = element;
|
|
|
|
if (element) {
|
|
|
|
this.add_removal_button();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._super();
|
|
|
|
},
|
|
|
|
add_removal_button: function () {
|
|
|
|
this.$('.modal-footer').prepend(
|
|
|
|
openerp.qweb.render(
|
|
|
|
'website.editor.dialog.link.footer-button'));
|
|
|
|
},
|
|
|
|
remove_link: function () {
|
|
|
|
var editor = this.editor;
|
|
|
|
// same issue as in make_link
|
|
|
|
setTimeout(function () {
|
|
|
|
editor.removeStyle(new CKEDITOR.style({
|
|
|
|
element: 'a',
|
|
|
|
type: CKEDITOR.STYLE_INLINE,
|
|
|
|
alwaysRemoveElement: true,
|
|
|
|
}));
|
|
|
|
}, 0);
|
|
|
|
this.close();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Greatly simplified version of CKEDITOR's
|
|
|
|
* plugins.link.dialogs.link.onOk.
|
|
|
|
*
|
|
|
|
* @param {String} url
|
|
|
|
* @param {Boolean} [new_window=false]
|
|
|
|
* @param {String} [label=null]
|
|
|
|
*/
|
|
|
|
make_link: function (url, new_window, label) {
|
|
|
|
var attributes = {href: url, 'data-cke-saved-href': url};
|
|
|
|
var to_remove = [];
|
|
|
|
if (new_window) {
|
|
|
|
attributes['target'] = '_blank';
|
|
|
|
} else {
|
|
|
|
to_remove.push('target');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.element) {
|
|
|
|
this.element.setAttributes(attributes);
|
|
|
|
this.element.removeAttributes(to_remove);
|
|
|
|
if (this.text) { this.element.setText(this.text); }
|
|
|
|
} else {
|
|
|
|
var selection = this.editor.getSelection();
|
|
|
|
var range = selection.getRanges(true)[0];
|
|
|
|
|
|
|
|
if (range.collapsed) {
|
|
|
|
//noinspection JSPotentiallyInvalidConstructorUsage
|
|
|
|
var text = new CKEDITOR.dom.text(
|
|
|
|
this.text || label || url);
|
|
|
|
range.insertNode(text);
|
|
|
|
range.selectNodeContents(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
//noinspection JSPotentiallyInvalidConstructorUsage
|
|
|
|
new CKEDITOR.style({
|
|
|
|
type: CKEDITOR.STYLE_INLINE,
|
|
|
|
element: 'a',
|
|
|
|
attributes: attributes,
|
|
|
|
}).applyToRange(range);
|
|
|
|
|
|
|
|
// focus dance between RTE & dialog blow up the stack in Safari
|
|
|
|
// and Chrome, so defer select() until dialog has been closed
|
|
|
|
setTimeout(function () {
|
|
|
|
range.select();
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* CKEDITOR.plugins.link.getSelectedLink ignores the editor's root,
|
|
|
|
* if the editor is set directly on a link it will thus not work.
|
|
|
|
*/
|
|
|
|
get_selected_link: function () {
|
|
|
|
return get_selected_link(this.editor);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2014-03-26 15:58:22 +00:00
|
|
|
website.editor.Media = openerp.Widget.extend({
|
2014-03-27 17:14:17 +00:00
|
|
|
init: function (parent, editor, media) {
|
2014-03-26 15:58:22 +00:00
|
|
|
this._super();
|
2014-03-27 17:14:17 +00:00
|
|
|
this.parent = parent;
|
2014-03-26 15:58:22 +00:00
|
|
|
this.editor = editor;
|
2014-03-27 10:51:03 +00:00
|
|
|
this.media = media;
|
2014-03-26 15:58:22 +00:00
|
|
|
},
|
2014-03-27 16:03:24 +00:00
|
|
|
start: function () {
|
|
|
|
this.$preview = this.$('.preview-container').detach();
|
|
|
|
return this._super();
|
|
|
|
},
|
2014-03-27 17:14:17 +00:00
|
|
|
search: function (needle) {
|
|
|
|
},
|
2014-03-26 16:07:01 +00:00
|
|
|
save: function () {
|
|
|
|
},
|
2014-03-28 15:47:15 +00:00
|
|
|
clear: function () {
|
|
|
|
},
|
2014-03-26 16:07:01 +00:00
|
|
|
cancel: function () {
|
|
|
|
},
|
|
|
|
close: function () {
|
|
|
|
},
|
2014-03-26 15:58:22 +00:00
|
|
|
});
|
|
|
|
website.editor.MediaDialog = website.editor.Dialog.extend({
|
|
|
|
template: 'website.editor.dialog.media',
|
2014-03-27 17:14:17 +00:00
|
|
|
events : _.extend({}, website.editor.Dialog.prototype.events, {
|
|
|
|
'input input#icon-search': 'search',
|
|
|
|
}),
|
2014-03-26 16:07:01 +00:00
|
|
|
|
2014-03-26 15:58:22 +00:00
|
|
|
init: function (editor, media) {
|
2014-03-28 10:44:53 +00:00
|
|
|
this._super(editor);
|
2014-03-26 15:58:22 +00:00
|
|
|
this.editor = editor;
|
2014-03-27 16:03:24 +00:00
|
|
|
this.page = 0;
|
2014-03-28 10:44:53 +00:00
|
|
|
this.media = media;
|
2014-03-26 15:58:22 +00:00
|
|
|
},
|
2014-03-28 10:44:53 +00:00
|
|
|
start: function () {
|
2014-03-27 10:51:03 +00:00
|
|
|
var self = this;
|
2014-04-07 14:49:14 +00:00
|
|
|
|
2014-03-27 17:14:17 +00:00
|
|
|
this.imageDialog = new website.editor.RTEImageDialog(this, this.editor, this.media);
|
2014-03-26 15:58:22 +00:00
|
|
|
this.imageDialog.appendTo(this.$("#editor-media-image"));
|
2014-03-27 17:14:17 +00:00
|
|
|
this.iconDialog = new website.editor.FontIconsDialog(this, this.editor, this.media);
|
2014-03-26 15:58:22 +00:00
|
|
|
this.iconDialog.appendTo(this.$("#editor-media-icon"));
|
2014-03-28 10:44:53 +00:00
|
|
|
this.videoDialog = new website.editor.VideoDialog(this, this.editor, this.media);
|
|
|
|
this.videoDialog.appendTo(this.$("#editor-media-video"));
|
2014-03-27 10:51:03 +00:00
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
this.active = this.imageDialog;
|
2014-03-27 10:51:03 +00:00
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
|
|
|
|
if ($(event.target).is('[href="#editor-media-image"]')) {
|
|
|
|
self.active = self.imageDialog;
|
2014-04-07 08:17:51 +00:00
|
|
|
self.$('li.search, li.previous, li.next').removeClass("hidden");
|
2014-03-27 16:03:24 +00:00
|
|
|
} else if ($(event.target).is('[href="#editor-media-icon"]')) {
|
|
|
|
self.active = self.iconDialog;
|
2014-04-07 08:17:51 +00:00
|
|
|
self.$('li.search, li.previous, li.next').removeClass("hidden");
|
|
|
|
self.$('.nav-tabs li.previous, .nav-tabs li.next').addClass("hidden");
|
2014-03-27 16:03:24 +00:00
|
|
|
} else if ($(event.target).is('[href="#editor-media-video"]')) {
|
|
|
|
self.active = self.videoDialog;
|
2014-03-28 10:44:53 +00:00
|
|
|
self.$('.nav-tabs li.search').addClass("hidden");
|
2014-03-27 16:03:24 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-04-07 14:49:14 +00:00
|
|
|
if (this.media) {
|
|
|
|
if (this.media.$.nodeName === "IMG") {
|
|
|
|
this.$('[href="#editor-media-image"]').tab('show');
|
|
|
|
} else if (this.media.$.className.match(/(^|\s)media_iframe_video($|\s)/)) {
|
|
|
|
this.$('[href="#editor-media-video"]').tab('show');
|
|
|
|
} else if (this.media.$.className.match(/(^|\s)fa($|\s)/)) {
|
|
|
|
this.$('[href="#editor-media-icon"]').tab('show');
|
|
|
|
}
|
2014-03-27 10:51:03 +00:00
|
|
|
|
2014-04-07 14:49:14 +00:00
|
|
|
if ($(this.media.$).parent().data("oe-field") === "image") {
|
|
|
|
this.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
|
|
|
|
}
|
2014-03-31 13:33:40 +00:00
|
|
|
}
|
|
|
|
|
2014-03-26 15:58:22 +00:00
|
|
|
return this._super();
|
2014-03-26 16:07:01 +00:00
|
|
|
},
|
|
|
|
save: function () {
|
2014-04-07 10:24:19 +00:00
|
|
|
var self = this;
|
2014-04-07 14:49:14 +00:00
|
|
|
if (self.media) {
|
|
|
|
this.media.$.innerHTML = "";
|
|
|
|
if (this.active !== this.imageDialog) {
|
|
|
|
this.imageDialog.clear();
|
|
|
|
}
|
|
|
|
if (this.active !== this.iconDialog) {
|
|
|
|
this.iconDialog.clear();
|
|
|
|
}
|
|
|
|
if (this.active !== this.videoDialog) {
|
|
|
|
this.videoDialog.clear();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var selection = this.editor.getSelection();
|
|
|
|
var range = selection.getRanges(true)[0];
|
|
|
|
this.media = new CKEDITOR.dom.element("img");
|
|
|
|
range.insertNode(this.media);
|
|
|
|
range.selectNodeContents(this.media);
|
|
|
|
this.active.media = this.media;
|
|
|
|
}
|
|
|
|
|
2014-04-07 16:33:37 +00:00
|
|
|
var $el = $(self.active.media.$);
|
2014-04-07 10:24:19 +00:00
|
|
|
|
2014-03-28 15:47:15 +00:00
|
|
|
this.active.save();
|
|
|
|
|
|
|
|
this.media.$.className = this.media.$.className.replace(/\s+/g, ' ');
|
|
|
|
|
2014-04-07 16:33:37 +00:00
|
|
|
setTimeout(function () {
|
|
|
|
$el.trigger("saved", self.active.media.$);
|
|
|
|
$(document.body).trigger("media-saved", [$el[0], self.active.media.$]);
|
|
|
|
},0);
|
|
|
|
|
2014-04-07 10:24:19 +00:00
|
|
|
this._super();
|
2014-03-26 16:07:01 +00:00
|
|
|
},
|
2014-03-31 12:09:06 +00:00
|
|
|
searchTimer: null,
|
2014-03-27 17:14:17 +00:00
|
|
|
search: function () {
|
2014-03-31 12:09:06 +00:00
|
|
|
var self = this;
|
2014-03-27 17:14:17 +00:00
|
|
|
var needle = this.$("input#icon-search").val();
|
2014-03-31 12:09:06 +00:00
|
|
|
clearTimeout(this.searchTimer);
|
|
|
|
this.searchTimer = setTimeout(function () {
|
|
|
|
self.active.search(needle || "");
|
|
|
|
},250);
|
2014-03-27 17:14:17 +00:00
|
|
|
}
|
2014-03-26 15:58:22 +00:00
|
|
|
});
|
|
|
|
|
2013-10-01 10:00:45 +00:00
|
|
|
/**
|
|
|
|
* ImageDialog widget. Lets users change an image, including uploading a
|
|
|
|
* new image in OpenERP or selecting the image style (if supported by
|
|
|
|
* the caller).
|
|
|
|
*
|
|
|
|
* Initialized as usual, but the caller can hook into two events:
|
|
|
|
*
|
|
|
|
* @event start({url, style}) called during dialog initialization and
|
|
|
|
* opening, the handler can *set* the ``url``
|
|
|
|
* and ``style`` properties on its parameter
|
|
|
|
* to provide these as default values to the
|
|
|
|
* dialog
|
|
|
|
* @event save({url, style}) called during dialog finalization, the handler
|
|
|
|
* is provided with the image url and style
|
|
|
|
* selected by the users (or possibly the ones
|
|
|
|
* originally passed in)
|
|
|
|
*/
|
2014-03-27 16:03:24 +00:00
|
|
|
var IMAGES_PER_ROW = 6;
|
2014-04-08 09:36:48 +00:00
|
|
|
var IMAGES_ROWS = 2;
|
2014-03-26 15:58:22 +00:00
|
|
|
website.editor.ImageDialog = website.editor.Media.extend({
|
2013-09-02 07:52:16 +00:00
|
|
|
template: 'website.editor.dialog.image',
|
|
|
|
events: _.extend({}, website.editor.Dialog.prototype.events, {
|
2014-03-27 16:03:24 +00:00
|
|
|
'change .url-source': function (e) {
|
|
|
|
this.changed($(e.target));
|
|
|
|
},
|
2013-09-02 07:52:16 +00:00
|
|
|
'click button.filepicker': function () {
|
|
|
|
this.$('input[type=file]').click();
|
|
|
|
},
|
|
|
|
'change input[type=file]': 'file_selection',
|
2014-03-27 16:03:24 +00:00
|
|
|
'submit form': 'form_submit',
|
2014-04-08 09:47:03 +00:00
|
|
|
'change input.url': "change_input",
|
|
|
|
'keyup input.url': "change_input",
|
2014-03-27 16:03:24 +00:00
|
|
|
//'change select.image-style': 'preview_image',
|
|
|
|
'click .existing-attachments img': 'select_existing',
|
|
|
|
'click .existing-attachment-remove': 'try_remove',
|
2013-09-02 07:52:16 +00:00
|
|
|
}),
|
2013-09-13 15:23:08 +00:00
|
|
|
|
2014-03-28 10:44:53 +00:00
|
|
|
init: function (parent, editor, media) {
|
2014-03-27 16:03:24 +00:00
|
|
|
this.page = 0;
|
2014-03-28 10:44:53 +00:00
|
|
|
this._super(parent, editor, media);
|
2014-03-27 16:03:24 +00:00
|
|
|
},
|
2013-10-01 10:00:45 +00:00
|
|
|
start: function () {
|
2014-04-08 08:42:37 +00:00
|
|
|
var self = this;
|
2014-03-27 16:03:24 +00:00
|
|
|
var res = this._super();
|
2013-10-01 11:34:29 +00:00
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
var o = { url: null };
|
2013-10-01 10:00:45 +00:00
|
|
|
// avoid typos, prevent addition of new properties to the object
|
|
|
|
Object.preventExtensions(o);
|
|
|
|
this.trigger('start', o);
|
|
|
|
|
2014-04-08 08:42:37 +00:00
|
|
|
this.parent.$(".pager > li").click(function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
var $target = $(e.currentTarget);
|
|
|
|
if ($target.hasClass('disabled')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
self.page += $target.hasClass('previous') ? -1 : 1;
|
|
|
|
self.display_attachments();
|
|
|
|
});
|
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
this.set_image(o.url);
|
2013-09-02 10:54:11 +00:00
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
return res;
|
2013-09-02 10:54:11 +00:00
|
|
|
},
|
|
|
|
save: function () {
|
2014-04-08 09:36:48 +00:00
|
|
|
if (!this.link) {
|
|
|
|
this.link = this.$(".existing-attachments img:first").attr('src');
|
|
|
|
}
|
2013-10-01 10:00:45 +00:00
|
|
|
this.trigger('save', {
|
2014-04-08 09:36:48 +00:00
|
|
|
url: this.link
|
2013-10-01 10:00:45 +00:00
|
|
|
});
|
2014-03-27 16:03:24 +00:00
|
|
|
this.media.renameNode("img");
|
2014-04-08 09:36:48 +00:00
|
|
|
this.media.$.attributes.src = this.link;
|
2013-09-13 15:23:08 +00:00
|
|
|
return this._super();
|
2013-09-02 10:54:11 +00:00
|
|
|
},
|
2014-03-28 15:47:15 +00:00
|
|
|
clear: function () {
|
|
|
|
this.media.$.className = this.media.$.className.replace(/(^|\s)(img(\s|$)|img-[^\s]*)/g, ' ');
|
|
|
|
},
|
2013-11-20 11:52:50 +00:00
|
|
|
cancel: function () {
|
|
|
|
this.trigger('cancel');
|
|
|
|
},
|
2013-09-02 07:52:16 +00:00
|
|
|
|
2014-04-08 09:47:03 +00:00
|
|
|
change_input: function (e) {
|
|
|
|
var $input = $(e.target);
|
|
|
|
var $button = $input.parent().find("button");
|
|
|
|
if ($input.val() === "") {
|
|
|
|
$button.addClass("btn-default").removeClass("btn-primary");
|
|
|
|
} else {
|
|
|
|
$button.removeClass("btn-default").addClass("btn-primary");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-03-31 12:09:06 +00:00
|
|
|
search: function (needle) {
|
|
|
|
var self = this;
|
|
|
|
this.fetch_existing(needle).then(function () {
|
|
|
|
self.selected_existing(self.$('input.url').val());
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2014-02-04 16:58:23 +00:00
|
|
|
set_image: function (url, error) {
|
2014-03-27 16:03:24 +00:00
|
|
|
var self = this;
|
2014-04-08 09:36:48 +00:00
|
|
|
if (url) this.link = url;
|
|
|
|
this.$('input.url').val('');
|
2014-03-27 16:03:24 +00:00
|
|
|
this.fetch_existing().then(function () {
|
|
|
|
self.selected_existing(url);
|
|
|
|
});
|
2013-09-04 11:32:22 +00:00
|
|
|
},
|
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
form_submit: function (event) {
|
2013-09-02 07:52:16 +00:00
|
|
|
var self = this;
|
2014-03-27 16:03:24 +00:00
|
|
|
var $form = this.$('form[action="/website/attach"]');
|
|
|
|
if (!$form.find('input[name="upload"]').val().length) {
|
|
|
|
var url = $form.find('input[name="url"]').val();
|
|
|
|
if (this.selected_existing(url).size()) {
|
|
|
|
event.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2013-09-02 07:52:16 +00:00
|
|
|
var callback = _.uniqueId('func_');
|
|
|
|
this.$('input[name=func]').val(callback);
|
|
|
|
window[callback] = function (url, error) {
|
|
|
|
delete window[callback];
|
|
|
|
self.file_selected(url, error);
|
|
|
|
};
|
2014-03-27 16:03:24 +00:00
|
|
|
},
|
|
|
|
file_selection: function () {
|
|
|
|
this.$el.addClass('nosave');
|
|
|
|
this.$('form').removeClass('has-error').find('.help-block').empty();
|
|
|
|
this.$('button.filepicker').removeClass('btn-danger btn-success');
|
2013-09-02 07:52:16 +00:00
|
|
|
this.$('form').submit();
|
|
|
|
},
|
|
|
|
file_selected: function(url, error) {
|
|
|
|
var $button = this.$('button.filepicker');
|
2014-02-04 16:58:23 +00:00
|
|
|
if (!error) {
|
|
|
|
$button.addClass('btn-success');
|
|
|
|
} else {
|
|
|
|
url = null;
|
|
|
|
this.$('form').addClass('has-error')
|
|
|
|
.find('.help-block').text(error);
|
2013-09-02 07:52:16 +00:00
|
|
|
$button.addClass('btn-danger');
|
|
|
|
}
|
2014-02-04 16:58:23 +00:00
|
|
|
this.set_image(url, error);
|
2014-04-08 09:36:48 +00:00
|
|
|
// auto save and close popup
|
|
|
|
this.parent.save();
|
2013-09-02 13:49:20 +00:00
|
|
|
},
|
2013-09-04 11:32:22 +00:00
|
|
|
|
2014-03-31 12:09:06 +00:00
|
|
|
fetch_existing: function (needle) {
|
2014-03-31 12:16:54 +00:00
|
|
|
var domain = [['res_model', '=', 'ir.ui.view'], '|',
|
|
|
|
['mimetype', '=', false], ['mimetype', '=like', 'image/%']];
|
2014-03-31 12:09:06 +00:00
|
|
|
if (needle && needle.length) {
|
|
|
|
domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]);
|
|
|
|
}
|
2013-09-04 11:32:22 +00:00
|
|
|
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
|
|
|
|
model: 'ir.attachment',
|
|
|
|
method: 'search_read',
|
|
|
|
args: [],
|
|
|
|
kwargs: {
|
2013-09-20 05:10:29 +00:00
|
|
|
fields: ['name', 'website_url'],
|
2014-03-31 12:09:06 +00:00
|
|
|
domain: domain,
|
2014-03-03 13:54:26 +00:00
|
|
|
order: 'id desc',
|
2013-09-11 16:18:54 +00:00
|
|
|
context: website.get_context(),
|
2013-09-04 11:32:22 +00:00
|
|
|
}
|
2014-03-27 16:03:24 +00:00
|
|
|
}).then(this.proxy('fetched_existing'));
|
2013-09-04 11:32:22 +00:00
|
|
|
},
|
|
|
|
fetched_existing: function (records) {
|
2013-09-13 15:13:16 +00:00
|
|
|
this.records = records;
|
|
|
|
this.display_attachments();
|
|
|
|
},
|
|
|
|
display_attachments: function () {
|
2014-03-04 11:07:16 +00:00
|
|
|
this.$('.help-block').empty();
|
2013-09-13 15:13:16 +00:00
|
|
|
var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
|
|
|
|
|
|
|
|
var from = this.page * per_screen;
|
|
|
|
var records = this.records;
|
|
|
|
|
2013-09-04 11:32:22 +00:00
|
|
|
// Create rows of 3 records
|
|
|
|
var rows = _(records).chain()
|
2013-09-13 15:13:16 +00:00
|
|
|
.slice(from, from + per_screen)
|
|
|
|
.groupBy(function (_, index) { return Math.floor(index / IMAGES_PER_ROW); })
|
2013-09-04 11:32:22 +00:00
|
|
|
.values()
|
|
|
|
.value();
|
2013-09-13 15:13:16 +00:00
|
|
|
|
2013-09-04 11:32:22 +00:00
|
|
|
this.$('.existing-attachments').replaceWith(
|
2013-09-13 11:41:43 +00:00
|
|
|
openerp.qweb.render(
|
|
|
|
'website.editor.dialog.image.existing.content', {rows: rows}));
|
2014-04-08 08:42:37 +00:00
|
|
|
this.parent.$('.pager')
|
2013-09-13 15:13:16 +00:00
|
|
|
.find('li.previous').toggleClass('disabled', (from === 0)).end()
|
|
|
|
.find('li.next').toggleClass('disabled', (from + per_screen >= records.length));
|
2013-09-04 11:32:22 +00:00
|
|
|
},
|
|
|
|
select_existing: function (e) {
|
2013-09-19 16:48:55 +00:00
|
|
|
var link = $(e.currentTarget).attr('src');
|
2014-04-08 09:36:48 +00:00
|
|
|
this.link = link;
|
2014-03-27 16:03:24 +00:00
|
|
|
this.selected_existing(link);
|
|
|
|
},
|
|
|
|
selected_existing: function (link) {
|
|
|
|
this.$('.existing-attachment-cell.media_selected').removeClass("media_selected");
|
|
|
|
var $select = this.$('.existing-attachment-cell img').filter(function () {
|
|
|
|
return $(this).attr("src") == link;
|
|
|
|
}).first();
|
|
|
|
$select.parent().addClass("media_selected");
|
|
|
|
return $select;
|
2013-09-02 07:52:16 +00:00
|
|
|
},
|
2014-03-04 11:07:16 +00:00
|
|
|
|
|
|
|
try_remove: function (e) {
|
|
|
|
var $help_block = this.$('.help-block').empty();
|
|
|
|
var self = this;
|
2014-04-07 07:32:47 +00:00
|
|
|
var $a = $(e.target);
|
|
|
|
var id = parseInt($a.data('id'), 10);
|
2014-03-04 11:07:16 +00:00
|
|
|
var attachment = _.findWhere(this.records, {id: id});
|
2014-04-07 07:32:47 +00:00
|
|
|
var $both = $a.parent().children();
|
|
|
|
|
|
|
|
$both.css({borderWidth: "5px", borderColor: "#f00"});
|
2014-03-04 11:07:16 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2014-04-07 07:32:47 +00:00
|
|
|
$both.css({borderWidth: "", borderColor: ""});
|
2014-03-04 11:07:16 +00:00
|
|
|
$help_block.replaceWith(openerp.qweb.render(
|
|
|
|
'website.editor.dialog.image.existing.error', {
|
|
|
|
views: prevented[id]
|
|
|
|
}
|
|
|
|
));
|
|
|
|
});
|
|
|
|
},
|
2013-09-02 07:52:16 +00:00
|
|
|
});
|
2013-08-29 15:34:41 +00:00
|
|
|
|
2014-03-27 16:03:24 +00:00
|
|
|
website.editor.RTEImageDialog = website.editor.ImageDialog.extend({
|
2014-03-27 17:14:17 +00:00
|
|
|
init: function (parent, editor, media) {
|
|
|
|
this._super(parent, editor, media);
|
2014-03-27 16:03:24 +00:00
|
|
|
|
|
|
|
this.on('start', this, this.proxy('started'));
|
|
|
|
this.on('save', this, this.proxy('saved'));
|
|
|
|
},
|
|
|
|
started: function (holder) {
|
|
|
|
if (!this.media) {
|
|
|
|
var selection = this.editor.getSelection();
|
|
|
|
this.media = selection && selection.getSelectedElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
var el = this.media;
|
|
|
|
if (!el || !el.is('img')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
holder.url = el.getAttribute('src');
|
|
|
|
},
|
|
|
|
saved: function (data) {
|
|
|
|
var element, editor = this.editor;
|
|
|
|
if (!(element = this.media)) {
|
|
|
|
element = editor.document.createElement('img');
|
|
|
|
element.addClass('img');
|
|
|
|
element.addClass('img-responsive');
|
|
|
|
// focus event handler interactions between bootstrap (modal)
|
|
|
|
// and ckeditor (RTE) lead to blowing the stack in Safari and
|
|
|
|
// Chrome (but not FF) when this is done synchronously =>
|
|
|
|
// defer insertion so modal has been hidden & destroyed before
|
|
|
|
// it happens
|
|
|
|
setTimeout(function () {
|
|
|
|
editor.insertElement(element);
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
var style = data.style;
|
|
|
|
element.setAttribute('src', data.url);
|
|
|
|
element.removeAttribute('data-cke-saved-src');
|
|
|
|
if (style) { element.addClass(style); }
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2013-09-16 09:03:53 +00:00
|
|
|
function get_selected_link(editor) {
|
|
|
|
var sel = editor.getSelection(),
|
|
|
|
el = sel.getSelectedElement();
|
|
|
|
if (el && el.is('a')) { return el; }
|
|
|
|
|
|
|
|
var range = sel.getRanges(true)[0];
|
|
|
|
if (!range) { return null; }
|
|
|
|
|
|
|
|
range.shrink(CKEDITOR.SHRINK_TEXT);
|
2013-09-19 15:20:39 +00:00
|
|
|
var commonAncestor = range.getCommonAncestor();
|
|
|
|
var viewRoot = editor.elementPath(commonAncestor).contains(function (element) {
|
2014-03-26 15:58:22 +00:00
|
|
|
return element.data('oe-model') === 'ir.ui.view';
|
2013-09-19 15:20:39 +00:00
|
|
|
});
|
|
|
|
if (!viewRoot) { return null; }
|
|
|
|
// if viewRoot is the first link, don't edit it.
|
|
|
|
return new CKEDITOR.dom.elementPath(commonAncestor, viewRoot)
|
|
|
|
.contains('a', true);
|
2013-09-16 09:03:53 +00:00
|
|
|
}
|
|
|
|
|
2014-03-26 15:58:22 +00:00
|
|
|
website.editor.FontIconsDialog = website.editor.Media.extend({
|
2014-01-16 09:47:47 +00:00
|
|
|
template: 'website.editor.dialog.font-icons',
|
|
|
|
events : _.extend({}, website.editor.Dialog.prototype.events, {
|
|
|
|
change: 'update_preview',
|
2014-01-20 15:36:10 +00:00
|
|
|
'click .font-icons-icon': function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
this.$('#fa-icon').val(e.target.getAttribute('data-id'));
|
|
|
|
this.update_preview();
|
|
|
|
},
|
2014-01-20 16:26:04 +00:00
|
|
|
'click #fa-preview span': function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
this.$('#fa-size').val(e.target.getAttribute('data-size'));
|
|
|
|
this.update_preview();
|
2014-01-21 10:04:08 +00:00
|
|
|
},
|
2014-01-16 09:47:47 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
// List of FontAwesome icons in 4.0.3, extracted from the cheatsheet.
|
|
|
|
// Each icon provides the unicode codepoint as ``text`` and the class
|
|
|
|
// name as ``id`` so the whole thing can be fed directly to select2
|
|
|
|
// without post-processing and do the right thing (except for the part
|
|
|
|
// where we still need to implement ``initSelection``)
|
|
|
|
// TODO: add id/name to the text in order to allow FAYT selection of icons?
|
|
|
|
icons: [{"text": "\uf000", "id": "fa-glass"}, {"text": "\uf001", "id": "fa-music"}, {"text": "\uf002", "id": "fa-search"}, {"text": "\uf003", "id": "fa-envelope-o"}, {"text": "\uf004", "id": "fa-heart"}, {"text": "\uf005", "id": "fa-star"}, {"text": "\uf006", "id": "fa-star-o"}, {"text": "\uf007", "id": "fa-user"}, {"text": "\uf008", "id": "fa-film"}, {"text": "\uf009", "id": "fa-th-large"}, {"text": "\uf00a", "id": "fa-th"}, {"text": "\uf00b", "id": "fa-th-list"}, {"text": "\uf00c", "id": "fa-check"}, {"text": "\uf00d", "id": "fa-times"}, {"text": "\uf00e", "id": "fa-search-plus"}, {"text": "\uf010", "id": "fa-search-minus"}, {"text": "\uf011", "id": "fa-power-off"}, {"text": "\uf012", "id": "fa-signal"}, {"text": "\uf013", "id": "fa-cog"}, {"text": "\uf014", "id": "fa-trash-o"}, {"text": "\uf015", "id": "fa-home"}, {"text": "\uf016", "id": "fa-file-o"}, {"text": "\uf017", "id": "fa-clock-o"}, {"text": "\uf018", "id": "fa-road"}, {"text": "\uf019", "id": "fa-download"}, {"text": "\uf01a", "id": "fa-arrow-circle-o-down"}, {"text": "\uf01b", "id": "fa-arrow-circle-o-up"}, {"text": "\uf01c", "id": "fa-inbox"}, {"text": "\uf01d", "id": "fa-play-circle-o"}, {"text": "\uf01e", "id": "fa-repeat"}, {"text": "\uf021", "id": "fa-refresh"}, {"text": "\uf022", "id": "fa-list-alt"}, {"text": "\uf023", "id": "fa-lock"}, {"text": "\uf024", "id": "fa-flag"}, {"text": "\uf025", "id": "fa-headphones"}, {"text": "\uf026", "id": "fa-volume-off"}, {"text": "\uf027", "id": "fa-volume-down"}, {"text": "\uf028", "id": "fa-volume-up"}, {"text": "\uf029", "id": "fa-qrcode"}, {"text": "\uf02a", "id": "fa-barcode"}, {"text": "\uf02b", "id": "fa-tag"}, {"text": "\uf02c", "id": "fa-tags"}, {"text": "\uf02d", "id": "fa-book"}, {"text": "\uf02e", "id": "fa-bookmark"}, {"text": "\uf02f", "id": "fa-print"}, {"text": "\uf030", "id": "fa-camera"}, {"text": "\uf031", "id": "fa-font"}, {"text": "\uf032", "id": "fa-bold"}, {"text": "\uf033", "id": "fa-italic"}, {"text": "\uf034", "id": "fa-text-height"}, {"text": "\uf035", "id": "fa-text-width"}, {"text": "\uf036", "id": "fa-align-left"}, {"text": "\uf037", "id": "fa-align-center"}, {"text": "\uf038", "id": "fa-align-right"}, {"text": "\uf039", "id": "fa-align-justify"}, {"text": "\uf03a", "id": "fa-list"}, {"text": "\uf03b", "id": "fa-outdent"}, {"text": "\uf03c", "id": "fa-indent"}, {"text": "\uf03d", "id": "fa-video-camera"}, {"text": "\uf03e", "id": "fa-picture-o"}, {"text": "\uf040", "id": "fa-pencil"}, {"text": "\uf041", "id": "fa-map-marker"}, {"text": "\uf042", "id": "fa-adjust"}, {"text": "\uf043", "id": "fa-tint"}, {"text": "\uf044", "id": "fa-pencil-square-o"}, {"text": "\uf045", "id": "fa-share-square-o"}, {"text": "\uf046", "id": "fa-check-square-o"}, {"text": "\uf047", "id": "fa-arrows"}, {"text": "\uf048", "id": "fa-step-backward"}, {"text": "\uf049", "id": "fa-fast-backward"}, {"text": "\uf04a", "id": "fa-backward"}, {"text": "\uf04b", "id": "fa-play"}, {"text": "\uf04c", "id": "fa-pause"}, {"text": "\uf04d", "id": "fa-stop"}, {"text": "\uf04e", "id": "fa-forward"}, {"text": "\uf050", "id": "fa-fast-forward"}, {"text": "\uf051", "id": "fa-step-forward"}, {"text": "\uf052", "id": "fa-eject"}, {"text": "\uf053", "id": "fa-chevron-left"}, {"text": "\uf054", "id": "fa-chevron-right"}, {"text": "\uf055", "id": "fa-plus-circle"}, {"text": "\uf056", "id": "fa-minus-circle"}, {"text": "\uf057", "id": "fa-times-circle"}, {"text": "\uf058", "id": "fa-check-circle"}, {"text": "\uf059", "id": "fa-question-circle"}, {"text": "\uf05a", "id": "fa-info-circle"}, {"text": "\uf05b", "id": "fa-crosshairs"}, {"text": "\uf05c", "id": "fa-times-circle-o"}, {"text": "\uf05d", "id": "fa-check-circle-o"}, {"text": "\uf05e", "id": "fa-ban"}, {"text": "\uf060", "id": "fa-arrow-left"}, {"text": "\uf061", "id": "fa-arrow-right"}, {"text": "\uf062", "id": "fa-arrow-up"}, {"text": "\uf063", "id": "fa-arrow-down"}, {"text": "\uf064", "id": "fa-share"}, {"text": "\uf065", "id": "fa-expand"}, {"text": "\uf066", "id": "fa-compress"}, {"text": "\uf067", "id": "fa-plus"}, {"text": "\uf068", "id": "fa-minus"}, {"text": "
|
|
|
|
/**
|
|
|
|
* Initializes select2: in Chrome and Safari, <select> font apparently
|
|
|
|
* isn't customizable (?) and the fontawesome glyphs fail to appear.
|
|
|
|
*/
|
|
|
|
start: function () {
|
2014-01-20 15:36:10 +00:00
|
|
|
return this._super().then(this.proxy('load_data'));
|
2014-01-16 09:47:47 +00:00
|
|
|
},
|
2014-03-27 17:14:17 +00:00
|
|
|
search: function (needle) {
|
|
|
|
var icons = this.icons;
|
|
|
|
if (needle) {
|
|
|
|
icons = _(icons).filter(function (icon) {
|
|
|
|
return icon.id.substring(3).indexOf(needle) !== -1;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$('div.font-icons-icons').html(
|
|
|
|
openerp.qweb.render(
|
|
|
|
'website.editor.dialog.font-icons.icons',
|
|
|
|
{icons: icons}));
|
|
|
|
},
|
2014-01-16 09:47:47 +00:00
|
|
|
/**
|
|
|
|
* Removes existing FontAwesome classes on the bound element, and sets
|
|
|
|
* all the new ones if necessary.
|
|
|
|
*/
|
|
|
|
save: function () {
|
2014-04-07 10:24:19 +00:00
|
|
|
var style = this.media.$.attributes.style ? this.media.$.attributes.style.textContent : '';
|
2014-03-27 16:03:24 +00:00
|
|
|
var classes = (this.media.$.className||"").split(/\s+/);
|
2014-01-16 09:47:47 +00:00
|
|
|
var non_fa_classes = _.reject(classes, function (cls) {
|
|
|
|
return cls === 'fa' || /^fa-/.test(cls);
|
|
|
|
});
|
|
|
|
var final_classes = non_fa_classes.concat(this.get_fa_classes());
|
2014-03-27 16:03:24 +00:00
|
|
|
this.media.$.className = final_classes.join(' ');
|
|
|
|
this.media.renameNode("span");
|
2014-04-07 10:24:19 +00:00
|
|
|
this.media.$.attributes.style.textContent = style;
|
2014-01-16 09:47:47 +00:00
|
|
|
this._super();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Looks up the various FontAwesome classes on the bound element and
|
|
|
|
* sets the corresponding template/form elements to the right state.
|
|
|
|
* If multiple classes of the same category are present on an element
|
|
|
|
* (e.g. fa-lg and fa-3x) the last one occurring will be selected,
|
|
|
|
* which may not match the visual look of the element.
|
|
|
|
*/
|
|
|
|
load_data: function () {
|
2014-04-07 14:49:14 +00:00
|
|
|
var classes = (this.media&&this.media.$.className||"").split(/\s+/);
|
2014-01-16 09:47:47 +00:00
|
|
|
for (var i = 0; i < classes.length; i++) {
|
|
|
|
var cls = classes[i];
|
|
|
|
switch(cls) {
|
2014-01-20 16:26:04 +00:00
|
|
|
case 'fa-2x':case 'fa-3x':case 'fa-4x':case 'fa-5x':
|
2014-01-16 09:47:47 +00:00
|
|
|
// size classes
|
|
|
|
this.$('#fa-size').val(cls);
|
|
|
|
continue;
|
|
|
|
case 'fa-spin':
|
|
|
|
case 'fa-rotate-90':case 'fa-rotate-180':case 'fa-rotate-270':
|
|
|
|
case 'fa-flip-horizontal':case 'fa-rotate-vertical':
|
|
|
|
this.$('#fa-rotation').val(cls);
|
|
|
|
continue;
|
2014-01-20 15:36:10 +00:00
|
|
|
case 'fa-fw':
|
2014-01-16 09:47:47 +00:00
|
|
|
continue;
|
|
|
|
case 'fa-border':
|
|
|
|
this.$('#fa-border').prop('checked', true);
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
if (!/^fa-/.test(cls)) { continue; }
|
2014-01-20 15:36:10 +00:00
|
|
|
this.$('#fa-icon').val(cls);
|
2014-01-16 09:47:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.update_preview();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Serializes the dialog to an array of FontAwesome classes. Includes
|
|
|
|
* the base ``fa``.
|
|
|
|
*/
|
|
|
|
get_fa_classes: function () {
|
|
|
|
return [
|
|
|
|
'fa',
|
2014-01-20 15:36:10 +00:00
|
|
|
this.$('#fa-icon').val(),
|
2014-01-16 09:47:47 +00:00
|
|
|
this.$('#fa-size').val(),
|
|
|
|
this.$('#fa-rotation').val(),
|
|
|
|
this.$('#fa-border').prop('checked') ? 'fa-border' : ''
|
|
|
|
];
|
|
|
|
},
|
|
|
|
update_preview: function () {
|
2014-03-27 16:03:24 +00:00
|
|
|
this.$preview.empty();
|
2014-01-20 16:26:04 +00:00
|
|
|
var $preview = this.$('#fa-preview').empty();
|
2014-03-27 16:03:24 +00:00
|
|
|
|
2014-01-20 16:26:04 +00:00
|
|
|
var sizes = ['', 'fa-2x', 'fa-3x', 'fa-4x', 'fa-5x'];
|
|
|
|
var classes = this.get_fa_classes();
|
|
|
|
var no_sizes = _.difference(classes, sizes).join(' ');
|
|
|
|
var selected = false;
|
|
|
|
for (var i = sizes.length - 1; i >= 0; i--) {
|
|
|
|
var size = sizes[i];
|
|
|
|
|
|
|
|
var $p = $('<span>')
|
|
|
|
.attr('data-size', size)
|
|
|
|
.addClass(size)
|
|
|
|
.addClass(no_sizes);
|
2014-04-07 14:49:14 +00:00
|
|
|
if ((size && _.contains(classes, size)) || (classes[2] === "" && !selected)) {
|
2014-03-27 16:03:24 +00:00
|
|
|
this.$preview.append($p.clone());
|
2014-04-07 10:24:19 +00:00
|
|
|
this.$('#fa-size').val(size);
|
2014-01-20 16:26:04 +00:00
|
|
|
$p.addClass('font-icons-selected');
|
|
|
|
selected = true;
|
|
|
|
}
|
|
|
|
$preview.prepend($p);
|
|
|
|
}
|
2014-03-28 15:47:15 +00:00
|
|
|
},
|
|
|
|
clear: function () {
|
|
|
|
this.media.$.className = this.media.$.className.replace(/(^|\s)(fa(\s|$)|fa-[^\s]*)/g, ' ');
|
|
|
|
},
|
2014-01-16 09:47:47 +00:00
|
|
|
});
|
|
|
|
|
2014-03-28 10:44:53 +00:00
|
|
|
website.editor.VideoDialog = website.editor.Media.extend({
|
|
|
|
template: 'website.editor.dialog.video',
|
|
|
|
events : _.extend({}, website.editor.Dialog.prototype.events, {
|
2014-04-07 08:11:59 +00:00
|
|
|
'click input#urlvideo ~ button': 'get_video',
|
|
|
|
'click input#embedvideo ~ button': 'get_embed_video',
|
2014-04-08 09:36:48 +00:00
|
|
|
'change input#urlvideo': 'change_input',
|
2014-04-08 09:47:03 +00:00
|
|
|
'keyup input#urlvideo': 'change_input',
|
|
|
|
'change input#embedvideo': 'change_input',
|
|
|
|
'keyup input#embedvideo': 'change_input'
|
2014-03-28 10:44:53 +00:00
|
|
|
}),
|
|
|
|
start: function () {
|
|
|
|
this.$iframe = this.$("iframe");
|
2014-04-07 14:49:14 +00:00
|
|
|
var $media = $(this.media && this.media.$);
|
2014-03-28 10:44:53 +00:00
|
|
|
if ($media.hasClass("media_iframe_video")) {
|
|
|
|
var src = $media.data('src');
|
|
|
|
this.$("input#urlvideo").val(src);
|
|
|
|
this.$("#autoplay").attr("checked", src.indexOf('autoplay=1') != -1);
|
|
|
|
this.get_video();
|
|
|
|
}
|
|
|
|
return this._super();
|
|
|
|
},
|
2014-04-08 09:36:48 +00:00
|
|
|
change_input: function (e) {
|
|
|
|
var $input = $(e.target);
|
|
|
|
var $button = $input.parent().find("button");
|
|
|
|
if ($input.val() === "") {
|
|
|
|
$button.addClass("btn-default").removeClass("btn-primary");
|
|
|
|
} else {
|
|
|
|
$button.removeClass("btn-default").addClass("btn-primary");
|
|
|
|
}
|
|
|
|
},
|
2014-03-28 10:44:53 +00:00
|
|
|
get_url: function () {
|
|
|
|
var video_id = this.$("#video_id").val();
|
|
|
|
var video_type = this.$("#video_type").val();
|
|
|
|
switch (video_type) {
|
|
|
|
case "youtube":
|
|
|
|
return "//www.youtube.com/embed/" + video_id + "?autoplay=" + (this.$("#autoplay").is(":checked") ? 1 : 0);
|
|
|
|
case "vimeo":
|
|
|
|
return "//player.vimeo.com/video/" + video_id + "?autoplay=" + (this.$("#autoplay").is(":checked") ? 1 : 0);
|
|
|
|
case "dailymotion":
|
|
|
|
return "//www.dailymotion.com/embed/video/" + video_id + "?autoplay=" + (this.$("#autoplay").is(":checked") ? 1 : 0);
|
2014-03-31 13:55:58 +00:00
|
|
|
default:
|
|
|
|
return video_id;
|
2014-03-28 10:44:53 +00:00
|
|
|
}
|
|
|
|
},
|
2014-04-07 08:11:59 +00:00
|
|
|
get_embed_video: function (event) {
|
|
|
|
event.preventDefault();
|
2014-03-31 13:55:58 +00:00
|
|
|
var embedvideo = this.$("input#embedvideo").val().match(/src=["']?([^"']+)["' ]?/);
|
2014-04-07 08:11:59 +00:00
|
|
|
if (embedvideo) {
|
|
|
|
this.$("input#urlvideo").val(embedvideo[1]);
|
|
|
|
this.get_video(event);
|
|
|
|
}
|
|
|
|
return false;
|
2014-03-31 13:55:58 +00:00
|
|
|
},
|
2014-04-07 08:11:59 +00:00
|
|
|
get_video: function (event) {
|
2014-04-07 14:49:14 +00:00
|
|
|
if (event) event.preventDefault();
|
2014-03-28 10:44:53 +00:00
|
|
|
var needle = this.$("input#urlvideo").val();
|
|
|
|
var video_id;
|
|
|
|
var video_type;
|
|
|
|
|
|
|
|
if (needle.indexOf(".youtube.") != -1) {
|
|
|
|
video_type = "youtube";
|
|
|
|
video_id = needle.match(/\.youtube\.[a-z]+\/(embed\/|watch\?v=)?([^\/?&]+)/i)[2];
|
|
|
|
} else if (needle.indexOf("//youtu.") != -1) {
|
|
|
|
video_type = "youtube";
|
|
|
|
video_id = needle.match(/youtube\.[a-z]+\/([^\/?&]+)/i)[1];
|
|
|
|
} else if (needle.indexOf("player.vimeo.") != -1 || needle.indexOf("//vimeo.") != -1) {
|
|
|
|
video_type = "vimeo";
|
2014-03-28 14:16:19 +00:00
|
|
|
video_id = needle.match(/vimeo\.[a-z]+\/(video\/)?([^?&]+)/i)[2];
|
2014-03-28 10:44:53 +00:00
|
|
|
} else if (needle.indexOf(".dailymotion.") != -1) {
|
|
|
|
video_type = "dailymotion";
|
|
|
|
video_id = needle.match(/dailymotion\.[a-z]+\/(embed\/)?(video\/)?([^\/?&]+)/i)[3];
|
2014-03-31 13:55:58 +00:00
|
|
|
} else {
|
|
|
|
video_type = "";
|
|
|
|
video_id = needle;
|
2014-03-28 10:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.$("#video_id").val(video_id);
|
|
|
|
this.$("#video_type").val(video_type);
|
|
|
|
|
|
|
|
this.$iframe.attr("src", this.get_url());
|
2014-04-07 08:11:59 +00:00
|
|
|
return false;
|
2014-03-28 10:44:53 +00:00
|
|
|
},
|
|
|
|
save: function () {
|
2014-03-28 14:16:19 +00:00
|
|
|
var video_id = this.$("#video_id").val();
|
2014-04-08 09:36:48 +00:00
|
|
|
if (!video_id) {
|
|
|
|
this.$("button.btn-primary").click();
|
|
|
|
video_id = this.$("#video_id").val();
|
|
|
|
}
|
2014-03-28 14:16:19 +00:00
|
|
|
var video_type = this.$("#video_type").val();
|
2014-04-07 10:24:19 +00:00
|
|
|
var style = this.media.$.attributes.style ? this.media.$.attributes.style.textContent : '';
|
2014-03-28 14:16:19 +00:00
|
|
|
var $iframe = $(
|
2014-04-07 10:24:19 +00:00
|
|
|
'<div class="media_iframe_video" data-src="'+this.get_url()+'" style="'+style+'">'+
|
2014-03-28 14:16:19 +00:00
|
|
|
'<div class="css_editable_mode_display"> </div>'+
|
2014-03-28 10:44:53 +00:00
|
|
|
'<iframe src="'+this.get_url()+'" frameborder="0" allowfullscreen="allowfullscreen"></iframe>'+
|
|
|
|
'</div>');
|
2014-03-28 14:16:19 +00:00
|
|
|
$(this.media.$).replaceWith($iframe);
|
2014-04-07 16:33:37 +00:00
|
|
|
this.media.$ = $iframe[0];
|
2014-03-28 10:44:53 +00:00
|
|
|
this._super();
|
|
|
|
},
|
2014-03-28 15:47:15 +00:00
|
|
|
clear: function () {
|
|
|
|
delete this.media.$.dataset.src;
|
|
|
|
this.media.$.className = this.media.$.className.replace(/(^|\s)media_iframe_video(\s|$)/g, ' ');
|
|
|
|
},
|
2014-03-28 10:44:53 +00:00
|
|
|
});
|
|
|
|
|
2013-10-16 12:37:08 +00:00
|
|
|
website.Observer = window.MutationObserver || window.WebkitMutationObserver || window.JsMutationObserver;
|
2013-09-04 14:12:47 +00:00
|
|
|
var OBSERVER_CONFIG = {
|
|
|
|
childList: true,
|
|
|
|
attributes: true,
|
|
|
|
characterData: true,
|
|
|
|
subtree: true,
|
|
|
|
attributeOldValue: true,
|
|
|
|
};
|
2013-10-16 12:37:08 +00:00
|
|
|
var observer = new website.Observer(function (mutations) {
|
2013-09-04 11:32:22 +00:00
|
|
|
// NOTE: Webkit does not fire DOMAttrModified => webkit browsers
|
|
|
|
// relying on JsMutationObserver shim (Chrome < 18, Safari < 6)
|
|
|
|
// will not mark dirty on attribute changes (@class, img/@src,
|
|
|
|
// a/@href, ...)
|
2013-08-29 10:29:56 +00:00
|
|
|
_(mutations).chain()
|
2013-09-09 10:42:38 +00:00
|
|
|
.filter(function (m) {
|
2013-12-02 11:33:04 +00:00
|
|
|
// ignore any change related to mundane image-edit-button
|
|
|
|
if (m.target && m.target.className
|
|
|
|
&& m.target.className.indexOf('image-edit-button') !== -1) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-08-29 10:29:56 +00:00
|
|
|
switch(m.type) {
|
2013-09-04 14:07:44 +00:00
|
|
|
case 'attributes': // ignore .cke_focus being added or removed
|
2014-02-18 16:01:44 +00:00
|
|
|
// ignore id modification
|
|
|
|
if (m.attributeName === 'id') { return false; }
|
2013-09-04 14:07:44 +00:00
|
|
|
// if attribute is not a class, can't be .cke_focus change
|
|
|
|
if (m.attributeName !== 'class') { return true; }
|
|
|
|
|
|
|
|
// find out what classes were added or removed
|
2013-10-01 14:19:30 +00:00
|
|
|
var oldClasses = (m.oldValue || '').split(/\s+/);
|
2013-09-04 14:07:44 +00:00
|
|
|
var newClasses = m.target.className.split(/\s+/);
|
|
|
|
var change = _.union(_.difference(oldClasses, newClasses),
|
|
|
|
_.difference(newClasses, oldClasses));
|
|
|
|
// ignore mutation if the *only* change is .cke_focus
|
|
|
|
return change.length !== 1 || change[0] === 'cke_focus';
|
2013-08-29 10:29:56 +00:00
|
|
|
case 'childList':
|
2014-02-26 15:20:04 +00:00
|
|
|
setTimeout(function () {
|
|
|
|
fixup_browser_crap(m.addedNodes);
|
|
|
|
}, 0);
|
2013-12-02 11:33:04 +00:00
|
|
|
// 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.
|
|
|
|
return !!remove_mundane_nodes(m.addedNodes).length ||
|
|
|
|
!!remove_mundane_nodes(m.removedNodes).length;
|
2013-08-29 10:29:56 +00:00
|
|
|
default:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map(function (m) {
|
|
|
|
var node = m.target;
|
|
|
|
while (node && !$(node).hasClass('oe_editable')) {
|
|
|
|
node = node.parentNode;
|
|
|
|
}
|
|
|
|
return node;
|
|
|
|
})
|
|
|
|
.compact()
|
|
|
|
.uniq()
|
|
|
|
.each(function (node) { $(node).trigger('content_changed'); })
|
|
|
|
});
|
2013-12-02 11:33:04 +00:00
|
|
|
function remove_mundane_nodes(nodes) {
|
|
|
|
if (!nodes || !nodes.length) { return []; }
|
|
|
|
|
|
|
|
var output = [];
|
|
|
|
for(var i=0; i<nodes.length; ++i) {
|
|
|
|
var node = nodes[i];
|
2013-12-03 16:16:30 +00:00
|
|
|
if (node.nodeType === document.ELEMENT_NODE) {
|
|
|
|
if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') {
|
|
|
|
// <br type="_moz"> appears when focusing RTE in FF, ignore
|
|
|
|
continue;
|
2014-03-24 10:18:50 +00:00
|
|
|
} else if (node.nodeName === 'DIV' && $(node).hasClass('oe_drop_zone')) {
|
|
|
|
// ignore dropzone inserted by snippets
|
|
|
|
continue
|
2013-12-03 16:16:30 +00:00
|
|
|
}
|
2013-12-02 11:33:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
output.push(node);
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
2014-02-26 15:20:04 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-29 10:29:56 +00:00
|
|
|
})();
|