Kaynağa Gözat

Merge branch 'develop' into star-rating-dev

version-14
Suraj Shetty 3 yıl önce
committed by GitHub
ebeveyn
işleme
4d2548cff9
Veri tabanında bu imza için bilinen anahtar bulunamadı GPG Anahtar Kimliği: 4AEE18F83AFDEB23
29 değiştirilmiş dosya ile 430 ekleme ve 128 silme
  1. +4
    -2
      codecov.yml
  2. +4
    -4
      cypress/integration/datetime.js
  3. +49
    -0
      cypress/integration/grid_keyboard_shortcut.js
  4. +5
    -10
      cypress/integration/list_view.js
  5. +10
    -2
      frappe/boot.py
  6. +6
    -0
      frappe/core/doctype/system_settings/system_settings.js
  7. +1
    -0
      frappe/core/doctype/system_settings/system_settings.json
  8. +12
    -1
      frappe/core/doctype/user/user.js
  9. +11
    -6
      frappe/core/doctype/user/user.py
  10. +1
    -1
      frappe/desk/form/linked_with.py
  11. +1
    -0
      frappe/patches.txt
  12. +1
    -12
      frappe/public/js/frappe/form/controls/data.js
  13. +7
    -4
      frappe/public/js/frappe/form/controls/date.js
  14. +45
    -3
      frappe/public/js/frappe/form/controls/datetime.js
  15. +1
    -1
      frappe/public/js/frappe/form/controls/link.js
  16. +1
    -1
      frappe/public/js/frappe/form/controls/time.js
  17. +1
    -1
      frappe/public/js/frappe/form/footer/version_timeline_content_builder.js
  18. +54
    -2
      frappe/public/js/frappe/form/form.js
  19. +2
    -6
      frappe/public/js/frappe/form/formatters.js
  20. +5
    -2
      frappe/public/js/frappe/form/grid.js
  21. +48
    -1
      frappe/public/js/frappe/form/grid_row.js
  22. +9
    -1
      frappe/public/js/frappe/list/base_list.js
  23. +79
    -22
      frappe/public/js/frappe/list/list_view.js
  24. +1
    -1
      frappe/public/js/frappe/model/perm.js
  25. +5
    -0
      frappe/public/js/frappe/ui/keyboard.js
  26. +62
    -42
      frappe/public/js/frappe/utils/datetime.js
  27. +1
    -1
      frappe/public/js/frappe/utils/pretty_date.js
  28. +1
    -2
      frappe/public/scss/common/quill.scss
  29. +3
    -0
      frappe/utils/image.py

+ 4
- 2
codecov.yml Dosyayı Görüntüle

@@ -14,8 +14,10 @@ coverage:
patch:
default: false
server:
target: auto
threshold: 85%
target: 85%
threshold: 0%
only_pulls: true
if_ci_failed: ignore
flags:
- server



+ 4
- 4
cypress/integration/datetime.js Dosyayı Görüntüle

@@ -92,15 +92,15 @@ context('Control Date, Time and DateTime', () => {
date_format: 'dd.mm.yyyy',
time_format: 'HH:mm:ss',
value: ' 02.12.2019 11:00:12',
doc_value: '2019-12-02 11:00:12',
input_value: '02.12.2019 11:00:12'
doc_value: '2019-12-02 00:30:12', // system timezone (America/New_York)
input_value: '02.12.2019 11:00:12' // admin timezone (Asia/Kolkata)
},
{
date_format: 'mm-dd-yyyy',
time_format: 'HH:mm',
value: ' 12-02-2019 11:00:00',
doc_value: '2019-12-02 11:00:00',
input_value: '12-02-2019 11:00'
doc_value: '2019-12-02 00:30:00', // system timezone (America/New_York)
input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata)
}
];
datetime_formats.forEach(d => {


+ 49
- 0
cypress/integration/grid_keyboard_shortcut.js Dosyayı Görüntüle

@@ -0,0 +1,49 @@
context('Grid Keyboard Shortcut', () => {
let total_count = 0;
beforeEach(() => {
cy.login();
cy.visit('/app/doctype/User');
});
before(() => {
cy.login();
cy.visit('/app/doctype/User');
return cy.window().its('frappe').then(frappe => {
frappe.db.count('DocField', {
filters: {
'parent': 'User', 'parentfield': 'fields', 'parenttype': 'DocType'
}
}).then((r) => {
total_count = r;
});
});
});
it('Insert new row at the end', () => {
cy.add_new_row_in_grid('{ctrl}{shift}{downarrow}', (cy, total_count) => {
cy.get('[data-name="new-docfield-1"]').should('have.attr', 'data-idx', `${total_count+1}`);
}, total_count);
});
it('Insert new row at the top', () => {
cy.add_new_row_in_grid('{ctrl}{shift}{uparrow}', (cy) => {
cy.get('[data-name="new-docfield-1"]').should('have.attr', 'data-idx', '1');
});
});
it('Insert new row below', () => {
cy.add_new_row_in_grid('{ctrl}{downarrow}', (cy) => {
cy.get('[data-name="new-docfield-1"]').should('have.attr', 'data-idx', '2');
});
});
it('Insert new row above', () => {
cy.add_new_row_in_grid('{ctrl}{uparrow}', (cy) => {
cy.get('[data-name="new-docfield-1"]').should('have.attr', 'data-idx', '1');
});
});
});

Cypress.Commands.add('add_new_row_in_grid', (shortcut_keys, callbackFn, total_count) => {
cy.get('.frappe-control[data-fieldname="fields"]').as('table');
cy.get('@table').find('.grid-body .col-xs-2').first().click();
cy.get('@table').find('.grid-body .col-xs-2')
.first().type(shortcut_keys);

callbackFn(cy, total_count);
});

+ 5
- 10
cypress/integration/list_view.js Dosyayı Görüntüle

@@ -7,18 +7,13 @@ context('List View', () => {
});
});

it('Keep checkbox checked after Bulk Update', () => {
it('Keep checkbox checked after Refresh', () => {
cy.go_to_list('ToDo');
cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true });
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
cy.get('.dropdown-menu li:visible .dropdown-item .menu-item-label[data-label="Edit"]').click();

cy.get('.modal-body .form-control[data-fieldname="field"]').first().select('Priority').wait(200);

cy.get('.modal-footer .standard-actions .btn-primary').click();
cy.wait(500);

cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
cy.get('.actions-btn-group button').contains('Actions').should('be.visible');
cy.intercept('/api/method/frappe.desk.reportview.get').as('list-refresh');
cy.get('button[data-original-title="Refresh"]').click();
cy.wait('@list-refresh');
cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible');
});



+ 10
- 2
frappe/boot.py Dosyayı Görüntüle

@@ -17,6 +17,7 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p
from frappe.model.base_document import get_controller
from frappe.social.doctype.post.post import frequently_visited_links
from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo
from frappe.utils import get_time_zone

def get_bootinfo():
"""build and return boot info"""
@@ -58,6 +59,7 @@ def get_bootinfo():
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})
bootinfo.navbar_settings = get_navbar_settings()
bootinfo.notification_settings = get_notification_settings()
set_time_zone(bootinfo)

# ipinfo
if frappe.session.data.get('ipinfo'):
@@ -220,8 +222,8 @@ def load_translations(bootinfo):
bootinfo["__messages"] = messages

def get_user_info():
user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image',
'gender', 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type'],
user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image', 'gender',
'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type', 'time_zone'],
filters=dict(enabled=1))

user_info_map = {d.name: d for d in user_info}
@@ -324,3 +326,9 @@ def get_desk_settings():

def get_notification_settings():
return frappe.get_cached_doc('Notification Settings', frappe.session.user)

def set_time_zone(bootinfo):
bootinfo.time_zone = {
"system": get_time_zone(),
"user": bootinfo.get("user_info", {}).get(frappe.session.user, {}).get("time_zone", None) or get_time_zone()
}

+ 6
- 0
frappe/core/doctype/system_settings/system_settings.js Dosyayı Görüntüle

@@ -32,5 +32,11 @@ frappe.ui.form.on("System Settings", {
frm.set_value('prepared_report_expiry_period', 7);
}
}
},
on_update: function(frm) {
if (frappe.boot.time_zone && frappe.boot.time_zone.system !== frm.doc.time_zone) {
// Clear cache after saving to refresh the values of boot.
frappe.ui.toolbar.clear_cache();
}
}
});

+ 1
- 0
frappe/core/doctype/system_settings/system_settings.json Dosyayı Görüntüle

@@ -97,6 +97,7 @@
"fieldname": "time_zone",
"fieldtype": "Select",
"label": "Time Zone",
"read_only": 1,
"reqd": 1
},
{


+ 12
- 1
frappe/core/doctype/user/user.js Dosyayı Görüntüle

@@ -77,7 +77,12 @@ frappe.ui.form.on('User', {
}
},
refresh: function(frm) {
var doc = frm.doc;
let doc = frm.doc;

if (frm.is_new()) {
frm.set_value("time_zone", frappe.sys_defaults.time_zone);
}

if (in_list(['System User', 'Website User'], frm.doc.user_type)
&& !frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
frm.reload_doc();
@@ -267,6 +272,12 @@ frappe.ui.form.on('User', {
}
}
});
},
on_update: function(frm) {
if (frappe.boot.time_zone && frappe.boot.time_zone.user !== frm.doc.time_zone) {
// Clear cache after saving to refresh the values of boot.
frappe.ui.toolbar.clear_cache();
}
}
});



+ 11
- 6
frappe/core/doctype/user/user.py Dosyayı Görüntüle

@@ -7,7 +7,7 @@ import frappe.defaults
import frappe.permissions
from frappe.model.document import Document
from frappe.utils import (cint, flt, has_gravatar, escape_html, format_datetime,
now_datetime, get_formatted_email, today)
now_datetime, get_formatted_email, today, get_time_zone)
from frappe import throw, msgprint, _
from frappe.utils.password import update_password as _update_password, check_password, get_password_reset_limit
from frappe.desk.notifications import clear_notifications
@@ -74,6 +74,7 @@ class User(Document):
self.validate_roles()
self.validate_allowed_modules()
self.validate_user_image()
self.set_time_zone()

if self.language == "Loading...":
self.language = None
@@ -227,11 +228,11 @@ class User(Document):
def validate_share(self, docshare):
pass
# if docshare.user == self.name:
# if self.user_type=="System User":
# if docshare.share != 1:
# frappe.throw(_("Sorry! User should have complete access to their own record."))
# else:
# frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
# if self.user_type=="System User":
# if docshare.share != 1:
# frappe.throw(_("Sorry! User should have complete access to their own record."))
# else:
# frappe.throw(_("Sorry! Sharing with Website User is prohibited."))

def send_password_notification(self, new_password):
try:
@@ -596,6 +597,10 @@ class User(Document):

return user

def set_time_zone(self):
if not self.time_zone:
self.time_zone = get_time_zone()

@frappe.whitelist()
def get_timezones():
import pytz


+ 1
- 1
frappe/desk/form/linked_with.py Dosyayı Görüntüle

@@ -133,7 +133,7 @@ class SubmittableDocumentTree:
"""Returns list of submittable doctypes.
"""
if not self._submittable_doctypes:
self._submittable_doctypes = frappe.db.get_list('DocType', {'is_submittable': 1}, pluck='name')
self._submittable_doctypes = frappe.db.get_all('DocType', {'is_submittable': 1}, pluck='name')
return self._submittable_doctypes




+ 1
- 0
frappe/patches.txt Dosyayı Görüntüle

@@ -2,6 +2,7 @@ frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3
execute:frappe.utils.global_search.setup_global_search_table()
execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23
execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2020-10-17
execute:frappe.reload_doc('core', 'doctype', 'doctype_state', force=True) #2021-12-15
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
frappe.patches.v11_0.drop_column_apply_user_permissions


+ 1
- 12
frappe/public/js/frappe/form/controls/data.js Dosyayı Görüntüle

@@ -122,12 +122,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
);

this.$scan_btn = this.$wrapper.find('.link-btn');

this.$input.on("focus", () => {
setTimeout(() => {
this.$scan_btn.toggle(true);
}, 500);
});
this.$scan_btn.toggle(true);

const me = this;
this.$scan_btn.on('click', 'a', () => {
@@ -141,12 +136,6 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
}
});
});

this.$input.on("blur", () => {
setTimeout(() => {
this.$scan_btn.toggle(false);
}, 500);
});
}

bind_change_event() {


+ 7
- 4
frappe/public/js/frappe/form/controls/date.js Dosyayı Görüntüle

@@ -53,8 +53,6 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat
let date_format = sysdefaults && sysdefaults.date_format
? sysdefaults.date_format : 'yyyy-mm-dd';

let now_date = new Date();

this.today_text = __("Today");
this.date_format = frappe.defaultDateFormat;
this.datepicker_options = {
@@ -62,7 +60,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat
autoClose: true,
todayButton: true,
dateFormat: date_format,
startDate: now_date,
startDate: this.get_start_date(),
keyboardNav: false,
onSelect: () => {
this.$input.trigger('change');
@@ -77,6 +75,11 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat
...(this.get_df_options())
};
}

get_start_date() {
return new Date(this.get_now_date());
}

set_datepicker() {
this.$input.datepicker(this.datepicker_options);
this.datepicker = this.$input.data('datepicker');
@@ -113,7 +116,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat
this.datepicker.update('position', position);
}
get_now_date() {
return frappe.datetime.now_date(true);
return frappe.datetime.convert_to_system_tz(frappe.datetime.now_date(true));
}
set_t_for_today() {
var me = this;


+ 45
- 3
frappe/public/js/frappe/form/controls/datetime.js Dosyayı Görüntüle

@@ -1,4 +1,22 @@
frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.ControlDate {
set_formatted_input(value) {
if (this.timepicker_only) return;
if (!this.datepicker) return;
if (!value) {
this.datepicker.clear();
return;
} else if (value === "Today") {
value = this.get_now_date();
}
value = this.format_for_input(value);
this.$input && this.$input.val(value);
this.datepicker.selectDate(frappe.datetime.user_to_obj(value));
}

get_start_date() {
let value = frappe.datetime.convert_to_user_tz(this.value);
return frappe.datetime.str_to_obj(value);
}
set_date_options() {
super.set_date_options();
this.today_text = __("Now");
@@ -14,10 +32,31 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
get_now_date() {
return frappe.datetime.now_datetime(true);
}
parse(value) {
if (value) {
value = frappe.datetime.user_to_str(value, false);

if (!frappe.datetime.is_system_time_zone()) {
value = frappe.datetime.convert_to_system_tz(value, true);
}

return value;
}
}
format_for_input(value) {
if (!value) return "";


return frappe.datetime.str_to_user(value, false);
}
set_description() {
const { description } = this.df;
const { time_zone } = frappe.sys_defaults;
if (!this.df.hide_timezone && !frappe.datetime.is_timezone_same()) {
const description = this.df.description;
const time_zone = this.get_user_time_zone();

if (!this.df.hide_timezone) {
// Always show the timezone when rendering the Datetime field since the datetime value will
// always be in system_time_zone rather then local time.

if (!description) {
this.df.description = time_zone;
} else if (!description.includes(time_zone)) {
@@ -26,6 +65,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
}
super.set_description();
}
get_user_time_zone() {
return frappe.boot.time_zone ? frappe.boot.time_zone.user : frappe.sys_defaults.time_zone;
}
set_datepicker() {
super.set_datepicker();
if (this.datepicker.opts.timeFormat.indexOf('s') == -1) {


+ 1
- 1
frappe/public/js/frappe/form/controls/link.js Dosyayı Görüntüle

@@ -471,7 +471,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
docname: value,
fields: columns_to_fetch,
}).then((response) => {
if (!response || !response.name) return "";
if (!response || !response.name) return null;
if (!docname || !columns_to_fetch.length) return response.name;

for (const [target_field, source_field] of Object.entries(fetch_map)) {


+ 1
- 1
frappe/public/js/frappe/form/controls/time.js Dosyayı Görüntüle

@@ -71,7 +71,7 @@ frappe.ui.form.ControlTime = class ControlTime extends frappe.ui.form.ControlDat
set_description() {
const { description } = this.df;
const { time_zone } = frappe.sys_defaults;
if (!frappe.datetime.is_timezone_same()) {
if (!frappe.datetime.is_system_time_zone()) {
if (!description) {
this.df.description = time_zone;
} else if (!description.includes(time_zone)) {


+ 1
- 1
frappe/public/js/frappe/form/footer/version_timeline_content_builder.js Dosyayı Görüntüle

@@ -116,7 +116,7 @@ function get_version_timeline_content(version_doc, frm) {
frm.perm);

if (field_display_status === 'Read' || field_display_status === 'Write') {
return frappe.meta.get_label(frm.doctype, p[0]);
return __(frappe.meta.get_label(frm.doctype, p[0]));
}
}
});


+ 54
- 2
frappe/public/js/frappe/form/form.js Dosyayı Görüntüle

@@ -85,7 +85,7 @@ frappe.ui.form.Form = class FrappeForm {
});

// navigate records keyboard shortcuts
this.add_nav_keyboard_shortcuts();
this.add_form_keyboard_shortcuts();

// 2 column layout
this.setup_std_layout();
@@ -116,7 +116,8 @@ frappe.ui.form.Form = class FrappeForm {
this.setup_done = true;
}

add_nav_keyboard_shortcuts() {
add_form_keyboard_shortcuts() {
// Navigate to next record
frappe.ui.keys.add_shortcut({
shortcut: 'shift+ctrl+>',
action: () => this.navigate_records(0),
@@ -126,6 +127,7 @@ frappe.ui.form.Form = class FrappeForm {
condition: () => !this.is_new()
});

// Navigate to previous record
frappe.ui.keys.add_shortcut({
shortcut: 'shift+ctrl+<',
action: () => this.navigate_records(1),
@@ -134,6 +136,56 @@ frappe.ui.form.Form = class FrappeForm {
ignore_inputs: true,
condition: () => !this.is_new()
});

let grid_shortcut_keys = [
{
'shortcut': 'Up Arrow',
'description': __('Move cursor to above row')
},
{
'shortcut': 'Down Arrow',
'description': __('Move cursor to below row')
},
{
'shortcut': 'tab',
'description': __('Move cursor to next column')
},
{
'shortcut': 'shift+tab',
'description': __('Move cursor to previous column')
},
{
'shortcut': 'Ctrl+up',
'description': __('Add a row above the current row')
},
{
'shortcut': 'Ctrl+down',
'description': __('Add a row below the current row')
},
{
'shortcut': 'Ctrl+shift+up',
'description': __('Add a row at the top')
},
{
'shortcut': 'Ctrl+shift+down',
'description': __('Add a row at the bottom')
},
{
'shortcut': 'shift+alt+down',
'description': __('To duplcate current row')
}
];

grid_shortcut_keys.forEach(row => {
frappe.ui.keys.add_shortcut({
shortcut: row.shortcut,
page: this,
description: __(row.description),
ignore_inputs: true,
condition: () => !this.is_new()
});
});

}

setup_std_layout() {


+ 2
- 6
frappe/public/js/frappe/form/formatters.js Dosyayı Görüntüle

@@ -167,12 +167,8 @@ frappe.form.formatters = {
},
Datetime: function(value) {
if(value) {
var m = moment(frappe.datetime.convert_to_user_tz(value));
if(frappe.boot.sysdefaults.time_zone) {
m = m.tz(frappe.boot.sysdefaults.time_zone);
}
return m.format(frappe.boot.sysdefaults.date_format.toUpperCase()
+ ' ' + (frappe.boot.sysdefaults.time_format || 'HH:mm:ss'));
return moment(frappe.datetime.convert_to_user_tz(value))
.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + frappe.boot.sysdefaults.time_format || 'HH:mm:ss');
} else {
return "";
}


+ 5
- 2
frappe/public/js/frappe/form/grid.js Dosyayı Görüntüle

@@ -616,11 +616,14 @@ export default class Grid {
});
}

add_new_row(idx, callback, show, copy_doc, go_to_last_page = false) {
add_new_row(idx, callback, show, copy_doc, go_to_last_page = false, go_to_first_page = false) {
if (this.is_editable()) {
if (go_to_last_page) {
this.grid_pagination.go_to_last_page_to_add_row();
} else if (go_to_first_page) {
this.grid_pagination.go_to_page(1);
}

if (this.frm) {
var d = frappe.model.add_child(this.frm.doc, this.df.options, this.df.fieldname, idx);
if (copy_doc) {
@@ -684,7 +687,7 @@ export default class Grid {
}

set_focus_on_row(idx) {
if (!idx) {
if (!idx && idx !== 0) {
idx = this.grid_rows.length - 1;
}



+ 48
- 1
frappe/public/js/frappe/form/grid_row.js Dosyayı Görüntüle

@@ -723,6 +723,7 @@ export default class GridRow {

set_arrow_keys(field) {
var me = this;
let ignore_fieldtypes = ['Text', 'Small Text', 'Code', 'Text Editor', 'HTML Editor'];
if (field.$input) {
field.$input.on('keydown', function(e) {
var { TAB, UP: UP_ARROW, DOWN: DOWN_ARROW } = frappe.ui.keyCode;
@@ -734,8 +735,20 @@ export default class GridRow {
var fieldname = $(this).attr('data-fieldname');
var fieldtype = $(this).attr('data-fieldtype');

let ctrl_key = e.metaKey || e.ctrlKey;
if (!in_list(ignore_fieldtypes, fieldtype)
&& ctrl_key && e.which !== TAB) {
me.add_new_row_using_keys(e);
return;
}

if (e.shiftKey && e.altKey && DOWN_ARROW === e.which) {
me.duplicate_row_using_keys();
return;
}

var move_up_down = function(base) {
if (in_list(['Text', 'Small Text', 'Code', 'Text Editor', 'HTML Editor'], fieldtype) && !e.altKey) {
if (in_list(ignore_fieldtypes, fieldtype) && !e.altKey) {
return false;
}
if (field.autocomplete_open) {
@@ -790,6 +803,40 @@ export default class GridRow {
}
}

duplicate_row_using_keys() {
setTimeout(() => {
this.insert(false, true, true);
this.grid.grid_rows[this.doc.idx].toggle_editable_row();
this.grid.set_focus_on_row(this.doc.idx);
}, 100);
}

add_new_row_using_keys(e) {
let idx = '';

let ctrl_key = e.metaKey || e.ctrlKey;
let is_down_arrow_key_press = (e.which === 40);

// Add new row at the end or start of the table
if (ctrl_key && e.shiftKey) {
idx = is_down_arrow_key_press ? null : 1;
this.grid.add_new_row(idx, null, is_down_arrow_key_press,
false, is_down_arrow_key_press, !is_down_arrow_key_press);
idx = is_down_arrow_key_press ? (cint(this.grid.grid_rows.length) - 1) : 0;

} else if (ctrl_key) {
idx = is_down_arrow_key_press ? this.doc.idx : (this.doc.idx - 1);
this.insert(false, is_down_arrow_key_press);
}

if (idx !== '') {
setTimeout(() => {
this.grid.grid_rows[idx].toggle_editable_row();
this.grid.set_focus_on_row(idx);
}, 100);
}
}

get_open_form() {
return frappe.ui.form.get_open_grid_form();
}


+ 9
- 1
frappe/public/js/frappe/list/base_list.js Dosyayı Görüntüle

@@ -392,9 +392,9 @@ frappe.views.BaseList = class BaseList {

this.start = 0;
this.page_length = $this.data().value;
this.refresh();
} else if ($this.is(".btn-more")) {
this.start = this.start + this.page_length;
this.page_length = 20;
}
this.refresh();
});
@@ -475,6 +475,7 @@ frappe.views.BaseList = class BaseList {
this.render();
this.after_render();
this.freeze(false);
this.reset_defaults();
if (this.settings.refresh) {
this.settings.refresh(this);
}
@@ -492,6 +493,13 @@ frappe.views.BaseList = class BaseList {
} else {
this.data = this.data.concat(data);
}

this.data = this.data.uniqBy((d) => d.name);
}

reset_defaults() {
this.page_length = this.page_length + this.start;
this.start = 0;
}

freeze() {


+ 79
- 22
frappe/public/js/frappe/list/list_view.js Dosyayı Görüntüle

@@ -1317,7 +1317,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return;
}
frappe.realtime.on("list_update", (data) => {
if (this.filter_area.is_being_edited()) {
if (this.avoid_realtime_update()) {
return;
}

@@ -1379,6 +1379,19 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
}

avoid_realtime_update() {
if (this.filter_area.is_being_edited()) {
return true;
}
// this is set when a bulk operation is called from a list view which might update the list view
// this is to avoid the list view from refreshing a lot of times
// the list view is updated once after the bulk operation is complete
if (this.disable_list_update) {
return true;
}
return false;
}

set_rows_as_checked() {
$.each(this.$checks, (i, el) => {
let docname = $(el).attr("data-name");
@@ -1433,6 +1446,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return this.data.filter((d) => docnames.includes(d.name));
}

clear_checked_items() {
this.$checks && this.$checks.prop("checked", false);
this.on_row_checked();
}

save_view_user_settings(obj) {
return frappe.model.user_settings.save(
this.doctype,
@@ -1655,11 +1673,17 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const bulk_assignment = () => {
return {
label: __("Assign To"),
action: () =>
action: () => {
this.disable_list_update = true;
bulk_operations.assign(
this.get_checked_items(true),
this.refresh
),
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
},
standard: true,
};
};
@@ -1667,11 +1691,17 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const bulk_assignment_rule = () => {
return {
label: __("Apply Assignment Rule"),
action: () =>
action: () => {
this.disable_list_update = true;
bulk_operations.apply_assignment_rule(
this.get_checked_items(true),
this.refresh
),
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
},
standard: true,
};
};
@@ -1679,11 +1709,17 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const bulk_add_tags = () => {
return {
label: __("Add Tags"),
action: () =>
action: () => {
this.disable_list_update = true;
bulk_operations.add_tags(
this.get_checked_items(true),
this.refresh
),
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
},
standard: true,
};
};
@@ -1705,7 +1741,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
);
frappe.confirm(
__("Delete {0} items permanently?", [docnames.length]),
() => bulk_operations.delete(docnames, this.refresh)
() => {
this.disable_list_update = true;
bulk_operations.delete(docnames, () => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
});
}
);
},
standard: true,
@@ -1720,13 +1763,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
if (docnames.length > 0) {
frappe.confirm(
__("Cancel {0} documents?", [docnames.length]),
() =>
() => {
this.disable_list_update = true;
bulk_operations.submit_or_cancel(
docnames,
"cancel",
this.refresh
)
);
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
});
}
},
standard: true,
@@ -1741,12 +1789,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
if (docnames.length > 0) {
frappe.confirm(
__("Submit {0} documents?", [docnames.length]),
() =>
() => {
this.disable_list_update = true;
bulk_operations.submit_or_cancel(
docnames,
"submit",
this.refresh
)
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
}
);
}
},
@@ -1769,12 +1823,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
});

const docnames = this.get_checked_items(true);

this.disable_list_update = true;
bulk_operations.edit(
docnames,
this.get_checked_items(true),
field_mappings,
this.refresh
() => {
this.disable_list_update = false;
this.clear_checked_items();
this.refresh();
}
);
},
standard: true,


+ 1
- 1
frappe/public/js/frappe/model/perm.js Dosyayı Görüntüle

@@ -15,7 +15,7 @@ Object.assign(window, {
});

$.extend(frappe.perm, {
rights: ["read", "write", "create", "delete", "submit", "cancel", "amend",
rights: ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
"report", "import", "export", "print", "email", "share", "set_user_permissions"],

doctype_perm: {},


+ 5
- 0
frappe/public/js/frappe/ui/keyboard.js Dosyayı Görüntüle

@@ -72,6 +72,9 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {
let current_page_shortcuts = standard_shortcuts.filter(
shortcut => shortcut.page && shortcut.page === window.cur_page.page.page);

let grid_shortcuts = standard_shortcuts.filter(
shortcut => shortcut.page && shortcut.page === window.cur_page.page.frm);

function generate_shortcuts_html(shortcuts, heading) {
if (!shortcuts.length) {
return '';
@@ -100,6 +103,7 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {

let global_shortcuts_html = generate_shortcuts_html(global_shortcuts, __('Global Shortcuts'));
let current_page_shortcuts_html = generate_shortcuts_html(current_page_shortcuts, __('Page Shortcuts'));
let grid_shortcuts_html = generate_shortcuts_html(grid_shortcuts, __('Grid Shortcuts'));

let dialog = new frappe.ui.Dialog({
title: __('Keyboard Shortcuts'),
@@ -110,6 +114,7 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {

dialog.$body.append(global_shortcuts_html);
dialog.$body.append(current_page_shortcuts_html);
dialog.$body.append(grid_shortcuts_html);
dialog.$body.append(`
<div class="text-muted">
${__('Press Alt Key to trigger additional shortcuts in Menu and Sidebar')}


+ 62
- 42
frappe/public/js/frappe/utils/datetime.js Dosyayı Görüntüle

@@ -13,33 +13,48 @@ frappe.provide("frappe.datetime");
$.extend(frappe.datetime, {
convert_to_user_tz: function(date, format) {
// format defaults to true
if(frappe.sys_defaults.time_zone) {
var date_obj = moment.tz(date, frappe.sys_defaults.time_zone).local();
// Converts the datetime string to system time zone first since the database only stores datetime in
// system time zone and then convert the string to user time zone(from User doctype).
let date_obj = null;
if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) {
date_obj = moment.tz(date, frappe.boot.time_zone.system)
.clone()
.tz(frappe.boot.time_zone.user);
} else {
var date_obj = moment(date);
date_obj = moment(date);
}

return (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);
return format === false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);
},

convert_to_system_tz: function(date, format) {
// format defaults to true

if(frappe.sys_defaults.time_zone) {
var date_obj = moment(date).tz(frappe.sys_defaults.time_zone);
// Converts the datetime string to user time zone (from User doctype) first since this fn is called in datetime which accepts datetime
// in user time zone then convert the string to user time zone.
// This is done so that only one timezone is present in database and we do not end up storing local timezone since it changes
// as per the location of user.
let date_obj = null;
if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) {
date_obj = moment.tz(date, frappe.boot.time_zone.user)
.clone()
.tz(frappe.boot.time_zone.system);
} else {
var date_obj = moment(date);
date_obj = moment(date);
}

return (format===false) ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);
return format===false ? date_obj : date_obj.format(frappe.defaultDatetimeFormat);
},

is_timezone_same: function() {
if(frappe.sys_defaults.time_zone) {
return moment().tz(frappe.sys_defaults.time_zone).utcOffset() === moment().utcOffset();
} else {
return true;
is_system_time_zone: function() {
if (frappe.boot.time_zone && frappe.boot.time_zone.system && frappe.boot.time_zone.user) {
return moment().tz(frappe.boot.time_zone.system).utcOffset() === moment().tz(frappe.boot.time_zone.user).utcOffset();
}

return true;
},

is_timezone_same: function() {
return frappe.datetime.is_system_time_zone();
},

str_to_obj: function(d) {
@@ -98,11 +113,11 @@ $.extend(frappe.datetime, {
return moment().endOf("quarter").format();
},

year_start: function(){
year_start: function() {
return moment().startOf("year").format();
},

year_end: function(){
year_end: function() {
return moment().endOf("year").format();
},

@@ -119,19 +134,25 @@ $.extend(frappe.datetime, {
},

str_to_user: function(val, only_time = false) {
if(!val) return "";

var user_time_fmt = frappe.datetime.get_user_time_fmt();
if(only_time) {
return moment(val, frappe.defaultTimeFormat)
.format(user_time_fmt);
}

var user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();
if(typeof val !== "string" || val.indexOf(" ")===-1) {
return moment(val).format(user_date_fmt);
if (!val) return "";
const user_date_fmt = frappe.datetime.get_user_date_fmt().toUpperCase();
const user_time_fmt = frappe.datetime.get_user_time_fmt();
let user_format = user_time_fmt;

if (only_time) {
let date_obj = moment(val, frappe.defaultTimeFormat);
return date_obj.format(user_format);
} else {
return moment(val, "YYYY-MM-DD HH:mm:ss").format(user_date_fmt + " " + user_time_fmt);
let date_obj = moment.tz(val, frappe.boot.time_zone.system);
if (typeof val !== "string" || val.indexOf(" ") === -1) {
user_format = user_date_fmt;
} else {
user_format = user_date_fmt + " " + user_time_fmt;
}
return date_obj
.clone()
.tz(frappe.boot.time_zone.user)
.format(user_format);
}
},

@@ -186,23 +207,22 @@ $.extend(frappe.datetime, {
},

_date: function(format, as_obj = false) {
const time_zone = frappe.sys_defaults && frappe.sys_defaults.time_zone;
let date;
if (time_zone) {
date = moment.tz(time_zone);
} else {
date = moment();
}
if (as_obj) {
return frappe.datetime.moment_to_date_obj(date);
} else {
return date.format(format);
}
/**
* Whenever we are getting now_date/datetime, always make sure dates are fetched using user time zone.
* This is to make sure that time is as per user time zone set in User doctype, If a user had to change the timezone,
* we will end up having multiple timezone by not honouring timezone in User doctype.
* This will make sure that at any point we know which timezone the user if following and not have random timezone
* when the timezone of the local machine changes.
*/
let time_zone = frappe.boot.time_zone ? frappe.boot.time_zone.user || frappe.boot.time_zone.system : frappe.sys_defaults.time_zone;
let date = moment.tz(time_zone);

return as_obj ? frappe.datetime.moment_to_date_obj(date) : date.format(format);
},

moment_to_date_obj: function(moment) {
moment_to_date_obj: function(moment_obj) {
const date_obj = new Date();
const date_array = moment.toArray();
const date_array = moment_obj.toArray();
date_obj.setFullYear(date_array[0]);
date_obj.setMonth(date_array[1]);
date_obj.setDate(date_array[2]);


+ 1
- 1
frappe/public/js/frappe/utils/pretty_date.js Dosyayı Görüntüle

@@ -6,7 +6,7 @@ function prettyDate(date, mini) {
date = new Date((date || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, ""));
}

let diff = (((new Date()).getTime() - date.getTime()) / 1000);
let diff = (((new Date(frappe.datetime.now_datetime())).getTime() - date.getTime()) / 1000);
let day_diff = Math.floor(diff / 86400);

if (isNaN(day_diff) || day_diff < 0) return '';


+ 1
- 2
frappe/public/scss/common/quill.scss Dosyayı Görüntüle

@@ -39,8 +39,7 @@

.ql-snow {
.ql-editor {
min-height: 400px;
max-height: 600px;
height: 300px;
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}


+ 3
- 0
frappe/utils/image.py Dosyayı Görüntüle

@@ -30,6 +30,9 @@ def strip_exif_data(content, content_type):

original_image = Image.open(io.BytesIO(content))
output = io.BytesIO()
# ref: https://stackoverflow.com/a/48248432
if content_type == "image/jpeg" and original_image.mode in ("RGBA", "P"):
original_image = original_image.convert("RGB")

new_image = Image.new(original_image.mode, original_image.size)
new_image.putdata(list(original_image.getdata()))


Yükleniyor…
İptal
Kaydet