[ADD] inline edition plugin
* No feature on plugin itself save marking field zones - TODO: handling of specific field types * ckeditor 4.3 (?) broke sharedspace plugin * symlinked ckeditor, to fix * new save API (by view section) bzr revid: xmo@openerp.com-20130812093314-ur8l7jjzf40fwlxy
This commit is contained in:
parent
9d929fc49d
commit
fc8321f103
|
@ -0,0 +1 @@
|
|||
/Users/masklinn/projects/thirdparty/ckeditor-dev
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* @license Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
var containerTpl = CKEDITOR.addTemplate( 'sharedcontainer', '<div' +
|
||||
' id="cke_{name}"' +
|
||||
' class="cke {id} cke_reset_all cke_chrome cke_editor_{name} cke_shared cke_detached cke_{langDir} ' + CKEDITOR.env.cssClass + '"' +
|
||||
' dir="{langDir}"' +
|
||||
' title="' + ( CKEDITOR.env.gecko ? ' ' : '' ) + '"' +
|
||||
' lang="{langCode}"' +
|
||||
' role="presentation"' +
|
||||
'>' +
|
||||
'<div class="cke_inner">' +
|
||||
'<div id="{spaceId}" class="cke_{space}" role="presentation">{content}</div>' +
|
||||
'</div>' +
|
||||
'</div>' );
|
||||
|
||||
CKEDITOR.plugins.add( 'sharedspace', {
|
||||
afterInit: function( editor ) {
|
||||
var spaces = editor.config.sharedSpaces;
|
||||
|
||||
if ( spaces ) {
|
||||
for ( var spaceName in spaces ) {
|
||||
create( editor, spaceName, spaces[ spaceName ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function create( editor, spaceName, targetId ) {
|
||||
var target = CKEDITOR.document.getById( targetId ),
|
||||
innerHtml, space;
|
||||
|
||||
if ( target ) {
|
||||
// Have other plugins filling the space.
|
||||
innerHtml = editor.fire( 'uiSpace', { space: spaceName, html: '' } ).html;
|
||||
|
||||
if ( innerHtml ) {
|
||||
// Block the uiSpace handling by others (e.g. themed-ui).
|
||||
editor.on( 'uiSpace', function( ev ) {
|
||||
if ( ev.data.space == spaceName )
|
||||
ev.cancel();
|
||||
}, null, null, 1 ); // Hi-priority
|
||||
|
||||
// Inject the space into the target.
|
||||
space = target.append( CKEDITOR.dom.element.createFromHtml( containerTpl.output({
|
||||
id: editor.id,
|
||||
name: editor.name,
|
||||
langDir: editor.lang.dir,
|
||||
langCode: editor.langCode,
|
||||
space: spaceName,
|
||||
spaceId: editor.ui.spaceId( spaceName ),
|
||||
content: innerHtml
|
||||
})));
|
||||
|
||||
// Only the first container starts visible. Others get hidden.
|
||||
if ( target.getCustomData( 'cke_hasshared' ) )
|
||||
space.hide();
|
||||
else
|
||||
target.setCustomData( 'cke_hasshared', 1 );
|
||||
|
||||
// There's no need for the space to be selectable.
|
||||
space.unselectable();
|
||||
|
||||
// Prevent clicking on non-buttons area of the space from blurring editor.
|
||||
space.on( 'mousedown', function( evt ) {
|
||||
evt = evt.data;
|
||||
if ( !evt.getTarget().hasAscendant( 'a', 1 ) )
|
||||
evt.preventDefault();
|
||||
});
|
||||
|
||||
// Register this UI space to the focus manager.
|
||||
editor.focusManager.add( space, 1 );
|
||||
|
||||
// When the editor gets focus, show the space container, hiding others.
|
||||
editor.on( 'focus', function() {
|
||||
for ( var i = 0, sibling, children = target.getChildren(); ( sibling = children.getItem( i ) ); i++ ) {
|
||||
if ( sibling.type == CKEDITOR.NODE_ELEMENT &&
|
||||
!sibling.equals( space ) &&
|
||||
sibling.hasClass( 'cke_shared' ) ) {
|
||||
sibling.hide();
|
||||
}
|
||||
}
|
||||
|
||||
space.show();
|
||||
});
|
||||
|
||||
editor.on( 'destroy', function() {
|
||||
space.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Makes it possible to place some of the editor UI blocks, like the toolbar
|
||||
* and the elements path, into any element in the page.
|
||||
*
|
||||
* The elements used to hold the UI blocks can be shared among several editor
|
||||
* instances. In that case, only the blocks of the active editor instance will
|
||||
* display.
|
||||
*
|
||||
* // Place the toolbar inside the element with ID "someElementId" and the
|
||||
* // elements path into the element with ID "anotherId".
|
||||
* config.sharedSpaces = {
|
||||
* top: 'someElementId',
|
||||
* bottom: 'anotherId'
|
||||
* };
|
||||
*
|
||||
* // Place the toolbar inside the element with ID "someElementId". The
|
||||
* // elements path will remain attached to the editor UI.
|
||||
* config.sharedSpaces = {
|
||||
* top: 'someElementId'
|
||||
* };
|
||||
*
|
||||
* @cfg {Object} [sharedSpaces]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
|
@ -52,76 +52,59 @@ instance.website.EditorBar = instance.web.Widget.extend({
|
|||
},
|
||||
edit: function () {
|
||||
this.$buttons.edit.prop('disabled', true).parent().hide();
|
||||
this.$buttons.cancel.add(this.$buttons.snippet).prop('disabled', false)
|
||||
this.$buttons.cancel.add(this.$buttons.snippet)
|
||||
//.prop('disabled', false)
|
||||
.add(this.$buttons.save)
|
||||
.prop('disabled', false)
|
||||
.parent().show();
|
||||
// TODO: span edition changing edition state (save button)
|
||||
var $editables = $('[data-oe-model]')
|
||||
.not('link, script')
|
||||
.filter('div, p, li, section, header, footer')
|
||||
.filter('[data-oe-xpath]')
|
||||
.not('[data-oe-type]')
|
||||
// FIXME: propagation should make "meta" blocks non-editable in the first place...
|
||||
.not('.oe_snippet_editor')
|
||||
.prop('contentEditable', true)
|
||||
.addClass('oe_editable');
|
||||
var $rte_ables = $editables.filter('div, p, li, section, header, footer').not('[data-oe-type]');
|
||||
var $raw_editables = $editables.not($rte_ables);
|
||||
|
||||
// temporary fix until we fix ckeditor
|
||||
$raw_editables.each(function () {
|
||||
$(this).parents().add($(this).find('*')).on('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
});
|
||||
|
||||
this.rte.start_edition($rte_ables);
|
||||
$raw_editables.on('keydown keypress cut paste', function (e) {
|
||||
var $target = $(e.target);
|
||||
if ($target.hasClass('oe_dirty')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target.addClass('oe_dirty');
|
||||
this.$buttons.save.prop('disabled', false);
|
||||
}.bind(this));
|
||||
this.rte.start_edition($editables);
|
||||
},
|
||||
rte_changed: function () {
|
||||
this.$buttons.save.prop('disabled', false);
|
||||
},
|
||||
save: function () {
|
||||
var self = this;
|
||||
var defs = [];
|
||||
$('.oe_dirty').each(function (i, v) {
|
||||
var $el = $(this);
|
||||
// TODO: Add a queue with concurrency limit in webclient
|
||||
// https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js
|
||||
var def = self.saving_mutex.exec(function () {
|
||||
return self.saveElement($el).then(function () {
|
||||
$el.removeClass('oe_dirty');
|
||||
}).fail(function () {
|
||||
var data = $el.data();
|
||||
console.error(_.str.sprintf('Could not save %s#%d#%s', data.oeModel, data.oeId, data.oeField));
|
||||
|
||||
var defs = _(CKEDITOR.instances).chain()
|
||||
.filter(function (editor) { return editor.checkDirty(); })
|
||||
.map(function (editor) {
|
||||
console.log('Saving', editor);
|
||||
// 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 () {
|
||||
return self.saveEditor(editor)
|
||||
.fail(function () {
|
||||
var data = $el.data();
|
||||
console.error(_.str.sprintf('Could not save %s(%d).%s', data.oeModel, data.oeId, data.oeField));
|
||||
});
|
||||
});
|
||||
});
|
||||
defs.push(def);
|
||||
});
|
||||
}).value();
|
||||
return $.when.apply(null, defs).then(function () {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
saveElement: function ($el) {
|
||||
var data = $el.data();
|
||||
var html = $el.html();
|
||||
var xpath = data.oeXpath;
|
||||
if (xpath) {
|
||||
var $w = $el.clone();
|
||||
$w.removeClass('oe_dirty');
|
||||
_.each(['model', 'id', 'field', 'xpath'], function(d) {$w.removeAttr('data-oe-' + d);});
|
||||
$w
|
||||
.removeClass('oe_editable')
|
||||
.prop('contentEditable', false);
|
||||
html = $w.wrap('<div>').parent().html();
|
||||
}
|
||||
return (new instance.web.DataSet(this, 'ir.ui.view')).call('save', [data.oeModel, data.oeId, data.oeField, html, xpath]);
|
||||
/**
|
||||
* Saves an RTE content, which always corresponds to a view section (?).
|
||||
*
|
||||
*
|
||||
*/
|
||||
saveEditor: function (editor) {
|
||||
var element = editor.element;
|
||||
var data = editor.getData();
|
||||
return new instance.web.Model('ir.ui.view').call('save', {
|
||||
res_id: element.data('oe-id'),
|
||||
xpath: element.data('oe-xpath'),
|
||||
value: data,
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
window.location.reload();
|
||||
|
@ -170,11 +153,10 @@ instance.website.RTE = instance.web.Widget.extend({
|
|||
var self = this;
|
||||
this.snippet_carousel();
|
||||
$elements
|
||||
.not('span, [data-oe-type]')
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
CKEDITOR.inline(this, self._config()).on('change', function () {
|
||||
$this.addClass('oe_dirty');
|
||||
var editor = CKEDITOR.inline(this, self._config());
|
||||
editor.on('change', function () {
|
||||
self.trigger('change', this, null);
|
||||
});
|
||||
});
|
||||
|
@ -201,7 +183,7 @@ instance.website.RTE = instance.web.Widget.extend({
|
|||
autoParagraph: false,
|
||||
filebrowserImageUploadUrl: "/website/attach",
|
||||
// Support for sharedSpaces in 4.x
|
||||
extraPlugins: 'sharedspace',
|
||||
extraPlugins: 'sharedspace,oeref',
|
||||
// Place toolbar in controlled location
|
||||
sharedSpaces: { top: 'oe_rte_toolbar' },
|
||||
toolbar: [
|
||||
|
@ -343,4 +325,35 @@ instance.web.GoToWebsite = function(parent, action) {
|
|||
};
|
||||
instance.web.client_actions.add("website.gotowebsite", "instance.web.GoToWebsite");
|
||||
|
||||
if (!window.CKEDITOR) { return; }
|
||||
|
||||
CKEDITOR.plugins.add('oeref', {
|
||||
requires: 'widget',
|
||||
|
||||
init: function (editor) {
|
||||
editor.widgets.add('oeref', {
|
||||
inline: true,
|
||||
// dialog: 'oeref',
|
||||
allowedContent: '[data-oe-type]',
|
||||
editables: { text: '*' },
|
||||
|
||||
init: function () {
|
||||
var element = this.element;
|
||||
this.setData({
|
||||
model: element.data('oe-model'),
|
||||
id: parseInt(element.data('oe-id'), 10),
|
||||
field: element.data('oe-field'),
|
||||
});
|
||||
},
|
||||
data: function () {
|
||||
this.element.data('oe-model', this.data.model);
|
||||
this.element.data('oe-id', this.data.id);
|
||||
this.element.data('oe-field', this.data.field);
|
||||
},
|
||||
upcast: function (el) {
|
||||
return el.attributes['data-oe-type'];
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="Website.EditorBar">
|
||||
<ul class="oe_website_editorbar openerp">
|
||||
<li><button data-action="edit">Edit</button></li>
|
||||
<li><button data-action="save">Save</button></li>
|
||||
<li><button data-action="cancel">Cancel</button></li>
|
||||
<li class="oe_right"><button data-action="snippet">Building Blocks</button></li>
|
||||
<li><button data-action="edit" type="button">Edit</button></li>
|
||||
<li><button data-action="save" type="button">Save</button></li>
|
||||
<li><button data-action="cancel" type="button">Cancel</button></li>
|
||||
<li class="oe_right"><button data-action="snippet" type="button"
|
||||
>Building Blocks</button></li>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-name="Website.ActionGroup">
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<head>
|
||||
<title t-raw="title"><t t-esc="res_company.name"/></title>
|
||||
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/ckeditor/4.2/ckeditor.js"></script>
|
||||
<script type="text/javascript" src="/website/static/lib/ckeditor/ckeditor.js"></script>
|
||||
<script type="text/javascript" src="/website/static/lib/ckeditor.sharedspace/plugin.js"></script>
|
||||
<script type="text/javascript">
|
||||
CKEDITOR.disableAutoInline = true;
|
||||
|
|
Loading…
Reference in New Issue