diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/note/static | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/note/static')
| -rw-r--r-- | addons/note/static/description/icon.png | bin | 0 -> 8138 bytes | |||
| -rw-r--r-- | addons/note/static/description/icon.svg | 1 | ||||
| -rw-r--r-- | addons/note/static/src/js/systray_activity_menu.js | 149 | ||||
| -rw-r--r-- | addons/note/static/src/scss/note.scss | 63 | ||||
| -rw-r--r-- | addons/note/static/src/xml/systray.xml | 32 | ||||
| -rw-r--r-- | addons/note/static/tests/systray_activity_menu_tests.js | 130 |
6 files changed, 375 insertions, 0 deletions
diff --git a/addons/note/static/description/icon.png b/addons/note/static/description/icon.png Binary files differnew file mode 100644 index 00000000..2298750c --- /dev/null +++ b/addons/note/static/description/icon.png diff --git a/addons/note/static/description/icon.svg b/addons/note/static/description/icon.svg new file mode 100644 index 00000000..212191dc --- /dev/null +++ b/addons/note/static/description/icon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="98.616%"><stop offset="0%" stop-color="#797C79"/><stop offset="100%" stop-color="#545554"/></linearGradient><path id="d" d="M56.342 31.863l-1.875 1.876a.489.489 0 0 1-.692 0l-4.517-4.516a.489.489 0 0 1 0-.692l1.876-1.876c.761-.76 1.998-.76 2.763 0l2.445 2.446a1.95 1.95 0 0 1 0 2.762zM15 47v-2h17v2H15zm0-4.915v-2h19v2H15zm0-9v-2h22v2H15zm0 5v-2h14v2H15zm0-9v-2h28v2H15zm32.647 1.057a.494.494 0 0 1 .696 0l4.516 4.516c.192.192.192.5 0 .692L42.174 46.034l-4.944.867a.978.978 0 0 1-1.13-1.131l.862-4.944 10.685-10.684zm-6.515 9.769a.567.567 0 0 0 .806 0l6.266-6.266a.567.567 0 0 0 0-.805.567.567 0 0 0-.805 0l-6.267 6.265a.567.567 0 0 0 0 .806zm-1.468 3.422V41.38h-1.477l-.46 2.624 1.265 1.265 2.625-.46v-1.476h-1.953z"/><path id="e" d="M56.342 29.863l-1.875 1.876a.489.489 0 0 1-.692 0l-4.517-4.516a.489.489 0 0 1 0-.692l1.876-1.876c.761-.76 1.998-.76 2.763 0l2.445 2.446a1.95 1.95 0 0 1 0 2.762zM15 45v-2h17v2H15zm0-4.915v-2h19v2H15zm0-9v-2h22v2H15zm0 5v-2h14v2H15zm0-9v-2h28v2H15zm32.647 1.057a.494.494 0 0 1 .696 0l4.516 4.516c.192.192.192.5 0 .692L42.174 44.034l-4.944.867a.978.978 0 0 1-1.13-1.131l.862-4.944 10.685-10.684zm-6.515 9.769a.567.567 0 0 0 .806 0l6.266-6.266a.567.567 0 0 0 0-.805.567.567 0 0 0-.805 0l-6.267 6.265a.567.567 0 0 0 0 .806zm-1.468 3.422V39.38h-1.477l-.46 2.624 1.265 1.265 2.625-.46v-1.476h-1.953z"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-1-4-4V41.668l15.07-16.471h27.883v1.843L31.949 38.148l1.998 1.89-4.983 4.239h1.513l21.088-19.832L56 30 25.947 68.643 4 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg>
\ No newline at end of file diff --git a/addons/note/static/src/js/systray_activity_menu.js b/addons/note/static/src/js/systray_activity_menu.js new file mode 100644 index 00000000..ac4054c0 --- /dev/null +++ b/addons/note/static/src/js/systray_activity_menu.js @@ -0,0 +1,149 @@ +odoo.define('note.systray.ActivityMenu', function (require) { +"use strict"; + +var ActivityMenu = require('mail.systray.ActivityMenu'); + +var core = require('web.core'); +var datepicker = require('web.datepicker'); + +var _t = core._t; + +ActivityMenu.include({ + events: _.extend({}, ActivityMenu.prototype.events, { + 'click .o_note_show': '_onAddNoteClick', + 'click .o_note_save': '_onNoteSaveClick', + 'click .o_note_set_datetime': '_onNoteDateTimeSetClick', + 'keydown input.o_note_input': '_onNoteInputKeyDown', + 'click .o_note': '_onNewNoteClick', + }), + //-------------------------------------------------- + // Private + //-------------------------------------------------- + /** + * Moving notes at first place + * @override + */ + _getActivityData: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + var reminderIndex = _.findIndex(self.activities, function (val) { + return val.model === 'note.note'; + }); + if (reminderIndex > 0) { + self.activities.splice(0, 0, self.activities.splice(reminderIndex, 1)[0]); + } + }); + }, + /** + * Save the note to database using datepicker date and field as note + * By default, when no datetime is set, it uses the current datetime. + * + * @private + */ + _saveNote: function () { + var note = this.$('.o_note_input').val().trim(); + if (! note) { + return; + } + var params = {'note': note}; + var noteDateTime = this.noteDateTimeWidget.getValue(); + if (noteDateTime) { + params = _.extend(params, {'date_deadline': noteDateTime}); + } else { + params = _.extend(params, {'date_deadline': moment()}); + } + this.$('.o_note_show').removeClass('d-none'); + this.$('.o_note').addClass('d-none'); + this._rpc({ + route: '/note/new', + params: params, + }).then(this._updateActivityPreview.bind(this)); + }, + //----------------------------------------- + // Handlers + //----------------------------------------- + /** + * @override + */ + _onActivityFilterClick: function (ev) { + var $el = $(ev.currentTarget); + if (!$el.hasClass("o_note")) { + var data = _.extend({}, $el.data(), $(ev.target).data()); + if (data.res_model === "note.note" && data.filter === "my") { + this.do_action({ + type: 'ir.actions.act_window', + name: data.model_name, + res_model: data.res_model, + views: [[false, 'kanban'], [false, 'form'], [false, 'list']] + }, { + clear_breadcrumbs: true, + }); + } else { + this._super.apply(this, arguments); + } + } + }, + /** + * When add new note button clicked, toggling quick note create view inside + * Systray activity view + * + * @private + * @param {MouseEvent} ev + */ + _onAddNoteClick: function (ev) { + var self = this; + ev.stopPropagation(); + if (!this.noteDateTimeWidget){ + this.noteDateTimeWidget = new datepicker.DateWidget(this, {useCurrent: true}); + } + this.noteDateTimeWidget.appendTo(this.$('.o_note_datetime')).then(function() { + self.noteDateTimeWidget.$input.attr('placeholder', _t("Today")); + self.noteDateTimeWidget.setValue(false); + self.$('.o_note_show, .o_note').toggleClass('d-none'); + self.$('.o_note_input').val('').focus(); + }); + }, + /** + * When focusing on input for new quick note systerm tray must be open. + * Preventing to close + * + * @private + * @param {MouseEvent} ev + */ + _onNewNoteClick: function (ev) { + ev.stopPropagation(); + }, + /** + * Opens datetime picker for note. + * Quick FIX due to no option for set custom icon instead of caret in datepicker. + * + * @private + * @param {MouseEvent} ev + */ + _onNoteDateTimeSetClick: function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + this.noteDateTimeWidget.$input.click(); + }, + /** + * Saving note (quick create) and updating activity preview + * + * @private + * @param {MouseEvent} ev + */ + _onNoteSaveClick: function (ev) { + this._saveNote(); + }, + /** + * Handling Enter key for quick create note. + * + * @private + * @param {KeyboardEvent} ev + */ + _onNoteInputKeyDown: function (ev) { + if (ev.which === $.ui.keyCode.ENTER) { + this._saveNote(); + } + }, +}); +}); diff --git a/addons/note/static/src/scss/note.scss b/addons/note/static/src/scss/note.scss new file mode 100644 index 00000000..2e15d5de --- /dev/null +++ b/addons/note/static/src/scss/note.scss @@ -0,0 +1,63 @@ + +.o_kanban_group .note_text_line_through { + text-decoration: line-through; +} + +.o_note_form_view.o_form_view { + .o_form_statusbar { + margin-bottom: 0; + } + .oe_form_field.oe_memo { + margin: 0; + padding: 0; + min-height: 200px; + } + &.o_form_readonly { + .oe_memo { + padding: $input-btn-padding-x-lg; + border-bottom: 1px solid gray('300'); + } + } +} + +// Quick create notes from systray +.o_note.o_mail_preview { + background-color: white; + .o_preview_info { + .o_preview_title { + .o_preview_name { + flex: 1 1 100%; + } + } + .o_note_input_box { + display: flex; + p { + flex: 1 1 auto; + margin-bottom: 0px; + } + } + .o_note_save { + font-size: 11px; + font-weight: bold; + } + } + .o_note_input { + border: none; + } + .o_note_datetime { + .o_datepicker { + .o_datepicker_input { + float: right; + text-align: right; + border: none; + font-size: 11px; + } + .o_datepicker_button { + display: none; + } + } + } + .o_note_set_datetime { + color: $text-muted; + } +} diff --git a/addons/note/static/src/xml/systray.xml b/addons/note/static/src/xml/systray.xml new file mode 100644 index 00000000..8f0e953d --- /dev/null +++ b/addons/note/static/src/xml/systray.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <t t-extend="mail.systray.ActivityMenu.Previews"> + <t t-jquery="t[t-foreach*='activities'][t-as*='activity']" t-operation="after"> + <div class="o_note_show"> + <a role="button" class="btn btn-block text-center">Add new note</a> + </div> + <div class="o_note o_mail_preview d-none"> + <div class="o_mail_preview_image o_mail_preview_app"> + <img src="/note/static/description/icon.png" alt="Channel"/> + </div> + <div class="o_preview_info"> + <div class="o_preview_title"> + <span class="o_preview_name"><strong>Add a note</strong></span> + <div class="o_note_datetime"/> + <span class="ml4"> + <a class="o_note_set_datetime"> + <span class="fa fa-calendar" role="img" aria-label="Set date and time" title="Set date and time"/> + </a> + </span> + </div> + <div class="o_note_input_box"> + <p><input class="o_note_input" type="text" placeholder="Remember..." /></p> + <span class="ml8 mr4"> + <a class="o_note_save">SAVE</a> + </span> + </div> + </div> + </div> + </t> + </t> +</templates> diff --git a/addons/note/static/tests/systray_activity_menu_tests.js b/addons/note/static/tests/systray_activity_menu_tests.js new file mode 100644 index 00000000..095bbb23 --- /dev/null +++ b/addons/note/static/tests/systray_activity_menu_tests.js @@ -0,0 +1,130 @@ +odoo.define('note.systray.ActivityMenuTests', function (require) { +"use strict"; + +const { afterEach, beforeEach, start } = require('mail/static/src/utils/test_utils.js'); +var ActivityMenu = require('mail.systray.ActivityMenu'); + +var testUtils = require('web.test_utils'); + +QUnit.module('note', {}, function () { +QUnit.module("ActivityMenu", { + beforeEach() { + beforeEach(this); + + Object.assign(this.data, { + 'mail.activity.menu': { + fields: { + name: { type: "char" }, + model: { type: "char" }, + type: { type: "char" }, + planned_count: { type: "integer" }, + today_count: { type: "integer" }, + overdue_count: { type: "integer" }, + total_count: { type: "integer" } + }, + records: [], + }, + 'note.note': { + fields: { + memo: { type: 'char' }, + }, + records: [], + } + }); + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('note activity menu widget: create note from activity menu', async function (assert) { + assert.expect(15); + var self = this; + + const { widget } = await start({ + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'systray_get_activities') { + return Promise.resolve(self.data['mail.activity.menu'].records); + } + if (route === '/note/new') { + if (args.date_deadline) { + var note = { + id: 1, + memo: args.note, + date_deadline: args.date_deadline + }; + self.data['note.note'].records.push(note); + if (_.isEmpty(self.data['mail.activity.menu'].records)) { + self.data['mail.activity.menu'].records.push({ + name: "Note", + model: "note.note", + type: "activity", + planned_count: 0, + today_count: 0, + overdue_count: 0, + total_count: 0, + }); + } + self.data['mail.activity.menu'].records[0].today_count++; + self.data['mail.activity.menu'].records[0].total_count++; + } + return Promise.resolve(); + } + return this._super(route, args); + }, + }); + + const activityMenu = new ActivityMenu(widget); + await activityMenu.appendTo($('#qunit-fixture')); + assert.hasClass(activityMenu.$el, 'o_mail_systray_item', + 'should be the instance of widget'); + assert.strictEqual(activityMenu.$('.o_notification_counter').text(), '0', + "should not have any activity notification initially"); + + // toggle quick create for note + await testUtils.dom.click(activityMenu.$('.dropdown-toggle')); + assert.containsOnce(activityMenu, '.o_no_activity', + "should not have any activity preview"); + assert.doesNotHaveClass(activityMenu.$('.o_note_show'), 'd-none', + 'ActivityMenu should have Add new note CTA'); + await testUtils.dom.click(activityMenu.$('.o_note_show')); + assert.hasClass(activityMenu.$('.o_note_show'), 'd-none', + 'ActivityMenu should hide CTA when entering a new note'); + assert.doesNotHaveClass(activityMenu.$('.o_note'), 'd-none', + 'ActivityMenu should display input for new note'); + + // creating quick note without date + await testUtils.fields.editInput(activityMenu.$("input.o_note_input"), "New Note"); + await testUtils.dom.click(activityMenu.$(".o_note_save")); + assert.strictEqual(activityMenu.$('.o_notification_counter').text(), '1', + "should increment activity notification counter after creating a note"); + assert.containsOnce(activityMenu, '.o_mail_preview[data-res_model="note.note"]', + "should have an activity preview that is a note"); + assert.strictEqual(activityMenu.$('.o_activity_filter_button[data-filter="today"]').text().trim(), + "1 Today", + "should display one note for today"); + + assert.doesNotHaveClass(activityMenu.$('.o_note_show'), 'd-none', + 'ActivityMenu add note button should be displayed'); + assert.hasClass(activityMenu.$('.o_note'), 'd-none', + 'ActivityMenu add note input should be hidden'); + + // creating quick note with date + await testUtils.dom.click(activityMenu.$('.o_note_show')); + activityMenu.$('input.o_note_input').val("New Note"); + await testUtils.dom.click(activityMenu.$(".o_note_save")); + assert.strictEqual(activityMenu.$('.o_notification_counter').text(), '2', + "should increment activity notification counter after creating a second note"); + assert.strictEqual(activityMenu.$('.o_activity_filter_button[data-filter="today"]').text().trim(), + "2 Today", + "should display 2 notes for today"); + assert.doesNotHaveClass(activityMenu.$('.o_note_show'), 'd-none', + 'ActivityMenu add note button should be displayed'); + assert.hasClass(activityMenu.$('.o_note'), 'd-none', + 'ActivityMenu add note input should be hidden'); + widget.destroy(); +}); +}); + +}); |
