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 ? $(`
- ${__("Comments")} +
+ ${__("Comments")} + +
+ +
+ +
${frappe.avatar(frappe.session.user, "avatar-medium")} diff --git a/frappe/public/js/frappe/form/footer/footer.js b/frappe/public/js/frappe/form/footer/footer.js index 904caa0f2b..c8d916a925 100644 --- a/frappe/public/js/frappe/form/footer/footer.js +++ b/frappe/public/js/frappe/form/footer/footer.js @@ -7,6 +7,7 @@ frappe.ui.form.Footer = class FormFooter { this.make(); this.make_comment_box(); this.make_timeline(); + this.make_like(); // render-complete $(this.frm.wrapper).on("render_complete", () => { this.refresh(); @@ -45,8 +46,8 @@ frappe.ui.form.Footer = class FormFooter { this.frm.comment_box.set_value(""); frappe.utils.play_sound("click"); this.frm.timeline.add_timeline_item(comment_item); - this.frm.sidebar.refresh_comments_count && - this.frm.sidebar.refresh_comments_count(); + this.frm.get_docinfo().comments.push(comment); + this.refresh_comments_count(); }) .finally(() => { this.frm.comment_box.enable(); @@ -68,5 +69,46 @@ frappe.ui.form.Footer = class FormFooter { this.parent.removeClass("hide"); this.frm.timeline.refresh(); } + this.refresh_comments_count(); + this.refresh_like(); + } + + refresh_comments_count() { + let count = (this.frm.get_docinfo().comments || []).length; + this.wrapper.find(".comment-count")?.html(count ? `(${count})` : ""); + } + + make_like() { + this.like_wrapper = this.wrapper.find(".liked-by"); + this.like_icon = this.wrapper.find(".liked-by .like-icon"); + this.like_count = this.wrapper.find(".liked-by .like-count"); + frappe.ui.setup_like_popover(this.wrapper.find(".form-stats-likes"), ".like-icon"); + + this.like_icon.on("click", () => { + frappe.ui.toggle_like( + this.like_wrapper, + this.frm.doctype, + this.frm.doc.name, + function () { + this.refresh_like(); + } + ); + }); + } + + refresh_like() { + if (!this.like_icon) { + return; + } + + this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by); + const liked = frappe.ui.is_liked(this.frm.doc); + this.like_wrapper + .toggleClass("not-liked", !liked) + .toggleClass("liked", liked) + .attr("data-doctype", this.frm.doctype) + .attr("data-name", this.frm.doc.name); + + this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length); } }; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 458851e698..872fd6a7b9 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -84,6 +84,7 @@ frappe.ui.form.Form = class FrappeForm { frappe.ui.make_app_page({ parent: this.wrapper, single_column: is_single_column, + sidebar_position: "Right", }); this.page = this.wrapper.page; this.layout_main = this.page.main.get(0); diff --git a/frappe/public/js/frappe/form/sidebar/document_follow.js b/frappe/public/js/frappe/form/sidebar/document_follow.js index 57db35e3cf..03aa6f32b7 100644 --- a/frappe/public/js/frappe/form/sidebar/document_follow.js +++ b/frappe/public/js/frappe/form/sidebar/document_follow.js @@ -87,11 +87,11 @@ frappe.ui.form.DocumentFollow = class DocumentFollow { } hide_follow_section() { - this.parent.hide(); + this.parent.addClass("hidden"); } set_followers() { - this.followed_by.removeClass("hidden"); + this.parent.removeClass("hidden"); this.followed_by_label.removeClass("hidden"); this.followed_by.empty(); this.get_followed_user().then((user) => { diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 5e03fb2f93..89b49a157f 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -23,7 +23,6 @@ frappe.ui.form.Sidebar = class { .html(sidebar_content) .appendTo(this.page.sidebar.empty()); - this.comments = this.sidebar.find(".form-sidebar-stats .comments"); this.user_actions = this.sidebar.find(".user-actions"); this.image_section = this.sidebar.find(".sidebar-image-section"); this.image_wrapper = this.image_section.find(".sidebar-image-wrapper"); @@ -33,10 +32,7 @@ frappe.ui.form.Sidebar = class { this.make_shared(); this.make_tags(); - this.make_like(); - this.make_follow(); - this.bind_events(); this.setup_keyboard_shortcuts(); this.show_auto_repeat_status(); frappe.ui.form.setup_user_image_event(this.frm); @@ -44,21 +40,6 @@ frappe.ui.form.Sidebar = class { this.refresh(); } - bind_events() { - var me = this; - - // scroll to comments - this.comments.on("click", function () { - frappe.utils.scroll_to(me.frm.footer.wrapper.find(".comment-box"), true); - }); - - this.like_icon.on("click", function () { - frappe.ui.toggle_like(me.like_wrapper, me.frm.doctype, me.frm.doc.name, function () { - me.refresh_like(); - }); - }); - } - setup_keyboard_shortcuts() { // add assignment shortcut let assignment_link = this.sidebar.find(".add-assignment"); @@ -78,45 +59,62 @@ frappe.ui.form.Sidebar = class { this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags); - if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) { - let route = this.frm.doc.route; - frappe.utils.get_page_view_count(route).then((res) => { - this.sidebar - .find(".pageview-count") - .html(__("{0} Web page views", [String(res.message).bold()])); - }); - } - - this.sidebar - .find(".modified-by") - .html( - get_user_message( - this.frm.doc.modified_by, - __("You last edited this", null), - __("{0} last edited this", [get_user_link(this.frm.doc.modified_by)]) - ) + - " · " + - comment_when(this.frm.doc.modified) - ); - this.sidebar - .find(".created-by") - .html( - get_user_message( - this.frm.doc.owner, - __("You created this", null), - __("{0} created this", [get_user_link(this.frm.doc.owner)]) - ) + - " · " + - comment_when(this.frm.doc.creation) - ); - - this.refresh_like(); - this.refresh_follow(); - this.refresh_comments_count(); + this.refresh_web_view_count(); + this.refresh_creation_modified(); frappe.ui.form.set_user_image(this.frm); } } + refresh_web_view_count() { + if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) { + let route = this.frm.doc.route; + frappe.utils.get_page_view_count(route).then((res) => { + this.sidebar + .find(".pageview-count") + .html(__("{0} Web page views", [String(res.message).bold()])); + }); + } + } + + refresh_creation_modified() { + let avatar_group = frappe.avatar_group([this.frm.doc.owner, this.frm.doc.modified_by], 5, { + align: "left", + overlap: true, + }); + + this.sidebar.find(".created-modified-section").append(avatar_group); + + let creation_message = + get_user_message( + this.frm.doc.owner, + __("You created this", null), + __("{0} created this", [get_user_link(this.frm.doc.owner)]) + ) + + " · " + + comment_when(this.frm.doc.creation); + + let modified_message = + get_user_message( + this.frm.doc.modified_by, + __("You last edited this", null), + __("{0} last edited this", [get_user_link(this.frm.doc.modified_by)]) + ) + + " · " + + comment_when(this.frm.doc.modified); + + avatar_group.find(".avatar:first-child").popover({ + trigger: "hover", + html: true, + content: creation_message, + }); + + avatar_group.find(".avatar:last-child").popover({ + trigger: "hover", + html: true, + content: modified_message, + }); + } + show_auto_repeat_status() { if (this.frm.meta.allow_auto_repeat && this.frm.doc.auto_repeat) { const me = this; @@ -195,64 +193,6 @@ frappe.ui.form.Sidebar = class { this.user_actions.find(".user-action-row").remove(); } - make_like() { - this.like_wrapper = this.sidebar.find(".liked-by"); - this.like_icon = this.sidebar.find(".liked-by .like-icon"); - this.like_count = this.sidebar.find(".liked-by .like-count"); - frappe.ui.setup_like_popover(this.sidebar.find(".form-stats-likes"), ".like-icon"); - } - - make_follow() { - this.follow_button = this.sidebar.find(".form-sidebar-stats .form-follow"); - - this.follow_button.on("click", () => { - 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(() => { - frappe.model.set_docinfo( - this.frm.doctype, - this.frm.doc.name, - "is_document_followed", - !is_followed - ); - this.refresh_follow(!is_followed); - }); - }); - } - - refresh_follow(follow) { - if (follow == null) { - follow = this.frm.get_docinfo().is_document_followed; - } - this.follow_button.text(follow ? __("Unfollow") : __("Follow")); - } - - refresh_like() { - if (!this.like_icon) { - return; - } - - this.like_wrapper.attr("data-liked-by", this.frm.doc._liked_by); - const liked = frappe.ui.is_liked(this.frm.doc); - this.like_wrapper - .toggleClass("not-liked", !liked) - .toggleClass("liked", liked) - .attr("data-doctype", this.frm.doctype) - .attr("data-name", this.frm.doc.name); - - this.like_count && this.like_count.text(JSON.parse(this.frm.doc._liked_by || "[]").length); - } - - refresh_comments_count() { - let count = (this.frm.get_docinfo().comments || []).length; - this.comments.find(".comments-count").html(count); - } - refresh_image() {} make_review() { diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 7a47be6b0a..57e20afb95 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -116,8 +116,8 @@
-
` ); + + 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, .form-layout > .form-page { - border-radius: var(--border-radius-md); border: 1px solid var(--border-color); + border-top: 0px; box-shadow: none; background-color: var(--card-bg); } @@ -145,7 +145,22 @@ margin-top: var(--margin-lg); padding: 0; .comment-input-header { - @extend .head-title; + display: flex; + justify-content: space-between; + + .comment-title { + @extend .head-title; + } + .comment-count { + margin-left: 5px; + color: var(--text-light); + } + + .form-stat-likes { + display: flex; + vertical-align: middle; + } + margin-bottom: var(--margin-sm); } .comment-input-container { @@ -305,10 +320,8 @@ .form-message { position: relative; - border-radius: var(--border-radius); padding: 8px 10px; font-size: var(--text-md, 13px); - margin-bottom: var(--margin-md); &.blue { @include form-message-background("blue"); diff --git a/frappe/public/scss/desk/page.scss b/frappe/public/scss/desk/page.scss index fd5edd9106..dd619352f9 100644 --- a/frappe/public/scss/desk/page.scss +++ b/frappe/public/scss/desk/page.scss @@ -93,6 +93,8 @@ @include card($padding: 0px); box-shadow: none; border: 1px solid var(--border-color); + border-top: 0px; + border-radius: 0px; } .page-head { @@ -100,7 +102,7 @@ position: sticky; top: var(--navbar-height); background: var(--bg-color); - margin-bottom: 5px; + border-bottom: 1px solid var(--border-color); transition: 0.5s top; .page-head-content { height: var(--page-head-height); diff --git a/frappe/public/scss/desk/sidebar.scss b/frappe/public/scss/desk/sidebar.scss index 652d1cef31..bd06ce72dc 100644 --- a/frappe/public/scss/desk/sidebar.scss +++ b/frappe/public/scss/desk/sidebar.scss @@ -113,11 +113,6 @@ body[data-route^="Module"] .main-menu { li:first-child { @include flex(flex, space-between, center, null); - .form-follow { - text-transform: uppercase; - font-size: var(--text-xs, 11px); - } - use.comment-icon { fill: var(--gray-500); } @@ -125,13 +120,13 @@ body[data-route^="Module"] .main-menu { } .sidebar-image-section { - width: min(100%, 170px); + width: min(100%, 200px); cursor: pointer; border-radius: var(--border-radius-lg); .sidebar-image { height: auto; - max-height: 170px; + max-height: 200px; object-fit: cover; } @@ -182,7 +177,14 @@ body[data-route^="Module"] .main-menu { .layout-side-section { @include get_textstyle("sm", "regular"); - padding-right: 30px; + // padding-right: 30px; + padding-top: 15px; + padding-right: 5px; + + &.right { + padding-left: 5px; + padding-right: 15px; + } &.hide-sidebar { display: none; @@ -205,9 +207,16 @@ body[data-route^="Module"] .main-menu { font-size: var(--text-xs); font-weight: var(--weight-regular); margin-bottom: var(--margin-sm); + text-transform: uppercase; display: flex; align-items: center; color: var(--text-muted); + cursor: pointer; + + .es-icon { + margin-right: 4px; + } + .icon { margin: 0; margin-right: var(--margin-xs); @@ -301,6 +310,10 @@ body[data-route^="Module"] .main-menu { .list-sidebar { .sidebar-section { margin-bottom: 30px; + + a { + font-size: var(--text-xs); + } } .list-link { diff --git a/frappe/public/scss/espresso/_typography.scss b/frappe/public/scss/espresso/_typography.scss index 20d51d3dde..d7c2cf65da 100644 --- a/frappe/public/scss/espresso/_typography.scss +++ b/frappe/public/scss/espresso/_typography.scss @@ -8,7 +8,7 @@ --text-xs: 12px; --text-sm: 13px; --text-md: 13px; // alias - --text-base: 14px; + --text-base: 13px; --text-lg: 16px; --text-xl: 18px; --text-2xl: 20px; diff --git a/pyproject.toml b/pyproject.toml index bb58092aea..4268bd4422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,6 +141,7 @@ ignore = [ "F722", # syntax error in forward type annotation "W191", # indentation contains tabs "RUF001", # string contains ambiguous unicode character + "UP032", # Use f-string instead of `format` call ] typing-modules = ["frappe.types.DF"]