diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml index 34623a7c3a1..1c65928f56b 100644 --- a/addons/website/views/website_navbar_templates.xml +++ b/addons/website/views/website_navbar_templates.xml @@ -147,15 +147,15 @@
- + -

New Slide

+

New Slide Channel

-

New Channel

+

New Livechat Channel

diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 3ea51a7395e..bd8d49b2ed0 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -114,10 +114,11 @@ class WebsiteSlides(http.Controller): if search: domain += [ - '|', '|', + '|', '|', '|', ('name', 'ilike', search), ('description', 'ilike', search), - ('index_content', 'ilike', search)] + ('index_content', 'ilike', search), + ('html_content', 'ilike', search)] pager_args['search'] = search else: if category: @@ -183,6 +184,23 @@ class WebsiteSlides(http.Controller): return request.render('website_slides.home', values) + @http.route(['/slides/channel/add'], type='http', auth='user', methods=['POST'], website=True) + def slide_channel_create(self, *args, **kw): + # `tag_ids` is a string representing a list of int with coma. i.e.: '2,5,7' + # We don't want to allow user to create tags and tag groups on the fly. + tag_ids = [] + if kw.get('tag_ids'): + tag_ids = [int(item) for item in kw['tag_ids'].split(',')] + + channel = request.env['slide.channel'].create({ + 'name': kw['name'], + 'description': kw.get('description'), + 'channel_type': kw.get('channel_type', 'documentation'), + 'user_id': request.env.user.id, + 'tag_ids': [(6, 0, tag_ids)], + }) + return werkzeug.utils.redirect("/slides/%s" % (slug(channel))) + # -------------------------------------------------- # SLIDE.SLIDE CONTOLLERS # -------------------------------------------------- @@ -257,8 +275,8 @@ class WebsiteSlides(http.Controller): return {'error': 'join_done'} return success - @http.route(['/slides/dialog_preview'], type='json', auth='user', methods=['POST'], website=True) - def dialog_preview(self, **data): + @http.route(['/slides/prepare_preview'], type='json', auth='user', methods=['POST'], website=True) + def prepare_preview(self, **data): Slide = request.env['slide.slide'] document_type, document_id = Slide._find_document_data_from_url(data['url']) preview = {} @@ -279,13 +297,14 @@ class WebsiteSlides(http.Controller): def create_slide(self, *args, **post): # check the size only when we upload a file. if post.get('datas'): - file_size = len(post['datas']) * 3 / 4 # base64 + file_size = len(post['datas']) * 3 / 4 # base64 if (file_size / 1024.0 / 1024.0) > 25: return {'error': _('File is too big. File size cannot exceed 25MB')} values = dict((fname, post[fname]) for fname in [ 'name', 'url', 'tag_ids', 'slide_type', 'channel_id', 'mime_type', 'datas', 'description', 'image', 'index_content', 'website_published'] if post.get(fname)) + if post.get('category_id'): if post['category_id'][0] == 0: values['category_id'] = request.env['slide.category'].create({ @@ -317,7 +336,19 @@ class WebsiteSlides(http.Controller): except Exception as e: _logger.error(e) return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s') % e} - return {'url': "/slides/slide/%s" % (slide.id)} + + redirect_url = "/slides/slide/%s" % (slide.id) + if slide.slide_type == 'webpage': + redirect_url += "?enable_editor=1" + return {'url': redirect_url} + + @http.route(['/slides/channel/tag/search_read'], type='json', auth='user', methods=['POST'], website=True) + def slide_channel_tag_search_read(self, fields, domain): + can_create = request.env['slide.channel.tag'].check_access_rights('create', raise_exception=False) + return { + 'read_results': request.env['slide.channel.tag'].search_read(domain, fields), + 'can_create': can_create, + } @http.route(['/slides/tag/search_read'], type='json', auth='user', methods=['POST'], website=True) def slide_tag_search_read(self, fields, domain): diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index c2e653411c1..53e46f6d7ee 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -67,6 +67,7 @@ class Channel(models.Model): ('documentation', 'Documentation'), ('training', 'Training')], string="Course type", default="documentation", required=True) sequence = fields.Integer(default=10, help='Display order') + user_id = fields.Many2one('res.users', string='Responsible', default=lambda self: self.env.uid) tag_ids = fields.Many2many( 'slide.channel.tag', 'slide_channel_tag_rel', 'channel_id', 'tag_id', string='Tags', help='Used to categorize and filter displayed channels/courses') @@ -90,6 +91,7 @@ class Channel(models.Model): nbr_documents = fields.Integer('Number of Documents', compute='_compute_slides_statistics', store=True) nbr_videos = fields.Integer('Number of Videos', compute='_compute_slides_statistics', store=True) nbr_infographics = fields.Integer('Number of Infographics', compute='_compute_slides_statistics', store=True) + nbr_webpages = fields.Integer("Number of Webpages", compute='_compute_slides_statistics', store=True) total_slides = fields.Integer('# Slides', compute='_compute_slides_statistics', store=True, oldname='total') total_views = fields.Integer('# Views', compute='_compute_slides_statistics', store=True) total_votes = fields.Integer('# Votes', compute='_compute_slides_statistics', store=True) @@ -149,7 +151,7 @@ class Channel(models.Model): 'slide_ids.likes', 'slide_ids.dislikes', 'slide_ids.total_views') def _compute_slides_statistics(self): result = dict.fromkeys(self.ids, dict( - nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, + nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0, total_slides=0, total_views=0, total_votes=0, total_time=0)) read_group_res = self.env['slide.slide'].read_group( [('is_published', '=', True), ('channel_id', 'in', self.ids)], @@ -162,6 +164,7 @@ class Channel(models.Model): result[cid]['nbr_documents'] += res_group.get('slide_type', '') == 'document' and res_group['__count'] or 0 result[cid]['nbr_videos'] += res_group.get('slide_type', '') == 'video' and res_group['__count'] or 0 result[cid]['nbr_infographics'] += res_group.get('slide_type', '') == 'infographic' and res_group['__count'] or 0 + result[cid]['nbr_webpages'] += res_group.get('slide_type', '') == 'webpage' and res_group['__count'] or 0 result[cid]['total_slides'] += res_group['__count'] result[cid]['total_views'] += res_group.get('total_views', 0) result[cid]['total_votes'] += res_group.get('likes', 0) @@ -363,6 +366,7 @@ class Category(models.Model): nbr_documents = fields.Integer("Number of Documents", compute='_count_presentations', store=True) nbr_videos = fields.Integer("Number of Videos", compute='_count_presentations', store=True) nbr_infographics = fields.Integer("Number of Infographics", compute='_count_presentations', store=True) + nbr_webpages = fields.Integer("Number of Webpages", compute='_count_presentations', store=True) total_slides = fields.Integer(compute='_count_presentations', store=True, oldname='total') @api.depends('slide_ids.slide_type', 'slide_ids.is_published') @@ -379,4 +383,5 @@ class Category(models.Model): record.nbr_documents = result[record.id].get('document', 0) record.nbr_videos = result[record.id].get('video', 0) record.nbr_infographics = result[record.id].get('infographic', 0) - record.total_slides = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics + record.nbr_webpages = result[record.id].get('webpage', 0) + record.total_slides = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics + record.nbr_webpages diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index 908513c5da8..4b85660885b 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -31,6 +31,15 @@ class SlidePartnerRelation(models.Model): completed = fields.Boolean('Completed') +class SlideLink(models.Model): + _name = 'slide.slide.link' + _description = "External URL for a particular slide" + + slide_id = fields.Many2one('slide.slide', required=True) + name = fields.Char('Title', required=True) + link = fields.Char("External Link", required=True) + + class EmbeddedSlide(models.Model): """ Embedding in third party websites. Track view count, generate statistics. """ _name = 'slide.embed' @@ -74,6 +83,7 @@ class Slide(models.Model): - Document - Infographic - Video + - Webpage Slide has various statistics like view count, embed count, like, dislikes """ @@ -93,7 +103,7 @@ class Slide(models.Model): 'website_published', 'website_url', 'website_meta_title', 'website_meta_description', 'website_meta_keywords', 'website_meta_og_img'] _sql_constraints = [ - ('name_uniq', 'UNIQUE(channel_id, name)', 'The slide name must be unique within a channel') + ('exclusion_html_content_and_url', "CHECK(html_content IS NULL OR url IS NULL)", "A slide is either filled with a document url or HTML content. Not both.") ] # description @@ -122,15 +132,18 @@ class Slide(models.Model): ('infographic', 'Infographic'), ('presentation', 'Presentation'), ('document', 'Document'), + ('webpage', 'Web Page'), ('video', 'Video')], string='Type', required=True, - default='document', + default='document', readonly=True, help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).") index_content = fields.Text('Transcript') datas = fields.Binary('Content', attachment=True) url = fields.Char('Document URL', help="Youtube or Google Document URL") document_id = fields.Char('Document ID', help="Youtube or Google Document ID") + link_ids = fields.One2many('slide.slide.link', 'slide_id', string="External URL for this slide") mime_type = fields.Char('Mime-type') + html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True) # website website_id = fields.Many2one(related='channel_id.website_id', readonly=True) date_published = fields.Datetime('Publish Date') diff --git a/addons/website_slides/security/ir.model.access.csv b/addons/website_slides/security/ir.model.access.csv index 6f12b3236c7..9dbfa916ff6 100644 --- a/addons/website_slides/security/ir.model.access.csv +++ b/addons/website_slides/security/ir.model.access.csv @@ -17,3 +17,5 @@ access_slide_category_all,slide.category.all,model_slide_category,,1,0,0,0 access_slide_category_publisher,slide.category.publisher,model_slide_category,website.group_website_publisher,1,1,1,1 access_slide_embed_all,slide.embed.all,model_slide_embed,,1,0,0,0 access_slide_embed_user,slide.embed.user,model_slide_embed,base.group_user,1,1,1,1 +access_slide_slide_link_all,slide.slide.link.all,model_slide_slide_link,,1,0,0,0 +access_slide_slide_link_publisher,slide.slide.link.publisher,model_slide_slide_link,website.group_website_publisher,1,1,1,1 \ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js index 30dc8c9547a..3fbc7b94ab4 100644 --- a/addons/website_slides/static/src/js/slides_upload.js +++ b/addons/website_slides/static/src/js/slides_upload.js @@ -1,50 +1,51 @@ -odoo.define('website_slides.upload', function (require) { +odoo.define('website_slides.upload_modal', function (require) { 'use strict'; var sAnimations = require('website.content.snippets.animation'); var core = require('web.core'); -var Widget = require('web.Widget'); +var Dialog = require('web.Dialog'); +var QWeb = core.qweb; var _t = core._t; -var SlideDialog = Widget.extend({ - template: 'website.slide.upload', - events: { - 'hidden.bs.modal': 'destroy', - 'click button.save': '_save', - 'click button[data-dismiss="modal"]': '_cancel', - 'change input#upload': '_slideUpload', - 'change input#url': '_slideUrl', - 'click .list-group-item': function (ev) { - this.$('.list-group-item').removeClass('active'); - $(ev.target).closest('li').addClass('active'); - } - }, +var SlideUploadDialog = Dialog.extend({ + template: 'website.slide.upload.modal', + events: _.extend({}, Dialog.prototype.events, { + 'click .o_wslides_select_type': '_onClickSlideTypeIcon', + 'change input#upload': '_onChangeSlideUpload', + 'change input#url': '_onChangeSlideUrl', + }), /** * @override - * @param {Object} el - * @param {Object} data holding channelId and optionally upload and publish control parameters + * @param {Object} parent + * @param {Object} options holding channelId and optionally upload and publish control parameters */ - init: function (el, data) { - this._super(el, data); - this.channelId = parseInt(data.channelId, 10); - this.canUpload = data.canUpload === 'True'; - this.canPublish = data.canPublish === 'True'; - console.log(this.channelId, this.canUpload, this.canPublish); - this.file = {}; - this.index_content = ''; - }, - /** - * @override - */ - start: function () { - this.$el.modal({ - backdrop: 'static' + init: function (parent, options) { + options = _.defaults(options || {}, { + title: _t("Upload a document"), + size: 'medium', + }); + this._super(parent, options); + this._setup(); + + this.channelID = parseInt(options.channelId, 10); + this.canUpload = options.canUpload === 'True'; + this.canPublish = options.canPublish === 'True'; + + this.set('state', '_select'); + this.on('change:state', this, this._onChangeType); + this.set('can_submit_form', false); + this.on('change:can_submit_form', this, this._onChangeCanSubmitForm); + + this.file = {}; + this.isValidUrl = true; + }, + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + self._resetModalButton(); }); - this._setCategoryId(); - this._setTagIds(); - return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -53,32 +54,167 @@ var SlideDialog = Widget.extend({ /** * @private - * @param {string} file_name + * @param {string} message */ - _checkUniqueSlide: function (fileName) { + _alertDisplay: function (message) { + this._alertRemove(); + $('
', { + "class": 'alert alert-warning', + role: 'alert' + }).text(message).insertBefore(this.$('form')); + }, + _alertRemove: function () { + this.$('.alert-warning').remove(); + }, + /** + * Category and tags management from select2 + * + * @private + */ + _bindSelect2Dropdown: function () { + var self = this; + this.$('#category_id').select2(this._select2Wrapper(_t('Category'), false, + function () { + return self._rpc({ + route: '/slides/category/search_read', + params: { + fields: ['name'], + domain: [['channel_id', '=', self.channelID]], + } + }); + }) + ); + this.$('#tag_ids').select2(this._select2Wrapper(_t('Tags'), true, function () { + return self._rpc({ + route: '/slides/tag/search_read', + params: { + fields: ['name'], + domain: [], + } + }); + })); + }, + _fetchUrlPreview: function (url) { return this._rpc({ - model: 'slide.slide', - method: 'search_count', - args: [[['channel_id', '=', self.channelId], ['name', '=', fileName]]], + route: '/slides/prepare_preview/', + params: { + 'url': url, + 'channel_id': this.channelID + }, }); }, + _formSetFieldValue: function (fieldId, value) { + this.$('form').find('#'+fieldId).val(value); + }, + _formGetFieldValue: function (fieldId) { + return this.$('#'+fieldId).val(); + }, + _formValidate: function () { + var form = this.$("form"); + form.addClass('was-validated'); + return form[0].checkValidity() && this.isValidUrl; + }, + /** + * Extract values to submit from form, force the slide_type according to + * filled values. + * + * @private + */ + _formValidateGetValues: function (forcePublished) { + var canvas = this.$('#data_canvas')[0]; + var values = _.extend({ + 'channel_id': this.channelID, + 'name': this._formGetFieldValue('name'), + 'url': this._formGetFieldValue('url'), + 'description': this._formGetFieldValue('description'), + 'index_content': this._formGetFieldValue('index_content'), + 'website_published': forcePublished, + }, this._getSelect2DropdownValues()); // add tags and category + + // default slide_type (for webpage for instance) + if (_.contains(this.slide_type_data), this.get('state')) { + values['slide_type'] = this.get('state'); + } + + if (this.file.type === 'application/pdf') { + _.extend(values, { + 'image': canvas.toDataURL().split(',')[1], + 'slide_type': canvas.height > canvas.width ? 'document' : 'presentation', + 'mime_type': this.file.type, + 'datas': this.file.data + }); + } else if (/^image\/.*/.test(this.file.type)) { + _.extend(values, { + 'slide_type': 'infographic', + 'mime_type': this.file.type === 'image/svg+xml' ? 'image/png' : this.file.type, + 'datas': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data + }); + } + return values; + }, /** * @private */ - _resetFile: function () { + _fileReset: function () { var control = this.$('#upload'); control.replaceWith(control = control.clone(true)); this.file.name = false; }, - /** - * @private - * @param {string} message - */ - _displayAlert: function (message) { - this.$('.alert-warning').remove(); - $('').insertBefore(this.$('form')); - }, + _getModalButtons: function () { + var btnList = []; + var state = this.get('state'); + if (state === '_select') { + btnList.push({text: _t("Cancel"), classes: 'o_w_slide_cancel', close: true}); + } else if (state !== '_upload') { // no button when uploading + if (this.canUpload) { + if (this.canPublish) { + btnList.push({text: _t("Save as Draft"), classes: 'o_w_slide_upload', click: this._onClickFormSubmit.bind(this)}); + btnList.push({text: _t("Publish"), classes: 'btn-primary o_w_slide_upload o_w_slide_upload_published', click: this._onClickFormSubmit.bind(this)}); + } else { + btnList.push({text: _t("Save as Draft"), classes: 'btn-primary o_w_slide_upload', click: this._onClickFormSubmit.bind(this)}); + } + } + btnList.push({text: _t("Go Back"), classes: 'o_w_slide_go_back', click: this._onClickGoBack.bind(this)}); + } + return btnList; + }, + /** + * Get value for category_id and tag_ids (ORM cmd) to send to server + * + * @private + */ + _getSelect2DropdownValues: function (){ + var result = {}; + // tags + var tagValues = []; + _.each(this.$('#tag_ids').select2('data'), function (val) { + if (val.create) { + tagValues.push([0, 0, {'name': val.text}]); + } else { + tagValues.push([4, val.id]); + } + }); + if (tagValues) { + result['tag_ids'] = tagValues; + } + // category + var categoryValue = this.$('#category_id').select2('data'); + if (categoryValue && categoryValue.create) { + result['category_id'] = [0, {'name': categoryValue.text}]; + } else if (categoryValue) { + result['category_id'] = [categoryValue.id]; + } + return result; + }, + /** + * Reset the footer buttons, according to current state of modal + * + * @private + */ + _resetModalButton: function () { + this.set_buttons(this._getModalButtons()); + }, /** * Wrapper for select2 load data from server at once and store it. * @@ -114,7 +250,7 @@ var SlideDialog = Widget.extend({ id: _.uniqueId('tag_'), create: true, tag: term, - text: _.str.sprintf(_t("Create new tag '%s'"), term), + text: _.str.sprintf(_t("Create new %s '%s'"), tag, term), }; } else { return undefined; @@ -147,64 +283,28 @@ var SlideDialog = Widget.extend({ }; }, /** - * Category management from select2 + * Init the data relative to the support slide type to upload * * @private */ - _setCategoryId: function () { - var self = this; - $('#category_id').select2(this._select2Wrapper(_t('Category'), false, - function () { - return self._rpc({ - route: '/slides/category/search_read', - params: { - fields: ['name'], - domain: [['channel_id', '=', self.channelId]], - } - }); - })); - }, - /** - * @private - */ - _getCategoryId: function () { - var value = $('#category_id').select2('data'); - if (value && value.create) { - return [0, {'name': value.text}]; - } - return [value ? value.id : null]; - }, - /** - * Tags management from select2 - * - * @private - */ - _setTagIds: function () { - var self = this; - $('#tag_ids').select2(this._select2Wrapper(_t('Tags'), true, function () { - return self._rpc({ - route: '/slides/tag/search_read', - params: { - fields: ['name'], - domain: [], - } - }); - })); - }, - /** - * @private - */ - _getTagIds: function () { - var res = []; - _.each($('#tag_ids').select2('data'), - function (val) { - if (val.create) { - res.push([0, 0, {'name': val.text}]); - } else { - res.push([4, val.id]); - } - }); - return res; + _setup: function () { + this.slide_type_data = { + presentation: { + icon: 'fa-file-pdf-o', + label: _t('Presentation (PDF)'), + template: 'website.slide.upload.modal.presentation', + }, + webpage: { + icon: 'fa-file-text', + label: _t('Web Page'), + template: 'website.slide.upload.modal.webpage', + }, + video: { + icon: 'fa-video-camera', + label: _t('Video'), + template: 'website.slide.upload.modal.video', + }, + }; }, /** * @private @@ -219,99 +319,35 @@ var SlideDialog = Widget.extend({ canvas.getContext('2d').drawImage(img, 0, 0); return canvas.toDataURL('image/png').split(',')[1]; }, - /** - * Values and save - * - * @private - */ - _getValue: function () { - var canvas = this.$('#data_canvas')[0], - values = { - 'channel_id': this.channelId || '', - 'name': this.$('#name').val(), - 'url': this.$('#url').val(), - 'description': this.$('#description').val(), - 'tag_ids': this._getTagIds(), - 'category_id': this._getCategoryId() - }; - if (this.file.type === 'application/pdf') { - _.extend(values, { - 'image': canvas.toDataURL().split(',')[1], - 'index_content': this.index_content, - 'slide_type': canvas.height > canvas.width ? 'document' : 'presentation', - 'mime_type': this.file.type, - 'datas': this.file.data - }); - } - if (/^image\/.*/.test(this.file.type)) { - _.extend(values, { - 'slide_type': 'infographic', - 'mime_type': this.file.type === 'image/svg+xml' ? 'image/png' : this.file.type, - 'datas': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data - }); - } - return values; - }, - /** - * @private - */ - _validate: function () { - this.$('.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid'); - if (!this.$('#name').val()) { - this.$('#name').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - return false; - } - var url = this.$('#url').val() ? this.is_valid_url : false; - if (!(this.file.name || url)) { - this.$('#url').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - return false; - } - return true; - }, - //-------------------------------------------------------------------------- - // Handlers + // Handler //-------------------------------------------------------------------------- - /** - * @override - * @param {Object} ev - */ - _save: function (ev) { - var self = this; - if (this._validate()) { - var values = this._getValue(); - if ($(ev.target).data('published')) { - values.website_published = true; - } - this.$('.oe_slides_upload_loading').show(); - this.$('.modal-footer, .modal-body').hide(); - this._rpc({ - route: '/slides/add_slide', - params: values, - }).then(function (data) { - if (data.error) { - self._displayAlert(data.error); - self.$('.oe_slides_upload_loading').hide(); - self.$('.modal-footer, .modal-body').show(); + _onChangeType: function () { + var currentType = this.get('state'); + var tmpl; + if (currentType === '_select') { + tmpl = 'website.slide.upload.modal.select'; + } else if (currentType === '_upload') { + tmpl = 'website.slide.upload.modal.uploading'; + } else { + tmpl = this.slide_type_data[currentType]['template']; + } + this.$('.o_w_slide_upload_modal_container').empty(); + this.$('.o_w_slide_upload_modal_container').append(QWeb.render(tmpl, {widget: this})); - } else { - window.location = data.url; - } - }); + this._resetModalButton(); + }, + _onChangeCanSubmitForm: function (ev) { + if (this.get('can_submit_form')) { + this.$('.o_w_slide_upload').button('reset'); + } else { + this.$('.o_w_slide_upload').button('loading'); } }, - /** - * @override - */ - _cancel: function () { - this.trigger('cancel'); - }, - /** - * @override - * @param {Object} ev - */ - _slideUpload: function (ev) { + _onChangeSlideUpload: function (ev) { + this._alertRemove(); + var self = this; var file = ev.target.files[0]; var isImage = /^image\/.*/.test(file.type); @@ -319,16 +355,15 @@ var SlideDialog = Widget.extend({ this.file.name = file.name; this.file.type = file.type; if (!(isImage || this.file.type === 'application/pdf')) { - this._displayAlert(_t("Invalid file type. Please select pdf or image file")); - this._resetFile(); + this._alertDisplay(_t("Invalid file type. Please select pdf or image file")); + this._fileReset(); return; } if (file.size / 1024 / 1024 > 25) { - this._displayAlert(_t("File is too big. File size cannot exceed 25MB")); - this._resetFile(); + this._alertDisplay(_t("File is too big. File size cannot exceed 25MB")); + this._fileReset(); return; } - this.$('.alert-warning').remove(); var BinaryReader = new FileReader(); // file read as DataURL BinaryReader.readAsDataURL(file); @@ -343,15 +378,15 @@ var SlideDialog = Widget.extend({ if (file.type === 'application/pdf') { var ArrayReader = new FileReader(); - this.$('.save').button('loading'); + this.set('can_submit_form', false); // file read as ArrayBuffer for PDFJS get_Document API ArrayReader.readAsArrayBuffer(file); ArrayReader.onload = function (evt) { var buffer = evt.target.result; var passwordNeeded = function () { - self._displayAlert(_t("You can not upload password protected file.")); - self._resetFile(); - self.$('.save').button('reset'); + self._alertDisplay(_t("You can not upload password protected file.")); + self._fileReset(); + self.set('can_submit_form', true); }; PDFJS.getDocument(new Uint8Array(buffer), null, passwordNeeded).then(function getPdf(pdf) { pdf.getPage(1).then(function getFirstPage(page) { @@ -369,10 +404,9 @@ var SlideDialog = Widget.extend({ var imageData = self.$('#data_canvas')[0].toDataURL(); self.$('#slide-image').attr('src', imageData); if (loaded) { - self.$('.save').button('reset'); + self.set('can_submit_form', true); } loaded = true; - }); }); var maxPages = pdf.pdfInfo.numPages; @@ -389,10 +423,12 @@ var SlideDialog = Widget.extend({ }); // page_content may contain null characters pageContent = pageContent.replace(/\0/g, ''); - self.index_content = self.index_content + pageNumber + '. ' + pageContent + '\n'; + var indexContent = self._formGetFieldValue('index_content') + pageNumber + '. ' + pageContent + '\n'; + self._formSetFieldValue('index_content', indexContent); + if (maxPages === pageNumber) { if (loaded) { - self.$('.save').button('reset'); + self.set('can_submit_form', false); } loaded = true; } @@ -405,48 +441,65 @@ var SlideDialog = Widget.extend({ var input = file.name; var inputVal = input.substr(0, input.lastIndexOf('.')) || input; - this._checkUniqueSlide(inputVal).then(function (exist) { - if (exist) { - var message = _t("Channel contains the given title, please change before Save or Publish."); - self._displayAlert(message); - } - self.$('#name').val(inputVal); - }); + this._formSetFieldValue('name', inputVal); }, - /** - * @private - * @param {Object} ev - */ - _slideUrl: function (ev) { - var self = this, - value = { - 'url': $(ev.target).val(), - 'channel_id': self.channelId - }; - this.$('.alert-warning').remove(); - this.is_valid_url = false; - this.$('.save').button('loading'); - this._rpc({ - route: '/slides/dialog_preview/', - params: value, - }).then(function (data) { - self.$('.save').button('reset'); + _onChangeSlideUrl: function (ev) { + var self = this; + var url = $(ev.target).val(); + + this._alertRemove(); + this.isValidUrl = false; + this.set('can_submit_form', false); + this._fetchUrlPreview(url).then(function (data) { + this.set('can_submit_form', true); if (data.error) { - self._displayAlert(data.error); + self._alertDisplay(data.error); } else { self.$('#slide-image').attr('src', data.url_src); - self.$('#name').val(data.title); - self.$('#description').val(data.description); - self.is_valid_url = true; + self._formSetFieldValue('name', data.title); + self._formSetFieldValue('description', data.description); + + self.isValidUrl = true; } }); }, + _onClickGoBack: function (ev) { + this.set('state', '_select'); + this.isValidUrl = true; + }, + _onClickFormSubmit: function (ev) { + var self = this; + var $btn = $(ev.currentTarget); + if (this._formValidate()) { + var values = this._formValidateGetValues($btn.hasClass('o_w_slide_upload_published')); // get info before changing state + var oldType = this.get('state'); + this.set('state', '_upload'); + return this._rpc({ + route: '/slides/add_slide', + params: values, + }).then(function (data) { + if (data.error) { + self.set('state', oldType); + self._alertDisplay(data.error); + } else { + window.location = data.url; + } + }); + } + }, + _onClickSlideTypeIcon: function (ev) { + var $elem = this.$(ev.currentTarget); + var slideType = $elem.data('slideType'); + this.set('state', slideType); + this._bindSelect2Dropdown(); // rebind select2 at each modal body rendering + }, }); + sAnimations.registry.websiteSlidesUpload = sAnimations.Class.extend({ selector: '.oe_slide_js_upload', - xmlDependencies: ['/website_slides/static/src/xml/website_slides.xml'], + xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'], read_events: { 'click': '_onUploadClick', }, @@ -468,7 +521,9 @@ sAnimations.registry.websiteSlidesUpload = sAnimations.Class.extend({ _openDialog: function ($element) { var data = $element.data(); - new SlideDialog(this, data).appendTo(document.body); + var dialog = new SlideUploadDialog(this, data) + dialog.appendTo(document.body); + return dialog.open(); }, //-------------------------------------------------------------------------- @@ -483,4 +538,7 @@ sAnimations.registry.websiteSlidesUpload = sAnimations.Class.extend({ this._openDialog($(ev.currentTarget)); }, }); + +return SlideUploadDialog; + }); diff --git a/addons/website_slides/static/src/js/website_slides.editor.js b/addons/website_slides/static/src/js/website_slides.editor.js index 2d4f0ee8992..40513e785e2 100644 --- a/addons/website_slides/static/src/js/website_slides.editor.js +++ b/addons/website_slides/static/src/js/website_slides.editor.js @@ -8,12 +8,83 @@ var WebsiteNewMenu = require('website.newMenu'); var _t = core._t; + +var ChannelCreateDialog = Dialog.extend({ + template: 'website.slide.channel.create', + /** + * @override + * @param {Object} parent + * @param {Object} options + */ + init: function (parent, options) { + options = _.defaults(options || {}, { + title: _t("New Channel Slide"), + size: 'medium', + buttons: [{ + text: _t("Create"), + classes: 'btn-primary', + click: this._onClickFormSubmit.bind(this) + }, { + text: _t("Discard"), + close: true + },] + }); + this._super(parent, options); + }, + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + var $input = self.$('#tag_ids'); + $input.select2({ + width: '100%', + allowClear: true, + formatNoMatches: false, + multiple: true, + selection_data: false, + fill_data: function (query, data) { + var that = this, + tags = {results: []}; + _.each(data, function (obj) { + if (that.matcher(query.term, obj.name)) { + tags.results.push({id: obj.id, text: obj.name}); + } + }); + query.callback(tags); + }, + query: function (query) { + var that = this; + // fetch data only once and store it + if (!this.selection_data) { + self._rpc({ + route: '/slides/channel/tag/search_read', + params: { + fields: ['name'], + domain: [], + } + }).then(function (data) { + that.can_create = data.can_create; + that.fill_data(query, data.read_results); + that.selection_data = data.read_results; + }); + } else { + this.fill_data(query, this.selection_data); + } + } + }); + }); + }, + _onClickFormSubmit: function (ev) { + var $form = this.$("#slide_channel_add_form"); + $form.submit() + }, +}); + WebsiteNewMenu.include({ actions: _.extend({}, WebsiteNewMenu.prototype.actions || {}, { - new_slide: '_createNewSlide', + new_slide_channel: '_createNewSlideChannel', }), xmlDependencies: WebsiteNewMenu.prototype.xmlDependencies.concat( - ['/website_slides/static/src/xml/website_slides.xml'] + ['/website_slides/static/src/xml/website_slides_channel.xml'] ), //-------------------------------------------------------------------------- @@ -21,44 +92,20 @@ WebsiteNewMenu.include({ //-------------------------------------------------------------------------- /** - * Asks the user information about in which channel to create a new slide, - * and redirects the user to this channel with the "new slide" popup open. + * Displays the popup to create a new slide channel, + * and redirects the user to this channel. * * @private * @returns {Deferred} Unresolved if there is a redirection */ - _createNewSlide: function () { - var self = this; - return this._rpc({ - model: 'slide.channel', - method: 'list_all', - args: [[]], - }).then(function (data) { - var def = $.Deferred(); - new Dialog(self, { - title: _t("New slide"), - subtitle: _t("On which channel do you want to add a slide?"), - size: 'medium', - $content: QWeb.render('website.slide.create', data), - buttons: [{ - text: _t("Select"), - classes: 'btn-primary', - click: function () { - var channel_url = this.$("option:selected").val(); - if (channel_url) { - window.location.href = channel_url + '?enable_slide_upload'; - } else { - def.reject(); - } - } - }, { - text: _t("Cancel"), close: true - },] - }).open() - .on('closed', def.resolve.bind(def)); - - return def; + _createNewSlideChannel: function () { + var def = $.Deferred(); + var dialog = new ChannelCreateDialog(this, {}); + dialog.open(); + dialog.on('closed', this, function() { + def.resolve(); }); - }, + return def; + }, }); }); diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss index e39e803c877..da91b79032c 100644 --- a/addons/website_slides/static/src/scss/website_slides.scss +++ b/addons/website_slides/static/src/scss/website_slides.scss @@ -172,3 +172,9 @@ div.embed-responsive { .oe_show_footer { z-index: $MAX-Z-INDEX; // Looks terrible but seems necessary due to fullscreen & canvas in PDFSlidesViewer } + + +// Upload Modal +.o_wslides_select_type { + cursor: pointer; +} \ No newline at end of file diff --git a/addons/website_slides/static/src/xml/website_slides.xml b/addons/website_slides/static/src/xml/website_slides.xml deleted file mode 100644 index 4283291b956..00000000000 --- a/addons/website_slides/static/src/xml/website_slides.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - -
-
- -
- -
-
-
-
- -
diff --git a/addons/website_slides/static/src/xml/website_slides_channel.xml b/addons/website_slides/static/src/xml/website_slides_channel.xml new file mode 100644 index 00000000000..525b55fd1ff --- /dev/null +++ b/addons/website_slides/static/src/xml/website_slides_channel.xml @@ -0,0 +1,51 @@ + + + + + +
+
+ +
+ +
+ +
+
+
+
+ Type +
+
+ + +
+
+ + +
+
+
+
+
+ +
+