[IMP] web_editor: add document into the media editor

User can upload documents and add the link in the page to download it.
This commit is contained in:
Christophe Matthieu
2015-08-21 22:06:52 +02:00
committed by Antony Lesuisse
parent 00adbd36ff
commit 41e730cbfa
9 changed files with 152 additions and 136 deletions
+15 -13
View File
@@ -147,30 +147,32 @@ class Web_Editor(http.Controller):
'url': url,
'res_model': 'ir.ui.view',
}, request.context)
uploads.append({'id': attachment_id})
uploads += Attachments.read(request.cr, request.uid, [attachment_id], ['name', 'mimetype', 'checksum', 'url'], request.context)
else: # images provided
try:
attachment_ids = []
for c_file in request.httprequest.files.getlist('upload'):
image_data = c_file.read()
image = Image.open(cStringIO.StringIO(image_data))
w, h = image.size
if w*h > 42e6: # Nokia Lumia 1020 photo resolution
raise ValueError(
u"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel")
if not disable_optimization and image.format in ('PNG', 'JPEG'):
image_data = tools.image_save_for_web(image)
data = c_file.read()
try:
image = Image.open(cStringIO.StringIO(data))
w, h = image.size
if w*h > 42e6: # Nokia Lumia 1020 photo resolution
raise ValueError(
u"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel")
if not disable_optimization and image.format in ('PNG', 'JPEG'):
data = tools.image_save_for_web(image)
except IOError, e:
pass
attachment_id = Attachments.create(request.cr, request.uid, {
'name': c_file.filename,
'datas': image_data.encode('base64'),
'datas': data.encode('base64'),
'datas_fname': c_file.filename,
'res_model': 'ir.ui.view',
}, request.context)
attachment_ids.append(attachment_id)
uploads = [{'id': id} for id in attachment_ids]
uploads += Attachments.read(request.cr, request.uid, attachment_ids, ['name', 'mimetype', 'checksum', 'url'], request.context)
except Exception, e:
logger.exception("Failed to upload image to attachment")
message = unicode(e)
+21 -3
View File
@@ -694,13 +694,31 @@
.note-image-dialog .existing-attachments .existing-attachment-cell {
position: relative;
}
.note-image-dialog .existing-attachments .existing-attachment-cell .img {
.note-image-dialog .existing-attachments .existing-attachment-cell .o_attachment_border {
border: 1px solid #848490;
text-align: center;
}
.note-image-dialog .existing-attachments .existing-attachment-cell .o_image {
max-width: 100%;
padding-top: 80%;
max-height: 100%;
display: inline-block;
}
.note-image-dialog .existing-attachments .existing-attachment-cell .o_image img {
width: 100%;
}
.note-image-dialog .existing-attachments .existing-attachment-cell .o_webimage {
background-image: none;
padding-top: 0;
width: 100%;
height: initial;
vertical-align: middle;
}
.note-image-dialog .existing-attachments .existing-attachment-cell .existing-attachment-remove {
position: absolute;
top: 0;
left: 15px;
z-index: 1;
cursor: pointer;
background: white;
padding: 2px;
@@ -711,7 +729,7 @@
-webkit-border-bottom-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.note-image-dialog .existing-attachments .existing-attachment-cell.media_selected > i, .note-image-dialog .existing-attachments .existing-attachment-cell.media_selected > img {
.note-image-dialog .existing-attachments .existing-attachment-cell.media_selected > * {
border-width: 5px;
border-color: #00f8f8;
}
@@ -1018,7 +1036,7 @@ html[lang] > body.editor_enable [data-oe-translation-state]:hover {
cursor: text;
}
html[lang] > body.editor_enable [data-oe-translation-state="translated"] {
background: rgba(120, 215, 110, 0.5) !important
background: rgba(120, 215, 110, 0.5) !important;
}
html[data-translatable] .oe_field_translate {
+17 -3
View File
@@ -601,13 +601,27 @@ $attachment-border-color: #848490
.existing-attachment-cell
position: relative
.img
.o_attachment_border
border: 1px solid $attachment-border-color
text-align: center
.o_image
max-width: 100%
padding-top: 80%
max-height: 100%
display: inline-block
img
width: 100%
.o_webimage
background-image: none
padding-top: 0
width: 100%
height: initial
vertical-align: middle
.existing-attachment-remove
position: absolute
top: 0
left: 15px // padding-left on col-*
z-index: 1
cursor: pointer
background: white
padding: 2px
@@ -616,7 +630,7 @@ $attachment-border-color: #848490
border-left: none
+border-bottom-right-radius(8px)
&.media_selected
> i, > img
> *
border-width: 5px
border-color: rgb(0, 248, 248)
@@ -398,7 +398,7 @@ dom.isVoid = function (node) {
return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase()) || dom.isImg(node);
};
dom.isImg = function (node) {
return dom.isImgFont(node) || (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)media_iframe_video(\s|$)/i)) ));
return dom.isImgFont(node) || (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image)(\s|$)/i)) ));
};
dom.isForbiddenNode = function (node) {
return $(node).is(".media_iframe_video, .fa, img");
@@ -609,7 +609,7 @@ eventHandler.attach = function (oLayoutInfo, options) {
$(document).on('mousedown', summernote_mousedown);
$(document).on('mouseup', summernote_mouseup);
oLayoutInfo.editor.off('click').on('click', function (e) {e.preventDefault();}); // if the content editable is a link
oLayoutInfo.editor.on('dblclick', 'img, .media_iframe_video, span.fa, i.fa, span.fa', function (event) {
oLayoutInfo.editor.on('dblclick', 'img, .media_iframe_video, span.fa, i.fa, span.fa, a.o_image', function (event) {
if (!$(event.target).closest(".note-toolbar").length) { // prevent icon edition of top bar for default summernote
new widgets.MediaDialog(oLayoutInfo.editor, event.target).appendTo(document.body);
}
+91 -58
View File
@@ -101,15 +101,17 @@ var MediaDialog = Dialog.extend({
this.only_images = this.options.only_images || this.options.select_images || (this.media && $(this.media).parent().data("oe-field") === "image");
if (this.only_images) {
this.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
this.$('[href="#editor-media-document"], [href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
}
if (this.media) {
if (this.media.nodeName === "IMG") {
this.$('[href="#editor-media-image"]').tab('show');
} else if ($(this.media).is('a.o_image')) {
this.$('[href="#editor-media-document"]').tab('show');
} else if (this.media.className.match(/(^|\s)media_iframe_video($|\s)/)) {
this.$('[href="#editor-media-video"]').tab('show');
} else if (this.media.parentNode.className.match(/(^|\s)media_iframe_video($|\s)/)) {
} else if (this.media.parentNode.className.match(/(^|\s)media_iframe_video($|\s)/)) {
this.media = this.media.parentNode;
this.$('[href="#editor-media-video"]').tab('show');
} else if (this.media.className.match(/(^|\s)fa($|\s)/)) {
@@ -119,6 +121,8 @@ var MediaDialog = Dialog.extend({
this.imageDialog = new ImageDialog(this, this.media, this.options);
this.imageDialog.appendTo(this.$("#editor-media-image"));
this.documentDialog = new ImageDialog(this, this.media, _.extend({'document': true}, this.options));
this.documentDialog.appendTo(this.$("#editor-media-document"));
this.iconDialog = new fontIconsDialog(this, this.media, this.options);
this.iconDialog.appendTo(this.$("#editor-media-icon"));
this.videoDialog = new VideoDialog(this, this.media, this.options);
@@ -130,6 +134,9 @@ var MediaDialog = Dialog.extend({
if ($(event.target).is('[href="#editor-media-image"]')) {
self.active = self.imageDialog;
self.$('li.search, li.previous, li.next').removeClass("hidden");
} if ($(event.target).is('[href="#editor-media-document"]')) {
self.active = self.documentDialog;
self.$('li.search, li.previous, li.next').removeClass("hidden");
} else if ($(event.target).is('[href="#editor-media-icon"]')) {
self.active = self.iconDialog;
self.$('li.search, li.previous, li.next').removeClass("hidden");
@@ -159,6 +166,9 @@ var MediaDialog = Dialog.extend({
if (this.active !== this.imageDialog) {
this.imageDialog.clear();
}
if (this.active !== this.documentDialog) {
this.documentDialog.clear();
}
if (this.active !== this.iconDialog) {
this.iconDialog.clear();
}
@@ -173,23 +183,24 @@ var MediaDialog = Dialog.extend({
this.active.save();
if (this.active.add_class) {
$(this.media).addClass(this.active.add_class);
$(this.active.media).addClass(this.active.add_class);
}
var media = this.active.media;
$(document.body).trigger("media-saved", [self.active.media, self.old_media]);
self.trigger("saved", [self.active.media, self.old_media]);
$(document.body).trigger("media-saved", [media, self.old_media]);
self.trigger("saved", [media, self.old_media]);
setTimeout(function () {
if (!self.active.media.parentNode) {
if (!media.parentNode) {
return;
}
range.createFromNode(self.active.media).select();
click_event(self.active.media, "mousedown");
range.createFromNode(media).select();
click_event(media, "mousedown");
if (!this.only_images) {
setTimeout(function () {
if($(self.active.media).parent().data("oe-field") !== "image") {
click_event(self.active.media, "click");
if($(media).parent().data("oe-field") !== "image") {
click_event(media, "click");
}
click_event(self.active.media, "mouseup");
click_event(media, "mouseup");
},0);
}
},0);
@@ -238,12 +249,14 @@ var ImageDialog = Widget.extend({
'change input.url': "change_input",
'keyup input.url': "change_input",
//'change select.image-style': 'preview_image',
'click .existing-attachments img': 'select_existing',
'click .existing-attachments [data-src]': 'select_existing',
'click .existing-attachment-remove': 'try_remove',
}),
init: function (parent, media, options) {
this._super();
this.options = options || {};
this.accept = this.options.accept || this.options.document ? "*/*" : "image/*";
this.domain = this.options.domain || ['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]];
this.parent = parent;
this.media = media;
this.images = [];
@@ -254,13 +267,12 @@ var ImageDialog = Widget.extend({
var self = this;
var res = this._super();
var o = { url: null, alt: null };
// avoid typos, prevent addition of new properties to the object
Object.preventExtensions(o);
if ($(this.media).is("img")) {
o.url = this.media.getAttribute('src');
} else {
this.add_class = "img-responsive pull-left";
} else if ($(this.media).is("a.o_image")) {
o.url = this.media.getAttribute('href').replace(/[?].*/, '');
o.id = +o.url.match(/\/web\/content\/([0-9]*)/, '')[1];
}
this.parent.$(".pager > li").click(function (e) {
e.preventDefault();
@@ -271,13 +283,14 @@ var ImageDialog = Widget.extend({
self.page += $target.hasClass('previous') ? -1 : 1;
self.display_attachments();
});
this.set_image(o.url, o.alt);
this.fetch_existing();
this.fetch_existing().then(function () {
self.set_image(_.find(self.records, function (record) { return record.url === o.url;}) || o);
});
return res;
},
push: function (url, alt, id) {
push: function (attachment) {
if (this.options.select_images) {
var img = _.select(this.images, function (v) { return v.url == url;});
var img = _.select(this.images, function (v) { return v.id == attachment.id;});
if (img.length) {
this.images.splice(this.images.indexOf(img[0]),1);
return;
@@ -285,7 +298,7 @@ var ImageDialog = Widget.extend({
} else {
this.images = [];
}
this.images.push({'url': url, 'alt': alt, 'id': id});
this.images.push(attachment);
},
save: function () {
if (this.options.select_images) {
@@ -294,21 +307,33 @@ var ImageDialog = Widget.extend({
}
this.parent.trigger("save", this.media);
var img = this.images[0] || {
'url': this.$(".existing-attachments img:first").attr('src'),
'alt': this.$(".existing-attachments img:first").attr('alt')
};
if (this.media.tagName !== "IMG") {
var media = document.createElement('img');
$(this.media).replaceWith(media);
this.media = media;
var img = this.images[0];
if (!img) {
var id = this.$(".existing-attachments [data-src]:first").data('id');
img = _.find(this.images, function (img) { return img.id === id;});
}
$(this.media).attr('src', img.url).attr('alt', img.alt);
if (!img.is_document) {
if(this.media.tagName !== "IMG") {
var media = document.createElement('img');
$(this.media).replaceWith(media);
this.media = media;
this.add_class = "img-responsive pull-left";
}
this.media.setAttribute('src', img.src);
} else {
if (this.media.tagName !== "A") {
$('.note-control-selection').hide();
var media = document.createElement('a');
$(this.media).replaceWith(media);
this.media = media;
}
this.media.setAttribute('href', '/web/content/' + img.id + '?unique=' + img.checksum + '&download=true');
$(this.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
}
$(this.media).attr('alt', img.alt);
var style = this.style;
this.media.setAttribute('src', img.url);
if (style) { this.media.addClass(style); }
return this.media;
@@ -334,11 +359,9 @@ var ImageDialog = Widget.extend({
self.selected_existing();
});
},
set_image: function (url, alt, error) {
set_image: function (attachment, error) {
var self = this;
if (url) {
this.push(url, alt);
}
this.push(attachment);
this.$('input.url').val('');
this.fetch_existing().then(function () {
self.selected_existing();
@@ -362,11 +385,16 @@ var ImageDialog = Widget.extend({
delete window[callback];
$form.find('.well > span').remove();
$form.find('.well > div').show();
_.each(attachments, function (record) {
record.src = record.url || '/web/image/' + record.id;
record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
});
if (error || !attachments.length) {
self.file_selected(null, error || !attachments.length);
}
self.images = attachments;
for (var i=0; i<attachments.length; i++) {
self.file_selected(attachments[i]['local_url'] || '/web/image/'+attachments[i].id, error);
self.file_selected(attachments[i], error);
}
};
},
@@ -376,17 +404,16 @@ var ImageDialog = Widget.extend({
this.$('button.filepicker').removeClass('btn-danger btn-success');
this.$('form').submit();
},
file_selected: function(url, error) {
file_selected: function(attachment, error) {
var $button = this.$('button.filepicker');
if (!error) {
$button.addClass('btn-success');
} else {
url = null;
this.$('form').addClass('has-error')
.find('.help-block').text(error);
$button.addClass('btn-danger');
this.set_image(attachment, error);
}
this.set_image(url, null, error);
if (!this.options.select_images) {
// auto save and close popup
@@ -394,8 +421,7 @@ var ImageDialog = Widget.extend({
}
},
fetch_existing: function (needle) {
var domain = [['res_model', '=', 'ir.ui.view'], '|',
['mimetype', '=', false], ['mimetype', '=like', 'image/%']];
var domain = [['res_model', '=', 'ir.ui.view']].concat(this.domain);
if (needle && needle.length) {
domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]);
}
@@ -405,7 +431,7 @@ var ImageDialog = Widget.extend({
args: [],
kwargs: {
domain: domain,
fields: ['name', 'local_url'],
fields: ['name', 'mimetype', 'checksum', 'url'], // if we want to use /web/image/xxx with redirect for image url, remove 'url'
order: 'id desc',
context: base.get_context()
}
@@ -413,12 +439,15 @@ var ImageDialog = Widget.extend({
},
fetched_existing: function (records) {
this.records = records;
_.each(records, function (record) {
record.src = record.url || '/web/image/' + record.id;
record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
});
this.display_attachments();
},
display_attachments: function () {
this.$('.help-block').empty();
var self = this;
var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
var from = this.page * per_screen;
var records = this.records;
@@ -428,6 +457,9 @@ var ImageDialog = Widget.extend({
.groupBy(function (_, index) { return Math.floor(index / IMAGES_PER_ROW); })
.values()
.value();
this.$('.help-block').empty();
this.$('.existing-attachments').replaceWith(
QWeb.render(
'web_editor.dialog.image.existing.content', {rows: rows}));
@@ -435,30 +467,31 @@ var ImageDialog = Widget.extend({
.find('li.previous').toggleClass('disabled', (from === 0)).end()
.find('li.next').toggleClass('disabled', (from + per_screen >= records.length));
this.$el.find('.o_image').each(function () {
var $div = $(this);
if (/gif|jpe|jpg|png/.test($div.data('mimetype'))) {
var $img = $('<img/>').addClass('img img-responsive').attr('src', $div.data('url') || $div.data('src'));
$div.addClass('o_webimage').append($img);
}
});
this.selected_existing();
},
select_existing: function (e) {
var $img = $(e.currentTarget);
this.push($img.attr('src'), $img.attr('alt'), $img.data('id'));
var attachment = _.find(this.records, function (record) { return record.id === $img.data('id'); });
this.push(attachment);
this.selected_existing();
},
selected_existing: function () {
var self = this;
this.$('.existing-attachment-cell.media_selected').removeClass("media_selected");
var $select = this.$('.existing-attachment-cell img').filter(function () {
var $select = this.$('.existing-attachment-cell [data-src]').filter(function () {
var $img = $(this);
var url = $img.attr("src");
return !!_.select(self.images, function (v) {
if (v.url === url) {
if (!v.id) {
v.id = $img.data('id');
v.alt = $img.attr('alt');
}
return true;
}
}).length;
return !!_.find(self.images, function (v) {
return (v.url === $img.data("src") || ($img.data("url") && v.url === $img.data("url")) || v.id === $img.data("id"));
});
});
$select.parent().addClass("media_selected");
$select.closest('.existing-attachment-cell').addClass("media_selected");
return $select;
},
try_remove: function (e) {
+5 -2
View File
@@ -41,6 +41,7 @@
<div class="modal-body">
<ul class="nav nav-tabs">
<li class="active"><a href="#editor-media-image" data-toggle="tab">Image</a></li>
<li><a href="#editor-media-document" data-toggle="tab">Document</a></li>
<li><a href="#editor-media-icon" data-toggle="tab">Pictogram</a></li>
<li><a href="#editor-media-video" data-toggle="tab">Video</a></li>
<li class="search" style="float: right;">
@@ -62,6 +63,8 @@
<div class="tab-content">
<div class="tab-pane fade in active" id="editor-media-image">
</div>
<div class="tab-pane fade" id="editor-media-document">
</div>
<div class="tab-pane fade" id="editor-media-icon">
</div>
<div class="tab-pane fade" id="editor-media-video">
@@ -87,7 +90,7 @@
class="form-inline">
<div class="well">
<div class="form-group pull-left">
<input type="file" name="upload" accept="image/*" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
<input type="file" name="upload" t-att-accept="widget.accept" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
<input type="hidden" name="disable_optimization" value=""/>
<div class="btn-group">
<button type="button" class="btn btn-primary filepicker">Upload an image from your computer</button>
@@ -144,7 +147,7 @@
<div class="col-sm-2 existing-attachment-cell"
t-foreach="row" t-as="attachment">
<i class="fa fa-times existing-attachment-remove" t-att-data-id="attachment.id"/>
<img t-att-src="attachment.local_url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" class="img img-responsive"/>
<div class="o_attachment_border"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image"/></div>
</div>
</div>
</div>
+1 -1
View File
@@ -40,7 +40,7 @@
<!-- media -->
<div data-js='media'
data-selector="img, .media_iframe_video, span.fa, i.fa, .glyphicon">
data-selector="img, .media_iframe_video, span.fa, i.fa, .glyphicon, a.o_image">
<li data-edition="edition"><a>Change...</a></li>
</div>
-28
View File
@@ -370,34 +370,6 @@ ul.oe_menu_editor .disclose {
background-color: #ddd;
}
.existing-attachments .pager .disabled {
display: none;
}
.existing-attachments .existing-attachment-cell {
position: relative;
}
.existing-attachments .existing-attachment-cell .img {
border: 1px solid #848490;
}
.existing-attachments .existing-attachment-cell .existing-attachment-remove {
position: absolute;
top: 0;
left: 15px;
cursor: pointer;
background: white;
padding: 2px;
border: 1px solid #848490;
border-top: none;
border-left: none;
-moz-border-radius-bottomright: 8px;
-webkit-border-bottom-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.existing-attachments .existing-attachment-cell.media_selected > i, .existing-attachments .existing-attachment-cell.media_selected > img {
border-width: 5px;
border-color: #00f8f8;
}
.oe_carlos_danger {
outline: 1px solid red !important;
background-color: #ffd9dd !important;
-26
View File
@@ -316,32 +316,6 @@ ul.oe_menu_editor
.font-icons-selected
background-color: #ddd
$attachment-border-color: #848490
.existing-attachments
.pager .disabled
display: none
.existing-attachment-cell
position: relative
.img
border: 1px solid $attachment-border-color
.existing-attachment-remove
position: absolute
top: 0
left: 15px // padding-left on col-*
cursor: pointer
background: white
padding: 2px
border: 1px solid $attachment-border-color
border-top: none
border-left: none
+border-bottom-right-radius(8px)
&.media_selected
> i, > img
border-width: 5px
border-color: rgb(0, 248, 248)
.oe_carlos_danger
outline: 1px solid red !important
background-color: #ffd9dd !important