diff --git a/frappe/__init__.py b/frappe/__init__.py index 4c1873ea9b..80e19e1e1b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -652,6 +652,10 @@ def msgprint( _raise_exception() +def toast(message: str, indicator: Literal["blue", "green", "orange", "red", "yellow"] | None = None): + frappe.msgprint(message, indicator=indicator, alert=True) + + def clear_messages(): local.message_log = [] diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 34d539b576..c7af72e14c 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -84,6 +84,7 @@ "default_app", "updates_tab", "system_updates_section", + "sidebar_position", "disable_system_update_notification", "disable_change_log_notification", "backups_tab", @@ -611,7 +612,7 @@ { "fieldname": "updates_tab", "fieldtype": "Tab Break", - "label": "Updates" + "label": "Display" }, { "fieldname": "backups_tab", @@ -661,6 +662,7 @@ "label": "Store Attached PDF Document" }, { +<<<<<<< HEAD "fieldname": "app_tab", "fieldtype": "Tab Break", "label": "App" @@ -677,12 +679,23 @@ "fieldname": "rate_limit_email_link_login", "fieldtype": "Int", "label": "Rate limit for email link login" +======= + "default": "Left", + "fieldname": "sidebar_position", + "fieldtype": "Select", + "label": "Sidebar Position", + "options": "Left\nRight" +>>>>>>> 770a6df82a (fix(styles): cleaner sidebars for list and form) } ], "icon": "fa fa-cog", "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2024-08-12 17:02:41.877346", +======= + "modified": "2024-08-09 11:55:19.592343", +>>>>>>> 770a6df82a (fix(styles): cleaner sidebars for list and form) "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 135c9b9094..3cc2c517e1 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -83,12 +83,12 @@ class SystemSettings(Document): ] otp_issuer_name: DF.Data | None password_reset_limit: DF.Int - rate_limit_email_link_login: DF.Int reset_password_link_expiry_duration: DF.Duration | None reset_password_template: DF.Link | None rounding_method: DF.Literal["Banker's Rounding (legacy)", "Banker's Rounding", "Commercial Rounding"] session_expiry: DF.Data | None setup_complete: DF.Check + sidebar_position: DF.Literal["Left", "Right"] store_attached_pdf_document: DF.Check strip_exif_metadata_from_uploaded_images: DF.Check time_format: DF.Literal["HH:mm:ss", "HH:mm"] diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index b55762dc4e..58c0e8d13f 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -42,19 +42,28 @@ def follow_document(doctype, doc_name, user): ) or doctype in log_types ): - return + return False - if (not frappe.get_meta(doctype).track_changes) or user == "Administrator": - return + if not frappe.get_meta(doctype).track_changes: + frappe.toast(_("Can't follow since changes are not tracked.")) + return False + + if user == "Administrator": + frappe.toast(_("Administrator can't follow")) + return False if not frappe.db.get_value("User", user, "document_follow_notify", ignore=True, cache=True): - return + frappe.toast(_("Document follow not set.")) + return False if not is_document_followed(doctype, doc_name, user): doc = frappe.new_doc("Document Follow") doc.update({"ref_doctype": doctype, "ref_docname": doc_name, "user": user}) doc.save() - return doc + frappe.toast(_("Following document {}".format(doc_name))) + return True + + return False @frappe.whitelist() @@ -67,8 +76,9 @@ def unfollow_document(doctype, doc_name, user): ) if doc: frappe.delete_doc("Document Follow", doc[0].name) - return 1 - return 0 + frappe.toast(_("Un-following document {}".format(doc_name))) + return False + return False def get_message(doc_name, doctype, frequency, user): diff --git a/frappe/public/js/frappe/form/controls/comment.js b/frappe/public/js/frappe/form/controls/comment.js index a12da9c895..2ba4243a16 100644 --- a/frappe/public/js/frappe/form/controls/comment.js +++ b/frappe/public/js/frappe/form/controls/comment.js @@ -9,7 +9,19 @@ frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.Cont ? $(`
--
+
+-
@@ -136,35 +136,10 @@
-
+
+
+
+ -
+
+ {{ __("Tags") }}
+
+
+
-
- -
- {{ __("Show Tags") }}
+
-
+
-
+ {{ __("Show Tags") }}
+
-
- {{ __("Save Filter") }}
+
+ {{ __("Saved Filters") }}
-
+
diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js
index b6e818841f..236d3ae9a8 100644
--- a/frappe/public/js/frappe/list/list_sidebar.js
+++ b/frappe/public/js/frappe/list/list_sidebar.js
@@ -23,6 +23,7 @@ frappe.views.ListSidebar = class ListSidebar {
this.setup_list_filter();
this.setup_list_group_by();
+ this.setup_collapsible();
// do not remove
// used to trigger custom scripts
@@ -164,9 +165,30 @@ frappe.views.ListSidebar = class ListSidebar {
wrapper: this.page.sidebar.find(".list-filters"),
doctype: this.doctype,
list_view: this.list_view,
+ section_title: this.page.sidebar.find(".save-filter-section .sidebar-label"),
});
}
+ setup_collapsible() {
+ // tags and save filter sections should be collapsible
+ let sections = [
+ ["tags-section", "list-tags"],
+ ["save-filter-section", "list-filters"],
+ ];
+
+ for (let s of sections) {
+ this.page.sidebar.find(`.${s[0]} .sidebar-label`).on("click", () => {
+ let list_tags = this.page.sidebar.find("." + s[1]);
+ let icon = "#es-line-down";
+ list_tags.toggleClass("hide");
+ if (list_tags.hasClass("hide")) {
+ icon = "#es-line-right-chevron";
+ }
+ this.page.sidebar.find(`.${s[0]} .es-line use`).attr("href", icon);
+ });
+ }
+ }
+
setup_kanban_boards() {
const $dropdown = this.page.sidebar.find(".kanban-dropdown");
frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown);
diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js
index 2c4edbb056..5e485c5060 100644
--- a/frappe/public/js/frappe/ui/page.js
+++ b/frappe/public/js/frappe/ui/page.js
@@ -8,6 +8,7 @@
*
* @param {string} opts.parent [HTMLElement] Parent element
* @param {boolean} opts.single_column Whether to include sidebar
+ * @param {string} [opts.sidebar_position] Position of sidebar (default None, "Left" or "Right")
* @param {string} [opts.title] Page title
* @param {Object} [opts.make_page]
*
@@ -106,6 +107,13 @@ frappe.ui.Page = class Page {
`
);
+
+ if (this.sidebar_position === "Right") {
+ this.wrapper
+ .find(".layout-main-section-wrapper")
+ .insertBefore(this.wrapper.find(".layout-side-section"));
+ this.wrapper.find(".layout-side-section").addClass("right");
+ }
}
this.setup_page();
@@ -509,7 +517,7 @@ frappe.ui.Page = class Page {
if (standard) {
$li.appendTo(parent);
} else {
- this.divider = parent.find(".dropdown-divider");
+ this.divider = parent.find(".dropdown-divider.user-action");
if (!this.divider.length) {
this.divider = $(' ').prependTo(
parent
@@ -647,6 +655,7 @@ frappe.ui.Page = class Page {
let response = action();
me.btn_disable_enable(btn, response);
};
+
// Add actions as menu item in Mobile View
let menu_item_label = group ? `${group} > ${label}` : label;
let menu_item = this.add_menu_item(menu_item_label, _action, false, false, false);
diff --git a/frappe/public/js/frappe/views/factory.js b/frappe/public/js/frappe/views/factory.js
index 71bc92508a..e2255a0934 100644
--- a/frappe/public/js/frappe/views/factory.js
+++ b/frappe/public/js/frappe/views/factory.js
@@ -29,12 +29,12 @@ frappe.views.Factory = class Factory {
}
}
- make_page(double_column, page_name) {
- return frappe.make_page(double_column, page_name);
+ make_page(double_column, page_name, sidebar_postition) {
+ return frappe.make_page(double_column, page_name, sidebar_postition);
}
};
-frappe.make_page = function (double_column, page_name) {
+frappe.make_page = function (double_column, page_name, sidebar_position) {
if (!page_name) {
page_name = frappe.get_route_str();
}
@@ -44,6 +44,7 @@ frappe.make_page = function (double_column, page_name) {
frappe.ui.make_app_page({
parent: page,
single_column: !double_column,
+ sidebar_position: sidebar_position,
});
frappe.container.change_to(page_name);
diff --git a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
index 534fe9eb37..c1af0ff8d3 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
@@ -121,7 +121,6 @@ export default class Onboarding extends Block {
this.add_settings_button();
this.add_new_block_button();
}
- $(this.wrapper).css("padding-bottom", "20px");
return this.wrapper;
}
diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss
index 0db6bf731d..6d010bcc56 100644
--- a/frappe/public/scss/common/css_variables.scss
+++ b/frappe/public/scss/common/css_variables.scss
@@ -79,7 +79,8 @@ $disabled-input-height: 22px;
--fg-color: white;
--subtle-accent: var(--gray-50);
--subtle-fg: var(--gray-100);
- --navbar-bg: white;
+ --navbar-bg: var(--gray-50);
+ // --navbar-bg: white;
--fg-hover-color: var(--gray-100);
--card-bg: var(--fg-color);
--disabled-text-color: var(--gray-600);
diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss
index 027b355d8e..16334fbe7e 100644
--- a/frappe/public/scss/desk/dark.scss
+++ b/frappe/public/scss/desk/dark.scss
@@ -27,7 +27,7 @@ $check-icon-dark: url("data:image/svg+xml,
---
-
-
-
-
-
-
-
- ·
-
-
-
-
-
-
- Follow
-
-
-
-- - +-
+
{% if(frappe.get_form_sidebar_extension) { %} {{ frappe.get_form_sidebar_extension() }} diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index bff2f5f38a..9e17b1a3d4 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -17,6 +17,7 @@ frappe.ui.form.Toolbar = class Toolbar { this.page.clear_user_actions(); this.show_title_as_dirty(); this.set_primary_action(); + this.refresh_follow(); if (this.frm.meta.hide_toolbar) { this.page.hide_menu(); @@ -273,85 +274,87 @@ frappe.ui.form.Toolbar = class Toolbar { this.page.clear_menu(); if (frappe.boot.desk_settings.form_sidebar) { - this.make_navigation(); + // this.make_navigation(); this.make_menu_items(); } } - make_navigation() { - // Navigate - if (!this.frm.is_new() && !this.frm.meta.issingle) { - this.page.add_action_icon( - "es-line-left-chevron", - () => { - this.frm.navigate_records(1); - }, - "prev-doc", - __("Previous Document") - ); - this.page.add_action_icon( - "es-line-right-chevron", - () => { - this.frm.navigate_records(0); - }, - "next-doc", - __("Next Document") - ); - } - } - make_menu_items() { // Print - const me = this; - const p = this.frm.perm[0]; - const docstatus = cint(this.frm.doc.docstatus); - const is_submittable = frappe.model.is_submittable(this.frm.doc.doctype); + this.add_discard(); + this.add_print(); + this.add_email(); + this.add_rename(); + this.add_reload(); + this.add_delete(); + this.add_duplicate(); + this.add_new(); + this.page.add_divider(); + this.add_audit_trail(); + this.add_jump_to_field(); + this.add_show_links(); + this.add_remind_me(); + this.add_follow(); + this.add_undo_redo(); + this.add_auto_repeat(); + this.page.add_divider(); + this.make_customize_buttons(); + } - const print_settings = frappe.model.get_doc(":Print Settings", "Print Settings"); - const allow_print_for_draft = cint(print_settings.allow_print_for_draft); - const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); - - if (is_submittable && docstatus == 0 && !this.has_workflow()) { + add_discard() { + if ( + frappe.model.is_submittable(this.frm.doc.doctype) && + this.frm.doc.docstatus == 0 && + !this.has_workflow() + ) { this.page.add_menu_item( __("Discard"), function () { - me.frm._discard(); + this.frm._discard(); }, true ); } + } + + add_print() { + const print_settings = frappe.model.get_doc(":Print Settings", "Print Settings"); + const allow_print_for_draft = cint(print_settings.allow_print_for_draft); + const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); if ( - !is_submittable || - docstatus == 1 || - (allow_print_for_cancelled && docstatus == 2) || - (allow_print_for_draft && docstatus == 0) + !frappe.model.is_submittable(this.frm.doc.doctype) || + this.frm.doc.docstatus == 1 || + (allow_print_for_cancelled && this.frm.doc.docstatus == 2) || + (allow_print_for_draft && this.frm.doc.docstatus == 0) ) { - if (frappe.model.can_print(null, me.frm) && !this.frm.meta.issingle) { + if (frappe.model.can_print(null, this.frm) && !this.frm.meta.issingle) { this.page.add_menu_item( __("Print"), - function () { - me.frm.print_doc(); + () => { + this.frm.print_doc(); }, true ); this.print_icon = this.page.add_action_icon( "printer", - function () { - me.frm.print_doc(); + () => { + this.frm.print_doc(); }, "", __("Print") ); } } + } + add_email() { // email - if (frappe.model.can_email(null, me.frm) && me.frm.doc.docstatus < 2) { + if (frappe.model.can_email(null, this.frm) && this.frm.doc.docstatus < 2) { this.page.add_menu_item( __("Email"), - function () { - me.frm.email_doc(); + () => { + this.frm.email_doc(); }, true, { @@ -360,80 +363,80 @@ frappe.ui.form.Toolbar = class Toolbar { } ); } + } + add_jump_to_field() { // go to field modal this.page.add_menu_item( __("Jump to field"), - function () { - me.show_jump_to_field_dialog(); + () => { + this.show_jump_to_field_dialog(); }, true, "Ctrl+J" ); + } - // Linked With - if (!me.frm.meta.issingle) { + add_show_links() { + if (!this.frm.meta.issingle) { this.page.add_menu_item( - __("Links"), - function () { - me.show_linked_with(); + __("Show Links"), + () => { + this.show_linked_with(); }, true ); } + } - // duplicate - if (frappe.boot.user.can_create.includes(me.frm.doctype) && !me.frm.meta.allow_copy) { + add_duplicate() { + if (frappe.boot.user.can_create.includes(this.frm.doctype) && !this.frm.meta.allow_copy) { this.page.add_menu_item( __("Duplicate"), - function () { - me.frm.copy_doc(); + () => { + this.frm.copy_doc(); }, true, "Shift+D" ); } + } - // copy doc to clipboard - this.page.add_menu_item( - __("Copy to Clipboard"), - function () { - frappe.utils.copy_to_clipboard(JSON.stringify(me.frm.doc)); - }, - true - ); - - // rename + add_rename() { if (this.can_rename()) { this.page.add_menu_item( __("Rename"), - function () { - me.frm.rename_doc(); + () => { + this.frm.rename_doc(); }, true ); } + } + add_reload() { // reload this.page.add_menu_item( __("Reload"), - function () { - me.frm.reload_doc(); + () => { + this.frm.reload_doc(); }, true ); + } + add_delete() { // delete if ( - cint(me.frm.doc.docstatus) != 1 && - !me.frm.doc.__islocal && - !frappe.model.is_single(me.frm.doctype) && - frappe.model.can_delete(me.frm.doctype) + cint(this.frm.doc.docstatus) != 1 && + !this.frm.doc.__islocal && + !frappe.model.is_single(this.frm.doctype) && + frappe.model.can_delete(this.frm.doctype) ) { this.page.add_menu_item( __("Delete"), - function () { - me.frm.savetrash(); + () => { + this.frm.savetrash(); }, true, { @@ -442,7 +445,9 @@ frappe.ui.form.Toolbar = class Toolbar { } ); } + } + add_remind_me() { this.page.add_menu_item( __("Remind Me"), () => { @@ -455,7 +460,21 @@ frappe.ui.form.Toolbar = class Toolbar { condition: () => !this.frm.is_new(), } ); - // + } + + add_follow() { + if (this.frm.meta.track_changes && frappe.boot.user.document_follow_notify) { + this.follow_menu_item = this.page.add_menu_item( + __(this.get_follow_text()), + () => { + this.follow(); + }, + true + ); + } + } + + add_undo_redo() { // Undo and redo this.page.add_menu_item( __("Undo"), @@ -481,26 +500,29 @@ frappe.ui.form.Toolbar = class Toolbar { description: __("Redo last action"), } ); + } - this.make_customize_buttons(); - + add_auto_repeat() { // Auto Repeat if (this.can_repeat()) { this.page.add_menu_item( __("Repeat"), - function () { - frappe.utils.new_auto_repeat_prompt(me.frm); + () => { + frappe.utils.new_auto_repeat_prompt(this.frm); }, true ); } + } + add_new() { + let p = this.frm.perm[0]; // New if (p[CREATE] && !this.frm.meta.issingle && !this.frm.meta.in_create) { this.page.add_menu_item( - __("New {0}", [__(me.frm.doctype)]), - function () { - frappe.new_doc(me.frm.doctype, true); + __("New {0}", [__(this.frm.doctype)]), + () => { + frappe.new_doc(this.frm.doctype, true); }, true, { @@ -509,14 +531,16 @@ frappe.ui.form.Toolbar = class Toolbar { } ); } + } + add_audit_trail() { if ( this.frm.doc.amended_from && frappe.model.get_value("DocType", this.frm.doc.doctype, "track_changes") ) { this.page.add_menu_item( __("View Audit Trail"), - function () { + () => { frappe.set_route("audit-trail"); }, true @@ -783,4 +807,36 @@ frappe.ui.form.Toolbar = class Toolbar { dialog.show(); } + + follow() { + let is_followed = this.frm.get_docinfo().is_document_followed; + frappe + .call("frappe.desk.form.document_follow.update_follow", { + doctype: this.frm.doctype, + doc_name: this.frm.doc.name, + following: !is_followed, + }) + .then((r) => { + is_followed = r.message ? true : false; + + frappe.model.set_docinfo( + this.frm.doctype, + this.frm.doc.name, + "is_document_followed", + is_followed + ); + this.refresh_follow(is_followed); + }); + } + + get_follow_text(follow) { + if (follow === null) { + follow = this.frm.get_docinfo().is_document_followed; + } + return follow ? __("Unfollow") : __("Follow"); + } + + refresh_follow(follow) { + this.follow_menu_item?.text(this.get_follow_text(follow)); + } }; diff --git a/frappe/public/js/frappe/list/list_factory.js b/frappe/public/js/frappe/list/list_factory.js index 56a03dee75..943062efb2 100644 --- a/frappe/public/js/frappe/list/list_factory.js +++ b/frappe/public/js/frappe/list/list_factory.js @@ -29,7 +29,7 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory { frappe.views.list_view[me.page_name] = new view_class({ doctype: doctype, - parent: me.make_page(true, me.page_name), + parent: me.make_page(true, me.page_name, "Right"), }); me.set_cur_list(); diff --git a/frappe/public/js/frappe/list/list_filter.js b/frappe/public/js/frappe/list/list_filter.js index 7f87267f45..2f7b1ba9bc 100644 --- a/frappe/public/js/frappe/list/list_filter.js +++ b/frappe/public/js/frappe/list/list_filter.js @@ -56,9 +56,16 @@ export default class ListFilter { refresh() { this.get_list_filters().then(() => { - this.filters.length - ? this.$saved_filters_preview.show() - : this.$saved_filters_preview.hide(); + if (this.filters.length) { + // expand collapsible sections + this.wrapper.hasClass("hide") && this.section_title.trigger("click"); + this.$saved_filters_preview.show(); + } else { + // hide collapsible sections + !this.wrapper.hasClass("hide") && this.section_title.trigger("click"); + this.$saved_filters_preview.hide(); + } + const html = this.filters.map((filter) => this.filter_template(filter)); this.wrapper.find(".filter-pill").remove(); this.$saved_filters.append(html); diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html index 3c45f6ab47..0437f4af20 100644 --- a/frappe/public/js/frappe/list/list_sidebar.html +++ b/frappe/public/js/frappe/list/list_sidebar.html @@ -38,7 +38,17 @@