diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index fb0c2d081b8..e11bd754a05 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -129,6 +129,26 @@ color: white; } +.oe_website_editorbar .oe_rte_toolbar div.dropdown { + display: inline-block; +} +.oe_website_editorbar .oe_rte_toolbar div.dropdown li { + display: list-item; +} +.oe_website_editorbar .oe_rte_toolbar button { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + text-decoration: inherit; +} +.oe_website_editorbar .oe_rte_toolbar button.oe_button_list { + padding-right: 3px; +} +.oe_website_editorbar .oe_rte_toolbar button.oe_button_list:after { + content: "\F0D7"; + padding-left: 6px; +} + /* }}} */ .oe_editable:focus { outline: #80b5f2 solid 5px !important; diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index 8e17378851b..9863264d692 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -218,8 +218,22 @@ text-shadow: black 0px 0px 3px color: white +.oe_website_editorbar .oe_rte_toolbar + div.dropdown + display: inline-block + li + display: list-item - + button + font-family: FontAwesome + font-weight: normal + font-style: normal + text-decoration: inherit + &.oe_button_list + padding-right: 3px + &:after + content: "\F0D7" + padding-left: 6px /* }}} */ diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index 0ebb564c200..8fd6469f3ab 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -1,5 +1,5 @@ openerp.website = function(instance) { - +var _lt = instance.web._lt; instance.website.EditorBar = instance.web.Widget.extend({ template: 'Website.EditorBar', events: { @@ -9,31 +9,47 @@ instance.website.EditorBar = instance.web.Widget.extend({ 'click button[data-action=snippet]': 'snippet', }, container: 'body', + init: function () { + this._super.apply(this, arguments); + this.saving_mutex = new $.Mutex(); + }, start: function() { var self = this; - this.saving_mutex = new $.Mutex(); - self.$('button[data-action]').prop('disabled', true); - self.$('button[data-action=edit],button[data-action=snippet]').prop('disabled', false); + + this.$('button[data-action]').prop('disabled', true) + .parent().hide(); + this.$buttons = { + edit: this.$('button[data-action=edit]'), + save: this.$('button[data-action=save]'), + cancel: this.$('button[data-action=cancel]'), + snippet: this.$('button[data-action=snippet]'), + }; + this.$buttons.edit.prop('disabled', false).parent().show(); + self.snippet_start(); - $('body').on("keypress", ".oe_editable", function(e) { - var $e = $(e.currentTarget); - if (!$e.is('.oe_dirty')) { - $e.addClass('oe_dirty'); - self.$('button[data-action=save],button[data-action=cancel]').prop('disabled', false); - // TODO: Are we going to use a meta-data flag in order to know if the field shall be text or html ? - } - if (e.which == 13) { - $e.blur(); - e.preventDefault(); - } - }); + this.rte = new instance.website.RTE(this); + this.rte.on('change', this, this.proxy('rte_changed')); - return this._super.apply(this, arguments); + return $.when( + this._super.apply(this, arguments), + this.rte.insertBefore(this.$buttons.snippet.parent()) + ); }, edit: function () { - this.$('button[data-action=edit]').prop('disabled', true); - $('[data-oe-model]').prop('contentEditable', true).addClass('oe_editable'); + this.$buttons.edit.prop('disabled', true).parent().hide(); + this.$buttons.cancel.add(this.$buttons.snippet).prop('disabled', false) + .add(this.$buttons.save) + .parent().show(); + // TODO: span edition changing edition state (save button) + this.rte.start_edition( + $('[data-oe-model]') + .not('link, script') + .prop('contentEditable', true) + .addClass('oe_editable')); + }, + rte_changed: function () { + this.$buttons.save.prop('disabled', false); }, save: function () { var self = this; @@ -62,9 +78,11 @@ instance.website.EditorBar = instance.web.Widget.extend({ var xpath = data.oeXpath; if (xpath) { var $w = $el.clone(); - $w.removeClass('aloha-editable aloha-editable-highlight oe_dirty'); + $w.removeClass('oe_dirty'); _.each(['model', 'id', 'field', 'xpath'], function(d) {$w.removeAttr('data-oe-' + d);}); - $w.prop('contentEditable', false).removeClass('oe_editable'); + $w + .removeClass('oe_editable') + .prop('contentEditable', false); html = $w.wrap('
').parent().html(); } return (new instance.web.DataSet(this, 'ir.ui.view')).call('save', [data.oeModel, data.oeId, data.oeField, html, xpath]); @@ -101,6 +119,195 @@ instance.website.EditorBar = instance.web.Widget.extend({ }, }); +instance.website.Action = instance.web.Widget.extend({ + tagName: 'button', + attributes: { + type: 'button', + }, + events: { click: 'perform' }, + init: function (parent, name) { + this._super(parent); + this.name = name; + }, + start: function () { + this.$el.text(this.name); + return this._super(); + }, + /** + * Executes action + */ + perform: null, + toggle: function (to) { + this.$el.prop('disabled', !to); + }, +}); +var Style = instance.website.Style = instance.website.Action.extend({ + init: function (parent, name, style) { + this._super(parent, name); + this.style = style; + }, + perform: function () { + this.getParent().with_editor(function (editor) { + editor.applyStyle(new CKEDITOR.style(this.style)) + }.bind(this)); + }, +}); +var Command = instance.website.Command = instance.website.Action.extend({ + init: function (parent, name, command) { + this._super(parent, name); + this.command = command; + }, + perform: function () { + this.getParent().with_editor(function (editor) { + switch (typeof this.command) { + case 'string': editor.execCommand(this.command); break; + case 'function': this.command(editor); break; + } + }.bind(this)); + }, +}); +var Group = instance.website.ActionGroup = instance.website.Action.extend({ + template: 'Website.ActionGroup', + events: { 'click > button': 'perform' }, + instances: [], + init: function (parent, name, actions) { + this._super(parent, name); + this.actions = _(actions).map(this.getParent().proxy('init_command')); + }, + start: function () { + this.instances.push(this); + var $ul = this.$('ul'); + return $.when.apply(null, _(this.actions).map(function (action) { + var $li = $('
  • ').appendTo($ul); + return action.appendTo($li); + })); + }, + destroy: function () { + this.instances = _(this.instances).without(this); + return this._super(); + }, + perform: function () { + this.getParent().with_editor(function () { + _(this.instances).chain() + .without(this) + .pluck('$el') + .invoke('removeClass', 'open'); + // JS part of bootstrap dropdown does really weird stuff which + // interacts quite badly with the RTE thing, so bypass it + this.$el.toggleClass('open'); + }.bind(this), false); + return false; + }, + toggle: function (to) { + this.$('> button').prop('disabled', !to); + }, +}); + +instance.website.RTE = instance.web.Widget.extend({ + tagName: 'li', + className: 'oe_right oe_rte_toolbar', + commands: [ + [Command, "\uf032", 'bold'], + [Command, "\uf033", 'italic'], + [Command, "\uf0cd", 'underline'], + [Command, "\uf0cc", 'strike'], + [Command, "\uf12b", 'superscript'], + [Command, "\uf12c", 'subscript'], + [Group, "\uf0ca", [ + [Command, "\uf0ca", 'bulletedlist'], + [Command, "\uf0cb", 'numberedlist'] + ]], + [Group, _lt("Heading"), [ + [Style, _lt('H1'), { element: 'h1' }], + [Style, _lt('H2'), { element: 'h2', }], + [Style, _lt('H3'), { element: 'h3', }], + [Style, _lt('H4'), { element: 'h4', }], + [Style, _lt('H5'), { element: 'h5', }], + [Style, _lt('H6'), { element: 'h6', }] + ]] + ], + // editor.ui.items -> possible commands &al + // editor.applyStyle(new CKEDITOR.style({element: "span",styles: {color: "#(color)"},overrides: [{element: "font",attributes: {color: null}}]}, {color: '#ff0000'})); + start: function () { + this.$el.hide(); + + return $.when.apply( + null, _(this.commands).map(this.proxy('start_command'))); + }, + init_command: function (command) { + var type = command[0], args = command.slice(1); + args.unshift(this); + var F = function (args) { + return type.apply(this, args); + }; + F.prototype = type.prototype; + + return new F(args); + }, + start_command: function (command) { + return this.init_command(command).appendTo(this.$el); + }, + start_edition: function ($elements) { + var self = this; + this.$el.show(); + this.disable(); + CKEDITOR.on('currentInstance', this.proxy('_change_focused_editor')); + $elements + .not('span, [data-oe-type]') + .each(function () { + var $this = $(this); + CKEDITOR.inline(this, self._config()).on('change', function () { + $this.addClass('oe_dirty'); + self.trigger('change', this, null); + }); + }); + }, + + /** + * @param {Function} fn + * @param {Boolean} [snapshot=true] + * @returns {$.Deferred} + */ + with_editor: function (fn, snapshot) { + var editor = this._current_editor(); + if (snapshot !== false) { editor.fire('saveSnapshot'); } + return $.when(fn(editor)).then(function () { + if (snapshot !== false) { editor.fire('saveSnapshot'); } + editor.focus(); + }); + }, + + + toggle: function (to) { + _(this.getChildren()).chain() + .filter(function (child) { return child instanceof instance.website.Action }) + .invoke('toggle', to); + }, + disable: function () { + this.toggle(false); + }, + + _current_editor: function () { + return CKEDITOR.currentInstance; + }, + _change_focused_editor: function () { + this.toggle(!!CKEDITOR.currentInstance); + }, + _config: function () { + return { + // Don't load ckeditor's style rules + stylesSet: [], + // Remove toolbar entirely + removePlugins: 'toolbar,elementspath,resize', + uiColor: '', + // Ensure no config file is loaded + customConfig: '', + // Disable ACF + allowedContent: true, + }; + }, +}); + $(function(){ function make_static(){ diff --git a/addons/website/static/src/xml/website.xml b/addons/website/static/src/xml/website.xml index 1754b7b81b6..e65565d6a96 100644 --- a/addons/website/static/src/xml/website.xml +++ b/addons/website/static/src/xml/website.xml @@ -4,11 +4,18 @@
      -
    • OpenERP Website Editor
    • -
    • -
    • -
    • -
    • +
    • +
    • +
    • +
    + + +
    diff --git a/addons/website/views/views.xml b/addons/website/views/views.xml index 1f57be9857c..0ab628b93c3 100644 --- a/addons/website/views/views.xml +++ b/addons/website/views/views.xml @@ -37,7 +37,7 @@
  • -
    Company name
    +
    Company name