diff --git a/frappe/core/doctype/communication_link/patches/copy_communication_date_to_link.py b/frappe/core/doctype/communication_link/patches/copy_communication_date_to_link.py
index 0a6cd1ee5e..563acd8f8d 100644
--- a/frappe/core/doctype/communication_link/patches/copy_communication_date_to_link.py
+++ b/frappe/core/doctype/communication_link/patches/copy_communication_date_to_link.py
@@ -6,18 +6,32 @@ def execute():
batch_size = 10_000
while True:
- frappe.db.sql(
- """
- update `tabCommunication Link` cl
- inner join `tabCommunication` c on cl.parent = c.name
- set cl.communication_date = c.communication_date
- where cl.communication_date is null
- and c.communication_date is not null
- limit %s
- """,
+ frappe.db.multisql(
+ {
+ "mariadb": """
+ update `tabCommunication Link` cl
+ inner join `tabCommunication` c on cl.parent = c.name
+ set cl.communication_date = c.communication_date
+ where cl.communication_date is null
+ and c.communication_date is not null
+ limit %s
+ """,
+ "postgres": """
+ UPDATE "tabCommunication Link"
+ SET communication_date = sub.communication_date
+ FROM (
+ SELECT cl.name, c.communication_date
+ FROM "tabCommunication Link" cl
+ JOIN "tabCommunication" c ON cl.parent = c.name
+ WHERE cl.communication_date IS NULL
+ AND c.communication_date IS NOT NULL
+ LIMIT %s
+ ) AS sub
+ WHERE "tabCommunication Link".name = sub.name
+ """,
+ },
(batch_size,),
)
-
frappe.db.commit()
if not frappe.db.sql(
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 9a13ccb3a5..5454f5ac62 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -59,6 +59,7 @@
"view_switcher",
"form_settings_section",
"form_sidebar",
+ "form_navigation_buttons",
"timeline",
"dashboard",
"show_absolute_datetime_in_timeline",
@@ -850,6 +851,12 @@
"is_virtual": 1,
"label": "Active Sessions",
"options": "User Session Display"
+ },
+ {
+ "default": "1",
+ "fieldname": "form_navigation_buttons",
+ "fieldtype": "Check",
+ "label": "Navigation Buttons"
}
],
"icon": "fa fa-user",
@@ -903,7 +910,7 @@
}
],
"make_attachments_public": 1,
- "modified": "2025-12-13 12:53:46.486021",
+ "modified": "2026-01-02 16:00:51.406511",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 4fbc28a96a..1557b90ce8 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -48,6 +48,7 @@ desk_properties = (
"bulk_actions",
"view_switcher",
"form_sidebar",
+ "form_navigation_buttons",
"timeline",
"dashboard",
)
@@ -96,6 +97,7 @@ class User(Document):
follow_created_documents: DF.Check
follow_liked_documents: DF.Check
follow_shared_documents: DF.Check
+ form_navigation_buttons: DF.Check
form_sidebar: DF.Check
full_name: DF.Data | None
gender: DF.Link | None
diff --git a/frappe/desk/page/desktop/desktop.css b/frappe/desk/page/desktop/desktop.css
index 9749e9af25..7ae5c0818b 100644
--- a/frappe/desk/page/desktop/desktop.css
+++ b/frappe/desk/page/desktop/desktop.css
@@ -446,9 +446,20 @@
bottom: 4%;
right: 4%;
z-index: 100;
- opacity: 0.1;
+ opacity: 0.5;
}
+
.desktop-edit:hover{
opacity: 1;
transition: opacity 0.3s;
+}
+
+[data-theme="dark"] .desktop-edit{
+ background-color: var(--surface-gray-3);
+ opacity: 1;
+}
+
+[data-theme="dark"] .desktop-edit:hover{
+ opacity: 0.8;
+ transition: opacity 0.3s;
}
\ No newline at end of file
diff --git a/frappe/locale/ru.po b/frappe/locale/ru.po
index dd2a0151b8..7bed58bfa8 100644
--- a/frappe/locale/ru.po
+++ b/frappe/locale/ru.po
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: developers@frappe.io\n"
"POT-Creation-Date: 2025-12-21 09:35+0000\n"
-"PO-Revision-Date: 2025-12-24 20:23\n"
+"PO-Revision-Date: 2026-01-03 23:05\n"
"Last-Translator: developers@frappe.io\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
@@ -133,7 +133,15 @@ msgid "0 - too guessable: risky password.\n"
"3 - safely unguessable: moderate protection from offline slow-hash scenario.\n"
"
\n"
"4 - very unguessable: strong protection from offline slow-hash scenario."
-msgstr ""
+msgstr "0 - слишком легко угадывается: рискованный пароль.\n"
+"
\n"
+"1 - очень легко угадывается: защита от атак с ограничением скорости. \n"
+"
\n"
+"2 - относительно легко угадывается: защита от атак без ограничения скорости.\n"
+"
\n"
+"3 - практически невозможно угадать: умеренная защита от сценариев с медленным хешированием в офлайн-режиме.\n"
+"
\n"
+"4 - очень трудно угадать: надежная защита от сценариев с медленным хешированием в офлайн-режиме."
#. Description of the 'Priority' (Int) field in DocType 'Web Page'
#: frappe/website/doctype/web_page/web_page.json
@@ -1466,7 +1474,7 @@ msgstr "Добавить новую вкладку"
#: frappe/utils/password_strength.py:191
msgid "Add numbers or special characters."
-msgstr ""
+msgstr "Добавьте цифры или специальные символы."
#: frappe/public/js/print_format_builder/PrintFormatSection.vue:125
msgid "Add page break"
@@ -5217,7 +5225,7 @@ msgstr "Распространенные имена и фамилии легко
#: frappe/utils/password_strength.py:190
msgid "Common words are easy to guess."
-msgstr ""
+msgstr "Общие слова легко угадать."
#. Name of a DocType
#. Option for the 'Communication Type' (Select) field in DocType
@@ -5912,7 +5920,7 @@ msgstr "Создать новую Канбан доску"
#: frappe/public/js/frappe/list/list_filter.js:101
msgid "Create Saved Filter"
-msgstr ""
+msgstr "Создать сохраненный фильтр"
#: frappe/core/doctype/user/user.js:271
msgid "Create User Email"
@@ -12025,7 +12033,7 @@ msgstr "Помощь HTML"
#. Description of the 'Content' (Text Editor) field in DocType 'Note'
#: frappe/desk/doctype/note/note.json
msgid "Help: To link to another record in the system, use \"/desk/note/[Note Name]\" as the Link URL. (don't use \"http://\")"
-msgstr ""
+msgstr "Помощь: Для ссылки на другую запись в системе используйте \"/desk/note/[Имя заметки]\" в качестве ссылки (не используйте \"http://\")"
#. Label of the helpful (Int) field in DocType 'Help Article'
#: frappe/website/doctype/help_article/help_article.json
@@ -17498,7 +17506,7 @@ msgstr "Не найден шаблон по пути: {0}"
#: frappe/core/page/permission_manager/permission_manager.js:362
msgid "No user has the role {0}"
-msgstr ""
+msgstr "Нет пользователя с ролью {0}"
#: frappe/public/js/frappe/form/controls/multiselect_list.js:276
msgid "No values to show"
@@ -19124,7 +19132,7 @@ msgstr "Пароль не найден для {0} {1} {2}"
#: frappe/core/doctype/user/user.py:1307
msgid "Password requirements not met"
-msgstr ""
+msgstr "Пароль не соответствует требованиям"
#: frappe/core/doctype/user/user.py:1140
msgid "Password reset instructions have been sent to {}'s email"
@@ -28580,7 +28588,7 @@ msgstr "Используйте TLS"
#: frappe/utils/password_strength.py:191
msgid "Use a few uncommon words together."
-msgstr ""
+msgstr "Используйте несколько редких слов вместе."
#: frappe/utils/password_strength.py:44
msgid "Use a few words, avoid common phrases."
@@ -29334,7 +29342,7 @@ msgstr "Посмотреть веб-сайт"
#: frappe/core/page/permission_manager/permission_manager.js:395
msgid "View all {0} users"
-msgstr ""
+msgstr "Просмотреть всех {0} пользователей"
#: frappe/www/confirm_workflow_action.html:12
msgid "View document"
@@ -29456,7 +29464,7 @@ msgstr "Предупреждение: Обновление счетчика мо
#: frappe/core/doctype/doctype/doctype.py:458
msgid "Warning: Usage of 'format:' is discouraged."
-msgstr ""
+msgstr "Внимание: использование конструкции 'format:' не рекомендуется."
#: frappe/website/doctype/help_article/templates/help_article.html:24
msgid "Was this article helpful?"
@@ -30472,7 +30480,7 @@ msgstr "Вы можете задать только 3 пользовательс
#: frappe/handler.py:184
msgid "You can only upload JPG, PNG, GIF, PDF, TXT, CSV or Microsoft documents."
-msgstr ""
+msgstr "Вы можете загружать только документы в форматах JPG, PNG, GIF, PDF, TXT, CSV или Microsoft."
#: frappe/core/doctype/data_export/exporter.py:199
msgid "You can only upload upto 5000 records in one go. (may be less in some cases)"
@@ -32285,7 +32293,7 @@ msgstr "{0} недель назад"
#: frappe/core/page/permission_manager/permission_manager.js:378
msgid "{0} with the role {1}"
-msgstr ""
+msgstr "{0} с ролью {1}"
#: frappe/public/js/frappe/utils/pretty_date.js:39
msgid "{0} y"
diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js
index e20859348a..a3b8fba88b 100644
--- a/frappe/printing/page/print/print.js
+++ b/frappe/printing/page/print/print.js
@@ -88,14 +88,16 @@ frappe.ui.form.PrintView = class {
icon: "refresh",
});
- this.page.add_action_icon(
- "es-line-filetype",
- () => {
- this.go_to_form_view();
- },
- "",
- __("Form")
- );
+ if (frappe.is_mobile()) {
+ this.page.add_button(__("Form"), () => this.go_to_form_view(), { icon: "small-file" });
+ } else {
+ this.page.add_action_icon(
+ "es-line-filetype",
+ () => this.go_to_form_view(),
+ "",
+ __("Form")
+ );
+ }
}
setup_sidebar() {
diff --git a/frappe/public/js/frappe/form/sidebar/share.js b/frappe/public/js/frappe/form/sidebar/share.js
index b771929a64..56a9af08bc 100644
--- a/frappe/public/js/frappe/form/sidebar/share.js
+++ b/frappe/public/js/frappe/form/sidebar/share.js
@@ -11,7 +11,8 @@ frappe.ui.form.Share = class Share {
}
render_sidebar() {
const shared = this.shared || this.frm.get_docinfo().shared;
- const shared_users = shared.filter(Boolean).map((s) => s.user);
+ const has_everyone = shared.some((s) => s && s.everyone);
+ const shared_users = shared.filter((s) => s && s.user && !s.everyone).map((s) => s.user);
if (this.frm.is_new()) {
this.parent.find(".share-doc-btn").hide();
@@ -26,7 +27,7 @@ frappe.ui.form.Share = class Share {
this.shares.empty();
- if (!shared_users.length) {
+ if (!shared_users.length && !has_everyone) {
this.shares.hide();
return;
}
@@ -36,7 +37,12 @@ frappe.ui.form.Share = class Share {
avatar_group.on("click", () => {
this.frm.share_doc();
});
- // REDESIGN-TODO: handle "shared with everyone"
+
+ if (has_everyone) {
+ avatar_group.prepend(
+ frappe.avatar_group(["Everyone"], 1, { align: "left", overlap: true })
+ );
+ }
this.shares.append(avatar_group);
}
show() {
diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js
index bf527655fd..1f65c44f30 100644
--- a/frappe/public/js/frappe/form/toolbar.js
+++ b/frappe/public/js/frappe/form/toolbar.js
@@ -318,9 +318,12 @@ frappe.ui.form.Toolbar = class Toolbar {
this.page.clear_menu();
if (frappe.boot.desk_settings.form_sidebar) {
- // this.make_navigation();
this.make_menu_items();
}
+
+ if (frappe.boot.desk_settings.form_navigation_buttons) {
+ this.make_navigation();
+ }
}
make_navigation() {
diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js
index 857bbcb4dd..b2ab3dc581 100644
--- a/frappe/public/js/frappe/ui/page.js
+++ b/frappe/public/js/frappe/ui/page.js
@@ -770,6 +770,7 @@ frappe.ui.Page = class Page {
if (icon) {
title = `${frappe.utils.icon(icon)} ${title}`;
}
+
let title_wrapper = this.$title_area.find(".title-text");
title_wrapper.html(title);
title_wrapper.attr("title", __(tooltip_label) || this.title);
diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js
index abe1f60922..af4345e5f4 100644
--- a/frappe/public/js/frappe/views/breadcrumbs.js
+++ b/frappe/public/js/frappe/views/breadcrumbs.js
@@ -224,7 +224,8 @@ frappe.breadcrumbs = {
if (docname.startsWith("new-" + doctype.toLowerCase().replace(/ /g, "-"))) {
docname_title = __("New {0}", [__(doctype)]);
} else {
- docname_title = doc.name;
+ let title = frappe.model.get_doc_title(doc);
+ docname_title = title || doc.name;
}
this.append_breadcrumb_element(form_route, docname_title, "title-text-form");
@@ -238,7 +239,12 @@ frappe.breadcrumbs = {
last_crumb.css("cursor", "copy");
last_crumb.click((event) => {
event.stopImmediatePropagation();
- frappe.utils.copy_to_clipboard(last_crumb.text());
+ frappe.utils.copy_to_clipboard(doc.name);
+ });
+ last_crumb.attr("title", __("Click to copy name"));
+ last_crumb.tooltip({
+ delay: { show: 100, hide: 100 },
+ trigger: "hover",
});
}
},
diff --git a/frappe/public/scss/desk/sidebar.scss b/frappe/public/scss/desk/sidebar.scss
index 65d6dcedc1..3a8c64e283 100644
--- a/frappe/public/scss/desk/sidebar.scss
+++ b/frappe/public/scss/desk/sidebar.scss
@@ -166,7 +166,7 @@
}
.nav-item {
- margin-left: 0px;
+ margin-left: -5px;
}
}
diff --git a/frappe/tests/test_sqlite_search.py b/frappe/tests/test_sqlite_search.py
index c4c528e294..8f6bfcee99 100644
--- a/frappe/tests/test_sqlite_search.py
+++ b/frappe/tests/test_sqlite_search.py
@@ -47,6 +47,8 @@ class TestSQLiteSearchAPI(IntegrationTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
+ frappe.db.delete("Note")
+ frappe.db.delete("ToDo")
cls.search = TestSQLiteSearch()
# Clean up any existing test database
cls.search.drop_index()
diff --git a/frappe/utils/error.py b/frappe/utils/error.py
index deed1deb34..3a424eff5d 100644
--- a/frappe/utils/error.py
+++ b/frappe/utils/error.py
@@ -109,9 +109,10 @@ def get_error_metadata() -> str:
metadata["form_dict"] = sanitized_dict(frappe.form_dict)
metadata["user"] = getattr(frappe.session, "user", "Unidentified")
- finally:
+ except Exception:
# We don't want to bother with exception handling *while* gathering some error's metadata
- return frappe.as_json(metadata) # noqa: B012
+ pass
+ return frappe.as_json(metadata)
def log_error_snapshot(exception: Exception):
diff --git a/frappe/workspace_sidebar/integrations.json b/frappe/workspace_sidebar/integrations.json
index c3a279c87d..aa53fcd538 100644
--- a/frappe/workspace_sidebar/integrations.json
+++ b/frappe/workspace_sidebar/integrations.json
@@ -113,7 +113,7 @@
"type": "Link"
},
{
- "child": 0,
+ "child": 1,
"collapsible": 1,
"icon": "settings",
"indent": 0,
@@ -125,7 +125,7 @@
"type": "Link"
},
{
- "child": 0,
+ "child": 1,
"collapsible": 1,
"icon": "list",
"indent": 0,
@@ -137,7 +137,7 @@
"type": "Link"
},
{
- "child": 0,
+ "child": 1,
"collapsible": 1,
"icon": "list",
"indent": 0,
@@ -149,7 +149,7 @@
"type": "Link"
}
],
- "modified": "2025-12-18 17:22:26.558605",
+ "modified": "2025-12-29 23:46:47.024937",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",