summaryrefslogtreecommitdiff
path: root/addons/note/static
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/note/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/note/static')
-rw-r--r--addons/note/static/description/icon.pngbin0 -> 8138 bytes
-rw-r--r--addons/note/static/description/icon.svg1
-rw-r--r--addons/note/static/src/js/systray_activity_menu.js149
-rw-r--r--addons/note/static/src/scss/note.scss63
-rw-r--r--addons/note/static/src/xml/systray.xml32
-rw-r--r--addons/note/static/tests/systray_activity_menu_tests.js130
6 files changed, 375 insertions, 0 deletions
diff --git a/addons/note/static/description/icon.png b/addons/note/static/description/icon.png
new file mode 100644
index 00000000..2298750c
--- /dev/null
+++ b/addons/note/static/description/icon.png
Binary files differ
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();
+});
+});
+
+});