Merge branch 'develop' into fix-number-card
This commit is contained in:
commit
9fe994e23e
56 changed files with 927 additions and 252 deletions
|
|
@ -253,7 +253,11 @@ class LoginManager:
|
|||
):
|
||||
return
|
||||
|
||||
clear_sessions(frappe.session.user, keep_current=True)
|
||||
clear_sessions(
|
||||
frappe.session.user,
|
||||
keep_current=True,
|
||||
force=frappe.session.user != "Administrator",
|
||||
)
|
||||
|
||||
def authenticate(self, user: str | None = None, pwd: str | None = None):
|
||||
from frappe.core.doctype.user.user import User
|
||||
|
|
|
|||
|
|
@ -1116,6 +1116,7 @@ class TestGunicornWorker(IntegrationTestCase):
|
|||
time.sleep(2)
|
||||
execute_in_shell("pgrep gunicorn | xargs -L1 kill -9")
|
||||
|
||||
@unittest.skip("Flaky test")
|
||||
def test_gunicorn_ping_sync(self):
|
||||
self.spawn_gunicorn()
|
||||
path = f"http://{self.TEST_SITE}:{self.port}/api/method/ping"
|
||||
|
|
@ -1126,6 +1127,7 @@ class TestGunicornWorker(IntegrationTestCase):
|
|||
path = f"http://{self.TEST_SITE}:{self.port}/api/method/ping"
|
||||
self.assertEqual(requests.get(path).status_code, 200)
|
||||
|
||||
@unittest.skip("Flaky test")
|
||||
def test_gunicorn_idle_cpu_usage(self):
|
||||
def get_total_usage():
|
||||
process = psutil.Process(self.handle.pid)
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
# Skip timeline links if a "Sent" communication already exists
|
||||
# else will create duplicate timeline entries
|
||||
if self.sent_or_received == "Received" and self.find_one_by_filters(
|
||||
message_id=self.message_id, sent_or_received="Sent"
|
||||
message_id=self.message_id, email_account=self.email_account, sent_or_received="Sent"
|
||||
):
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -43,25 +43,34 @@ frappe.ui.form.on("File", {
|
|||
if (!frappe.utils.can_upload_public_files() && frm.doc.is_private) {
|
||||
frm.set_df_property("is_private", "read_only", 1);
|
||||
}
|
||||
|
||||
if (frm.doc.attached_to_name) {
|
||||
const field = frm.get_field("attached_to_name");
|
||||
field.$input_wrapper
|
||||
.find(".control-value")
|
||||
.html(`${frappe.utils.get_form_link(frm.doctype, frm.docname, true)}`);
|
||||
}
|
||||
},
|
||||
|
||||
preview_file: function (frm) {
|
||||
let $preview = "";
|
||||
let file_extension = frm.doc.file_type.toLowerCase();
|
||||
const full_file_url = frm.doc.file_url + "?fid=" + frm.doc.name;
|
||||
const src_url = frappe.utils.escape_html(full_file_url);
|
||||
|
||||
if (frappe.utils.is_image_file(frm.doc.file_url)) {
|
||||
if (frappe.utils.is_image_file(full_file_url)) {
|
||||
$preview = $(`<div class="img_preview">
|
||||
<img
|
||||
class="img-responsive"
|
||||
style="max-width: 500px";
|
||||
src="${frappe.utils.escape_html(frm.doc.file_url)}"
|
||||
src="${src_url}"
|
||||
onerror="${frm.toggle_display("preview", false)}"
|
||||
/>
|
||||
</div>`);
|
||||
} else if (frappe.utils.is_video_file(frm.doc.file_url)) {
|
||||
} else if (frappe.utils.is_video_file(full_file_url)) {
|
||||
$preview = $(`<div class="img_preview">
|
||||
<video width="480" height="320" controls>
|
||||
<source src="${frappe.utils.escape_html(frm.doc.file_url)}">
|
||||
<source src="${src_url}">
|
||||
${__("Your browser does not support the video element.")}
|
||||
</video>
|
||||
</div>`);
|
||||
|
|
@ -72,14 +81,14 @@ frappe.ui.form.on("File", {
|
|||
style="background:#323639;"
|
||||
width="100%"
|
||||
height="1190"
|
||||
src="${frappe.utils.escape_html(frm.doc.file_url)}" type="application/pdf"
|
||||
src="${src_url}" type="application/pdf"
|
||||
>
|
||||
</object>
|
||||
</div>`);
|
||||
} else if (file_extension === "mp3") {
|
||||
$preview = $(`<div class="img_preview">
|
||||
<audio width="480" height="60" controls>
|
||||
<source src="${frappe.utils.escape_html(frm.doc.file_url)}" type="audio/mpeg">
|
||||
<source src="${src_url}" type="audio/mpeg">
|
||||
${__("Your browser does not support the audio element.")}
|
||||
</audio >
|
||||
</div>`);
|
||||
|
|
|
|||
|
|
@ -248,7 +248,6 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Note: Multiple sessions will be allowed in case of mobile device",
|
||||
"fieldname": "deny_multiple_sessions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow only one session per user"
|
||||
|
|
@ -790,7 +789,7 @@
|
|||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:13:45.430712",
|
||||
"modified": "2026-02-24 14:27:04.763075",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -76,6 +76,18 @@ class Workspace(Document):
|
|||
|
||||
if self.public and not is_workspace_manager() and not disable_saving_as_public():
|
||||
frappe.throw(_("You need to be Workspace Manager to edit this document"))
|
||||
|
||||
if (
|
||||
not self.public
|
||||
and self.for_user
|
||||
and self.for_user != frappe.session.user
|
||||
and not is_workspace_manager()
|
||||
):
|
||||
frappe.throw(
|
||||
_("You are not allowed to edit this workspace"),
|
||||
frappe.PermissionError,
|
||||
)
|
||||
|
||||
if self.has_value_changed("title"):
|
||||
validate_route_conflict(self.doctype, self.title)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
--desktop-blur: blur(10.2px);
|
||||
--desktop-modal-width: 590px;
|
||||
--desktop-modal-height: 450px;
|
||||
--folder-thumbnail-icon-height: 12px;
|
||||
--folder-thumbnail-icon-height: 16px;
|
||||
--desktop-icon-dimension: 54px;
|
||||
--folder-icon-background-color: var(--surface-gray-1);
|
||||
--desktop-modal-radius: 30px;
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
padding:0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.icons{
|
||||
gap: 16px;
|
||||
|
|
@ -109,6 +109,10 @@
|
|||
gap: 12px;
|
||||
padding: 13px 16px 12px 16px;
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
border-width: 1px;
|
||||
border-style: dashed;
|
||||
border-color: transparent;
|
||||
}
|
||||
.desktop-icon.desktop-edit-mode .hide-button {
|
||||
display: flex;
|
||||
|
|
@ -129,6 +133,7 @@
|
|||
.icon-container{
|
||||
padding: 10px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -254,13 +259,13 @@
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.folder-icon{
|
||||
border-radius: 10px;
|
||||
background-color: var(--folder-icon-background-color) !important;
|
||||
.folder-icon {
|
||||
border-radius: 16px;
|
||||
background-color: var(--folder-icon-background-color) !important;
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.14);
|
||||
padding: 7px;
|
||||
align-items: normal;
|
||||
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.14);
|
||||
/* box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.14); */
|
||||
& .icons{
|
||||
gap: 2.1px;
|
||||
margin-top: 0px;
|
||||
|
|
@ -343,8 +348,7 @@
|
|||
}
|
||||
|
||||
.desktop-edit-mode{
|
||||
border: 1px dashed var(--outline-gray-2);
|
||||
border-radius: 20px;
|
||||
border-color: var(--outline-gray-2);
|
||||
}
|
||||
.edit-mode-buttons{
|
||||
display: none;
|
||||
|
|
@ -364,7 +368,7 @@
|
|||
:root {
|
||||
--desktop-icon-dimension: 50px;
|
||||
--desktop-icon-container: 117px;
|
||||
--folder-thumbnail-icon-height:17px;
|
||||
--folder-thumbnail-icon-height:15px;
|
||||
}
|
||||
|
||||
.desktop-container {
|
||||
|
|
@ -443,6 +447,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icons-container {
|
||||
> .icons-container {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-edit{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
|
|
|||
|
|
@ -286,7 +286,6 @@ class DesktopPage {
|
|||
this.setup_navbar();
|
||||
this.setup_awesomebar();
|
||||
this.handle_route_change();
|
||||
this.setup_edit_button();
|
||||
}
|
||||
setup_edit_button() {
|
||||
if (this.edit_mode || frappe.is_mobile()) return;
|
||||
|
|
@ -1087,11 +1086,6 @@ class DesktopIcon {
|
|||
this.folder_grid = new DesktopIconGrid({
|
||||
wrapper: this.folder_wrapper,
|
||||
icons_data: this.child_icons,
|
||||
row_size: 3,
|
||||
page_size: {
|
||||
row: 3,
|
||||
col: 3,
|
||||
},
|
||||
in_folder: true,
|
||||
in_modal: false,
|
||||
no_dragging: true,
|
||||
|
|
|
|||
|
|
@ -521,12 +521,14 @@ class TestInboundMail(IntegrationTestCase):
|
|||
|
||||
def test_mail_exist_validation(self):
|
||||
"""Do not create communication record if the mail is already downloaded into the system."""
|
||||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
|
||||
mail_content = self.get_test_mail(fname="incoming-1.raw")
|
||||
message_id = Email(mail_content).message_id
|
||||
# Create new communication record in DB
|
||||
communication = self.new_communication(message_id=message_id, sent_or_received="Received")
|
||||
communication = self.new_communication(
|
||||
message_id=message_id, email_account=email_account.name, sent_or_received="Received"
|
||||
)
|
||||
|
||||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
|
||||
inbound_mail = InboundMail(mail_content, email_account, 12345, 1)
|
||||
new_communication = inbound_mail.process()
|
||||
|
||||
|
|
|
|||
|
|
@ -514,7 +514,18 @@ def send_now(name: str | int, force_send: bool = False):
|
|||
@frappe.whitelist()
|
||||
def toggle_sending(enable: bool | int | str):
|
||||
frappe.only_for("System Manager")
|
||||
frappe.db.set_default("suspend_email_queue", 0 if sbool(enable) else 1)
|
||||
suspend_value = 0 if sbool(enable) else 1
|
||||
frappe.db.set_default("suspend_email_queue", suspend_value)
|
||||
|
||||
action = "Resumed" if suspend_value == 0 else "Suspended"
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Activity Log",
|
||||
"user": frappe.session.user,
|
||||
"status": "Success",
|
||||
"subject": f"Email Queue sending {action.lower()}",
|
||||
}
|
||||
).insert(ignore_permissions=True, ignore_links=True)
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
|
|
|
|||
|
|
@ -751,7 +751,10 @@ class InboundMail(Email):
|
|||
return
|
||||
|
||||
return Communication.find_one_by_filters(
|
||||
message_id=self.message_id, sent_or_received="Received", order_by="creation DESC"
|
||||
message_id=self.message_id,
|
||||
email_account=self.email_account.name,
|
||||
sent_or_received="Received",
|
||||
order_by="creation DESC",
|
||||
)
|
||||
|
||||
def is_sender_same_as_receiver(self):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: developers@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-02-22 09:42+0000\n"
|
||||
"PO-Revision-Date: 2026-02-23 22:07\n"
|
||||
"PO-Revision-Date: 2026-02-25 23:15\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Bosnian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2870,7 +2870,7 @@ msgstr "Dodjela je Završena"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Dani Dodjeljivanja"
|
||||
msgstr "Dani Dodjele"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
@ -2888,7 +2888,7 @@ msgstr "Dan Dodjele Pravila"
|
|||
#. Name of a DocType
|
||||
#: frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json
|
||||
msgid "Assignment Rule User"
|
||||
msgstr "Korisnik Dodjele Pravila"
|
||||
msgstr "Korisnik Pravila Dodjele"
|
||||
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.py:55
|
||||
msgid "Assignment Rule is not allowed on document type {0}"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: developers@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-02-22 09:42+0000\n"
|
||||
"PO-Revision-Date: 2026-02-23 22:07\n"
|
||||
"PO-Revision-Date: 2026-02-25 23:14\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Persian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -6314,7 +6314,7 @@ msgstr "سفارشیسازی"
|
|||
|
||||
#: frappe/custom/doctype/customize_form/customize_form.js:89
|
||||
msgid "Customize Child Table"
|
||||
msgstr "سفارشی کردن جدول فرزند"
|
||||
msgstr "سفارشیسازی جدول فرزند"
|
||||
|
||||
#: frappe/public/js/frappe/views/dashboard/dashboard_view.js:38
|
||||
msgid "Customize Dashboard"
|
||||
|
|
@ -6339,7 +6339,7 @@ msgstr "سفارشیسازی فرم - {0}"
|
|||
#. Name of a DocType
|
||||
#: frappe/custom/doctype/customize_form_field/customize_form_field.json
|
||||
msgid "Customize Form Field"
|
||||
msgstr "سفارشی کردن فیلد فرم"
|
||||
msgstr "سفارشیسازی فیلد فرم"
|
||||
|
||||
#: frappe/public/js/frappe/list/list_view.js:1994
|
||||
msgctxt "Customize qucik filters of List View"
|
||||
|
|
@ -18808,7 +18808,7 @@ msgstr ""
|
|||
|
||||
#: frappe/core/doctype/doctype/doctype.py:1699
|
||||
msgid "Options for Rating field can range from 3 to 10"
|
||||
msgstr "گزینههای فیلد رتبه بندی میتواند از 3 تا 10 باشد"
|
||||
msgstr "گزینههای فیلد رتبهبندی میتواند از 3 تا 10 باشد"
|
||||
|
||||
#: frappe/custom/doctype/custom_field/custom_field.js:96
|
||||
msgid "Options for select. Each option on a new line."
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: developers@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-02-22 09:42+0000\n"
|
||||
"PO-Revision-Date: 2026-02-23 22:07\n"
|
||||
"PO-Revision-Date: 2026-02-25 23:14\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2870,7 +2870,7 @@ msgstr "Dodjela je Završena"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Dani Dodjeljivanja"
|
||||
msgstr "Dani Dodjele"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: developers@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-02-22 09:42+0000\n"
|
||||
"PO-Revision-Date: 2026-02-23 22:07\n"
|
||||
"PO-Revision-Date: 2026-02-25 23:14\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Swedish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2868,7 +2868,7 @@ msgstr "Tilldelning Klar"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Automation Dagar"
|
||||
msgstr "Tilldelning Dagar"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
@ -2876,7 +2876,7 @@ msgstr "Automation Dagar"
|
|||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
#: frappe/desk/doctype/todo/todo.json frappe/workspace_sidebar/automation.json
|
||||
msgid "Assignment Rule"
|
||||
msgstr "Automation Regel"
|
||||
msgstr "Tilldelning Regel"
|
||||
|
||||
#. Name of a DocType
|
||||
#: frappe/automation/doctype/assignment_rule_day/assignment_rule_day.json
|
||||
|
|
@ -2890,13 +2890,13 @@ msgstr "Automation Regel Användare"
|
|||
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.py:55
|
||||
msgid "Assignment Rule is not allowed on document type {0}"
|
||||
msgstr "Automation Regel är ej tillåten på dokument typ {0}"
|
||||
msgstr "Tilldelning Regel är ej tillåten på dokument typ {0}"
|
||||
|
||||
#. Label of the assignment_rules_section (Section Break) field in DocType
|
||||
#. 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Rules"
|
||||
msgstr "Automation Regler"
|
||||
msgstr "Tilldelning Regler"
|
||||
|
||||
#: frappe/desk/doctype/notification_log/notification_log.py:153
|
||||
msgid "Assignment Update on {0}"
|
||||
|
|
|
|||
|
|
@ -100,9 +100,6 @@ def delete_doc(
|
|||
else:
|
||||
return False
|
||||
|
||||
# delete passwords
|
||||
delete_all_passwords_for(doctype, name)
|
||||
|
||||
doc = None
|
||||
if doctype == "DocType":
|
||||
if for_reload:
|
||||
|
|
@ -200,6 +197,9 @@ def delete_doc(
|
|||
enqueue_after_commit=True,
|
||||
)
|
||||
|
||||
# delete passwords
|
||||
delete_all_passwords_for(doctype, name)
|
||||
|
||||
# clear cache for Document
|
||||
doc.clear_cache()
|
||||
# delete global search entry
|
||||
|
|
|
|||
|
|
@ -793,11 +793,21 @@ class Meta(Document):
|
|||
group.get("items").append(doctype)
|
||||
link.added = True
|
||||
|
||||
# Add fieldname to transaction group for external links
|
||||
if not link.is_child_table:
|
||||
if "fieldnames" not in group:
|
||||
group["fieldnames"] = {}
|
||||
group["fieldnames"][link.link_doctype] = link.link_fieldname
|
||||
|
||||
if not link.added:
|
||||
# group not found, make a new group
|
||||
data.transactions.append(
|
||||
dict(label=link.group, items=[link.parent_doctype or link.link_doctype])
|
||||
)
|
||||
new_group = dict(label=link.group, items=[link.parent_doctype or link.link_doctype])
|
||||
|
||||
# Add fieldname to new transaction group for external links
|
||||
if not link.is_child_table:
|
||||
new_group["fieldnames"] = {link.link_doctype: link.link_fieldname}
|
||||
|
||||
data.transactions.append(new_group)
|
||||
|
||||
if not data.fieldname and link.link_fieldname:
|
||||
data.fieldname = link.link_fieldname
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ import base64
|
|||
import datetime
|
||||
import hashlib
|
||||
import re
|
||||
from http import cookies
|
||||
from urllib.parse import unquote, urljoin, urlparse
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
from oauthlib.common import Request
|
||||
from oauthlib.openid import RequestValidator
|
||||
|
||||
import frappe
|
||||
from frappe.auth import LoginManager
|
||||
from frappe.integrations.doctype.oauth_client.oauth_client import OAuthClient
|
||||
from frappe.utils.data import cstr, get_system_timezone, now_datetime
|
||||
|
||||
|
||||
|
|
@ -73,13 +74,11 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Post-authorization
|
||||
|
||||
def save_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
|
||||
oac = frappe.new_doc("OAuth Authorization Code")
|
||||
oac.scopes = get_url_delimiter().join(request.scopes)
|
||||
oac.redirect_uri_bound_to_authorization_code = request.redirect_uri
|
||||
oac.client = client_id
|
||||
oac.user = unquote(cookie_dict["user_id"].value)
|
||||
oac.user = frappe.session.user
|
||||
oac.authorization_code = code["code"]
|
||||
|
||||
if request.nonce:
|
||||
|
|
@ -92,43 +91,32 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
oac.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def authenticate_client(self, request, *args, **kwargs):
|
||||
def authenticate_client(self, request: Request, *args, **kwargs) -> bool | None:
|
||||
"""
|
||||
Loads the client based on request parameters and sets in oauth request.
|
||||
Returns True on success, None on error.
|
||||
"""
|
||||
# Get ClientID in URL
|
||||
if request.client_id:
|
||||
oc = frappe.get_doc("OAuth Client", request.client_id)
|
||||
client_name = request.client_id
|
||||
else:
|
||||
# Extract token, instantiate OAuth Bearer Token and use clientid from there.
|
||||
if "refresh_token" in frappe.form_dict:
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
{"refresh_token": frappe.form_dict["refresh_token"]},
|
||||
"client",
|
||||
),
|
||||
)
|
||||
token_filters = {"refresh_token": frappe.form_dict["refresh_token"]}
|
||||
elif "token" in frappe.form_dict:
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], "client"),
|
||||
)
|
||||
token_filters = {"name": frappe.form_dict["token"]}
|
||||
else:
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
frappe.get_request_header("Authorization").split(" ")[1],
|
||||
"client",
|
||||
),
|
||||
)
|
||||
token_filters = {"name": frappe.get_request_header("Authorization").split(" ")[1]}
|
||||
|
||||
client_name = frappe.db.get_value("OAuth Bearer Token", filters=token_filters, fieldname="client")
|
||||
|
||||
oc: OAuthClient = frappe.get_doc("OAuth Client", client_name)
|
||||
try:
|
||||
request.client = request.client or oc.as_dict()
|
||||
except Exception as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
user_id = unquote(cookie_dict.get("user_id").value) if "user_id" in cookie_dict else "Guest"
|
||||
return frappe.session.user == user_id
|
||||
return True
|
||||
|
||||
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
||||
cli_id = frappe.db.get_value("OAuth Client", client_id, "name")
|
||||
|
|
@ -506,13 +494,6 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
return True
|
||||
|
||||
|
||||
def get_cookie_dict_from_headers(r):
|
||||
cookie = cookies.BaseCookie()
|
||||
if r.headers.get("Cookie"):
|
||||
cookie.load(r.headers.get("Cookie"))
|
||||
return cookie
|
||||
|
||||
|
||||
def calculate_at_hash(access_token, hash_alg):
|
||||
"""Helper method for calculating an access token
|
||||
hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@
|
|||
"description": "Letter Head in HTML",
|
||||
"fieldname": "content",
|
||||
"fieldtype": "HTML Editor",
|
||||
"ignore_xss_filter": 1,
|
||||
"label": "Header HTML",
|
||||
"oldfieldname": "content",
|
||||
"oldfieldtype": "Text Editor"
|
||||
|
|
@ -113,6 +114,7 @@
|
|||
"description": "Footer will display correctly only in PDF",
|
||||
"fieldname": "footer",
|
||||
"fieldtype": "HTML Editor",
|
||||
"ignore_xss_filter": 1,
|
||||
"label": "Footer HTML"
|
||||
},
|
||||
{
|
||||
|
|
@ -184,6 +186,7 @@
|
|||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.header_script || doc.footer_script",
|
||||
"depends_on": "eval: !doc.__islocal",
|
||||
"fieldname": "scripts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scripts"
|
||||
|
|
@ -200,7 +203,7 @@
|
|||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"max_attachments": 3,
|
||||
"modified": "2024-04-12 10:30:25.793932",
|
||||
"modified": "2026-02-24 20:53:14.297567",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Letter Head",
|
||||
|
|
@ -223,8 +226,9 @@
|
|||
"role": "Desk User"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -706,7 +706,10 @@ frappe.ui.form.PrintView = class {
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
this.is_wkhtmltopdf_valid();
|
||||
let pdf_generator = this.get_pdf_generator(print_format?.pdf_generator);
|
||||
if (pdf_generator === "wkhtmltopdf") {
|
||||
this.is_wkhtmltopdf_valid();
|
||||
}
|
||||
this.render_page(
|
||||
"/api/method/frappe.utils.print_format.download_pdf?",
|
||||
false,
|
||||
|
|
@ -738,9 +741,7 @@ frappe.ui.form.PrintView = class {
|
|||
encodeURIComponent(this.get_letterhead()) +
|
||||
"&settings=" +
|
||||
encodeURIComponent(JSON.stringify(this.additional_settings)) +
|
||||
(this.lang_code ? "&_lang=" + this.lang_code : "") +
|
||||
"&pdf_generator=" +
|
||||
encodeURIComponent(pdf_generator)
|
||||
(this.lang_code ? "&_lang=" + this.lang_code : "")
|
||||
)
|
||||
);
|
||||
if (!w) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ $(document).ready(function () {
|
|||
dismiss_key: `${frappe.boot.site_info.name}_trial_card_time`,
|
||||
dismiss_it_for: "day",
|
||||
};
|
||||
let visiblity_condition =
|
||||
frappe.boot.is_fc_site &&
|
||||
!!frappe.boot.setup_complete &&
|
||||
!frappe.is_mobile() &&
|
||||
frappe.user.has_role("System Manager");
|
||||
if (visiblity_condition && isFCUser) {
|
||||
addChatBubble();
|
||||
}
|
||||
if (isFCUser) {
|
||||
$.extend(card_args, {
|
||||
primary_action_label: "Upgrade",
|
||||
|
|
@ -42,12 +50,7 @@ $(document).ready(function () {
|
|||
});
|
||||
}
|
||||
$(document).on("desktop_screen", function (event, data) {
|
||||
if (
|
||||
frappe.boot.is_fc_site &&
|
||||
!!frappe.boot.setup_complete &&
|
||||
!frappe.is_mobile() &&
|
||||
frappe.user.has_role("System Manager")
|
||||
) {
|
||||
if (visiblity_condition) {
|
||||
if (site_info.trial_end_date && trial_end_date > new Date()) {
|
||||
card_args.parent = $(".icons-container").first();
|
||||
let banner_card = new frappe.ui.SidebarCard(card_args);
|
||||
|
|
@ -84,3 +87,25 @@ function openFrappeCloudDashboard() {
|
|||
"_blank"
|
||||
);
|
||||
}
|
||||
|
||||
function addChatBubble() {
|
||||
const all_apps = frappe.utils.get_installed_apps();
|
||||
const desk_apps = ["erpnext", "hrms"];
|
||||
|
||||
const apps_allowed = frappe.utils.is_sub_array(all_apps, desk_apps);
|
||||
if (checkBusinessHours && apps_allowed) {
|
||||
let chat_banner = document.createElement("script");
|
||||
chat_banner.innerHTML =
|
||||
'(function(d,t){var BASE_URL="https://chat.frappe.cloud";var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src=BASE_URL+"/packs/js/sdk.js";g.async=true;s.parentNode.insertBefore(g,s);g.onload=function(){window.chatwootSDK.run({websiteToken:"LdmfJzftdJGEcFjoTqk8CrSq",baseUrl:BASE_URL})}})(document,"script");';
|
||||
document.body.append(chat_banner);
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty("--s-700", "var(--gray-500)");
|
||||
}
|
||||
}
|
||||
|
||||
function checkBusinessHours() {
|
||||
let currentTime = new Date();
|
||||
const istTime = new Date(currentTime.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
|
||||
|
||||
return istTime.getHours() >= 11 && istTime.getHours() < 18;
|
||||
}
|
||||
|
|
|
|||
2
frappe/public/js/bootstrap-4-web.bundle.js
vendored
2
frappe/public/js/bootstrap-4-web.bundle.js
vendored
|
|
@ -25,7 +25,7 @@ frappe.get_modal = function (title, content) {
|
|||
<div class="modal-header">
|
||||
<h5 class="modal-title">${title}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
${frappe.utils.icon("close-alt", "sm", "close-alt")}
|
||||
${frappe.utils.icon("x", "sm", "close-alt")}
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
|
|||
|
|
@ -324,8 +324,8 @@ frappe.get_data_pill = (
|
|||
`);
|
||||
if (remove_action) {
|
||||
let remove_btn = $(`
|
||||
<span class="remove-btn cursor-pointer">
|
||||
${frappe.utils.icon("close", "sm", "es-icon")}
|
||||
<span class="remove-btn cursor-pointer flex align-items-center">
|
||||
${frappe.utils.icon("x", "sm")}
|
||||
</span>
|
||||
`);
|
||||
if (typeof remove_action === "function") {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ frappe.ui.form.ControlButton = class ControlButton extends frappe.ui.form.Contro
|
|||
this.$input = $(
|
||||
`<button
|
||||
class="btn ${frappe.utils.escape_html(btn_size)} ${frappe.utils.escape_html(btn_type)}"
|
||||
title="${frappe.utils.escape_html(this.df.label)}"
|
||||
title="${this.df.title || frappe.utils.escape_html(this.df.label)}"
|
||||
>`
|
||||
)
|
||||
.prependTo(me.input_area)
|
||||
|
|
|
|||
|
|
@ -270,6 +270,13 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
if (d.label == group.label) {
|
||||
group_added.push(d.label);
|
||||
group.items.push(...d.items);
|
||||
|
||||
if (d.fieldnames) {
|
||||
if (!group.fieldnames) {
|
||||
group.fieldnames = {};
|
||||
}
|
||||
Object.assign(group.fieldnames, d.fieldnames);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -335,7 +342,9 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
|
||||
// bind new
|
||||
transactions_area_body.find(".btn-new").on("click", function () {
|
||||
me.frm.make_new($(this).attr("data-doctype"));
|
||||
const doctype = $(this).attr("data-doctype");
|
||||
const fieldname = $(this).attr("data-fieldname");
|
||||
me.frm.make_new(doctype, fieldname);
|
||||
});
|
||||
|
||||
this.data_rendered = true;
|
||||
|
|
@ -369,6 +378,11 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
let doctype = $link.attr("data-doctype"),
|
||||
names = $link.attr("data-names") || [];
|
||||
|
||||
const fieldname =
|
||||
$link.find(".document-link-badge").attr("data-fieldname") ||
|
||||
(this.data.non_standard_fieldnames && this.data.non_standard_fieldnames[doctype]) ||
|
||||
this.data.fieldname;
|
||||
|
||||
if (
|
||||
this.internal_links_found &&
|
||||
this.internal_links_found.find((d) => d.doctype === doctype)
|
||||
|
|
@ -378,8 +392,8 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (this.data.fieldname) {
|
||||
frappe.route_options = this.get_document_filter(doctype);
|
||||
} else if (fieldname) {
|
||||
frappe.route_options = this.get_document_filter(doctype, fieldname);
|
||||
if (show_open && frappe.ui.notifications) {
|
||||
frappe.ui.notifications.show_open_count_list(doctype);
|
||||
}
|
||||
|
|
@ -388,13 +402,10 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
frappe.set_route("List", doctype, "List");
|
||||
}
|
||||
|
||||
get_document_filter(doctype) {
|
||||
get_document_filter(doctype, fieldname) {
|
||||
// return the default filter for the given document
|
||||
// like {"customer": frm.doc.name}
|
||||
let filter = {};
|
||||
let fieldname = this.data.non_standard_fieldnames
|
||||
? this.data.non_standard_fieldnames[doctype] || this.data.fieldname
|
||||
: this.data.fieldname;
|
||||
const filter = {};
|
||||
|
||||
if (this.data.dynamic_links && this.data.dynamic_links[fieldname]) {
|
||||
let dynamic_fieldname = this.data.dynamic_links[fieldname][1];
|
||||
|
|
|
|||
|
|
@ -783,6 +783,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
this.show_submit_message();
|
||||
this.clear_custom_buttons();
|
||||
this.show_web_link();
|
||||
this.show_report_bug_link();
|
||||
this.show_workflow_read_only_banner();
|
||||
}
|
||||
|
||||
|
|
@ -1280,6 +1281,17 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
}
|
||||
|
||||
show_report_bug_link() {
|
||||
if (this.meta.beta) {
|
||||
this.add_web_link(
|
||||
"https://github.com/frappe/" +
|
||||
frappe.boot.module_app[frappe.scrub(this.meta.module)] +
|
||||
"/issues/new",
|
||||
__("Report bug")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
add_web_link(path, label) {
|
||||
label = __(label) || __("See on Website");
|
||||
this.web_link = this.sidebar
|
||||
|
|
@ -2007,7 +2019,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
}
|
||||
|
||||
make_new(doctype) {
|
||||
make_new(doctype, fieldname) {
|
||||
// make new doctype from the current form
|
||||
// will handover to `make_methods` if defined
|
||||
// or will create and match link fields
|
||||
|
|
@ -2021,7 +2033,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
let new_doc = frappe.model.get_new_doc(doctype, null, null, true);
|
||||
|
||||
// set link fields (if found)
|
||||
me.set_link_field(doctype, new_doc);
|
||||
me.set_link_field(doctype, new_doc, fieldname);
|
||||
|
||||
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
|
||||
// frappe.set_route('Form', doctype, new_doc.name);
|
||||
|
|
@ -2029,16 +2041,27 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
}
|
||||
|
||||
set_link_field(doctype, new_doc) {
|
||||
set_link_field(doctype, new_doc, fieldname) {
|
||||
let me = this;
|
||||
frappe.get_meta(doctype).fields.forEach(function (df) {
|
||||
if (df.fieldtype === "Link" && df.options === me.doctype) {
|
||||
const isLinkToParent = df.fieldtype === "Link" && df.options === me.doctype;
|
||||
|
||||
if (fieldname) {
|
||||
if (df.fieldname === fieldname && isLinkToParent) {
|
||||
new_doc[df.fieldname] = me.doc.name;
|
||||
}
|
||||
if (df.fieldtype === "Table" && df.options && df.reqd) {
|
||||
me.set_link_field(df.options, new_doc[df.fieldname][0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLinkToParent) {
|
||||
new_doc[df.fieldname] = me.doc.name;
|
||||
} else if (["Link", "Dynamic Link"].includes(df.fieldtype) && me.doc[df.fieldname]) {
|
||||
new_doc[df.fieldname] = me.doc[df.fieldname];
|
||||
} else if (df.fieldtype === "Table" && df.options && df.reqd) {
|
||||
let row = new_doc[df.fieldname][0];
|
||||
me.set_link_field(df.options, row);
|
||||
me.set_link_field(df.options, new_doc[df.fieldname][0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ frappe.ui.form.Layout = class Layout {
|
|||
}
|
||||
|
||||
// Add close button to block if not permanent
|
||||
const close_message = $(`<div class="close-message">${frappe.utils.icon("close")}</div>`);
|
||||
const close_message = $(`<div class="close-message">${frappe.utils.icon("x")}</div>`);
|
||||
if (!permanent) {
|
||||
close_message.appendTo($html);
|
||||
close_message.on("click", () => $html.remove());
|
||||
|
|
|
|||
|
|
@ -285,8 +285,8 @@ frappe.ui.form.AssignmentClass = class AssignmentClass {
|
|||
const row = $(`
|
||||
<div class="dialog-assignment-row" data-user="${assignment}">
|
||||
<div class="assignee">
|
||||
${frappe.avatar(assignment)}
|
||||
${frappe.user.full_name(assignment)}
|
||||
${frappe.avatar(assignment, "avatar-smaller")}
|
||||
<span>${frappe.user.full_name(assignment)}</span>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
|
||||
</div>
|
||||
|
|
@ -297,8 +297,8 @@ frappe.ui.form.AssignmentClass = class AssignmentClass {
|
|||
|
||||
if (assignment === frappe.session.user) {
|
||||
btn_group.append(`
|
||||
<button type="button" class="btn complete-btn" title="${__("Done")}">
|
||||
${frappe.utils.icon("tick", "xs")}
|
||||
<button type="button" class="btn btn-xs complete-btn" title="${__("Done")}">
|
||||
${frappe.utils.icon("check")}
|
||||
</button>
|
||||
`);
|
||||
btn_group.find(".complete-btn").click(() => {
|
||||
|
|
@ -324,7 +324,7 @@ frappe.ui.form.AssignmentClass = class AssignmentClass {
|
|||
|
||||
if (assignment === frappe.session.user || this.frm.perm[0].write) {
|
||||
btn_group.append(`
|
||||
<button type="button" class="btn remove-btn" title="${__("Cancel")}">
|
||||
<button type="button" class="btn btn-xs remove-btn" title="${__("Cancel")}">
|
||||
${frappe.utils.icon("x")}
|
||||
</button>
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ frappe.ui.form.Sidebar = class {
|
|||
.appendTo(this.page.sidebar.empty());
|
||||
|
||||
this.user_actions = this.sidebar.find(".user-actions");
|
||||
this.user_actions_list = this.sidebar.find(".user-actions-list");
|
||||
this.image_section = this.sidebar.find(".sidebar-image-section");
|
||||
this.image_wrapper = this.image_section.find(".sidebar-image-wrapper");
|
||||
this.make_assignments();
|
||||
|
|
@ -115,7 +116,6 @@ frappe.ui.form.Sidebar = class {
|
|||
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");
|
||||
|
||||
this.like_icon.on("click", () => {
|
||||
|
|
@ -138,8 +138,6 @@ frappe.ui.form.Sidebar = class {
|
|||
.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_web_view_count() {
|
||||
|
|
@ -245,19 +243,23 @@ frappe.ui.form.Sidebar = class {
|
|||
}
|
||||
|
||||
add_user_action(label, click) {
|
||||
return $("<a>")
|
||||
.html(label)
|
||||
.appendTo(
|
||||
$('<div class="user-action-row"></div>').appendTo(
|
||||
this.user_actions.removeClass("hidden")
|
||||
)
|
||||
const parent = this.user_actions_list.length ? this.user_actions_list : this.user_actions;
|
||||
this.user_actions.removeClass("hidden");
|
||||
const row = $('<div class="user-action-row"></div>').appendTo(parent);
|
||||
|
||||
return $('<a class="user-action-link"></a>')
|
||||
.html(
|
||||
`<span class="user-action-label">${label}</span>
|
||||
<span class="user-action-external-icon">${frappe.utils.icon("external-link", "sm")}</span>`
|
||||
)
|
||||
.appendTo(row)
|
||||
.on("click", click);
|
||||
}
|
||||
|
||||
clear_user_actions() {
|
||||
this.user_actions.addClass("hidden");
|
||||
this.user_actions.find(".user-action-row").remove();
|
||||
const parent = this.user_actions_list.length ? this.user_actions_list : this.user_actions;
|
||||
parent.find(".user-action-row").remove();
|
||||
}
|
||||
|
||||
refresh_image() {}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@
|
|||
</div>
|
||||
{% for (let j=0; j < transactions[i].items.length; j++) { %}
|
||||
{% let doctype = transactions[i].items[j]; %}
|
||||
{% let fieldname = (transactions[i].fieldnames && transactions[i].fieldnames[doctype]) || transactions[i].fieldname; %}
|
||||
<div class="document-link" data-doctype="{{ doctype }}">
|
||||
<div class="document-link-badge" data-doctype="{{ doctype }}">
|
||||
<div class="document-link-badge" data-doctype="{{ doctype }}" data-fieldname="{{ fieldname }}">
|
||||
<a class="badge-link">{{ __(doctype) }}</a>
|
||||
<span class="count hidden"></span>
|
||||
</div>
|
||||
|
|
@ -19,7 +20,8 @@
|
|||
</span>
|
||||
{% if !internal_links[doctype] %}
|
||||
<button class="btn btn-new btn-secondary btn-xs icon-btn hidden"
|
||||
data-doctype="{{ doctype }}">
|
||||
data-doctype="{{ doctype }}"
|
||||
data-fieldname="{{ fieldname }}">
|
||||
<svg class="icon icon-sm"><use href="#icon-add"></use></svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
<div class="sidebar-section user-actions hidden"></div>
|
||||
<div class="flex justify-between sidebar-image-section sidebar-section hide">
|
||||
<div class="sidebar-image-wrapper">
|
||||
<img class="sidebar-image">
|
||||
|
|
@ -25,7 +24,6 @@
|
|||
<svg class="icon icon-sm like-icon pointer">
|
||||
<use href="#icon-heart"></use>
|
||||
</svg>
|
||||
<span class="like-count ml-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -34,14 +32,19 @@
|
|||
<div class="flex justify-between overflow-hidden">
|
||||
<div class="ellipsis">
|
||||
{% let title = frm.get_title(); %}
|
||||
{% if (title && title !== frm.doc.name) { %}
|
||||
<div class="form-details flex justify-between">
|
||||
<span class="bold ellipsis form-title-text mr-3 text-medium">{%= frappe.utils.escape_html(frappe.utils.html2text(title)) %}</span>
|
||||
</div>
|
||||
{% } %}
|
||||
<div class="form-name-container mt-2 flex justify-between form-name-copy" data-copy="{{frm.doc.name}}" title="{{frm.doc.name}}" >
|
||||
{% if frm.meta.beta %}
|
||||
<div class="pt-1">
|
||||
<label class="indicator-pill yellow mb-0" title="{{ __("This feature is brand new and still experimental") }}">{{ __("Experimental") }}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (title && title !== frm.doc.name) { %}
|
||||
<div class="form-name-container mt-1 flex justify-between form-name-copy" data-copy="{{frm.doc.name}}" title="{{frm.doc.name}}" >
|
||||
<span class="ellipsis mr-3">{%= frm.doc.name %}</span>
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
{% if not image_field %}
|
||||
<div class="align-items-baseline flex form-stats-likes">
|
||||
|
|
@ -51,21 +54,10 @@
|
|||
<svg class="icon icon-sm like-icon pointer">
|
||||
<use href="#icon-heart"></use>
|
||||
</svg>
|
||||
<span class="like-count ml-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if frm.meta.beta %}
|
||||
<div class="flex pt-2 justify-between">
|
||||
<div>
|
||||
<a class="small text-muted" href="https://github.com/frappe/{{ frappe.boot.module_app[frappe.scrub(frm.meta.module)] }}/issues/new"
|
||||
target="_blank">
|
||||
{{ __("Report bug") }}</a>
|
||||
</div>
|
||||
<label class="indicator-pill yellow mb-0" title="{{ __("This feature is brand new and still experimental") }}">{{ __("Experimental") }}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="sidebar-section sidebar-rating hide border-bottom">
|
||||
<div style="position: relative;">
|
||||
|
|
@ -82,6 +74,15 @@
|
|||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
<div class="sidebar-section user-actions hidden border-bottom">
|
||||
<div class="form-sidebar-items user-actions-header">
|
||||
<div class="form-sidebar-label">
|
||||
{%= frappe.utils.icon("link-2") %}
|
||||
<span class="ellipsis">{%= __("Links") %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-actions-list"></div>
|
||||
</div>
|
||||
<div class="sidebar-section form-assignments">
|
||||
<div>
|
||||
<span class="form-sidebar-items">
|
||||
|
|
|
|||
|
|
@ -291,8 +291,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
set_primary_action() {
|
||||
if (this.can_create && !frappe.boot.read_only) {
|
||||
const doctype_name = __(frappe.router.doctype_layout) || __(this.doctype);
|
||||
const add_button_label = __("Add {0}", [doctype_name], "Primary action in list view");
|
||||
const create_button = this.page.set_primary_action(
|
||||
__("Add {0}", [doctype_name], "Primary action in list view"),
|
||||
add_button_label,
|
||||
() => {
|
||||
if (this.settings.primary_action) {
|
||||
this.settings.primary_action();
|
||||
|
|
@ -304,12 +305,26 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
);
|
||||
if (frappe.is_mobile()) {
|
||||
create_button.append(__("Add"));
|
||||
} else {
|
||||
this._trim_primary_action_if_overflow(create_button, add_button_label);
|
||||
}
|
||||
} else {
|
||||
this.page.clear_primary_action();
|
||||
}
|
||||
}
|
||||
|
||||
_trim_primary_action_if_overflow(btn, add_button_label) {
|
||||
const container = this.page.wrapper.find(".page-head-content")[0];
|
||||
if (!container || !btn[0]) return;
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const btnRect = btn[0].getBoundingClientRect();
|
||||
if (btnRect.right > containerRect.right) {
|
||||
const short_label = __("Add");
|
||||
btn.attr("title", add_button_label).tooltip();
|
||||
btn.find("span").text(short_label);
|
||||
}
|
||||
}
|
||||
|
||||
make_new_doc() {
|
||||
const doctype = this.doctype;
|
||||
const options = {};
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ $.extend(frappe.model, {
|
|||
allowed_records.length;
|
||||
|
||||
// don't set defaults for "User" link field using User Permissions!
|
||||
if (df.fieldtype === "Link" && df.options !== "User") {
|
||||
if (!df.read_only && df.fieldtype === "Link" && df.options !== "User") {
|
||||
// If user permission has Is Default enabled or single-user permission has found against respective doctype.
|
||||
if (has_user_permissions && default_doc) {
|
||||
value = default_doc;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
{% if (!in_folder) { %}
|
||||
<div class="icon-caption">
|
||||
<div class="icon-title" data-toggle="tooltip" data-original-title="{{ __(icon.label) }}">{{ __(icon.label) }}</div>
|
||||
<div class="icon-subtitle"></div>
|
||||
<!-- <div class="icon-subtitle"></div> -->
|
||||
</div>
|
||||
{% } %}
|
||||
{% if(!icon.in_folder && !icon.restrict_removal) { %}
|
||||
|
|
@ -25,4 +25,4 @@
|
|||
{%= frappe.utils.icon("x") %}
|
||||
</div>
|
||||
{% } %}
|
||||
</a>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -128,6 +128,14 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
) {
|
||||
$input.blur();
|
||||
}
|
||||
})
|
||||
.on("keydown", function (e) {
|
||||
if (e.key === "Escape" || e.keyCode === 27) {
|
||||
// when dialog is open and contains an awesomplete dropdown - do not close the dialog on escape key press
|
||||
if (me.display && me.$wrapper.find(".awesomplete").length) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
import { createPopper } from "@popperjs/core";
|
||||
|
||||
frappe.ui.is_liked = function (doc) {
|
||||
return frappe.ui.get_liked_by(doc).includes(frappe.session.user);
|
||||
|
|
@ -65,63 +66,157 @@ frappe.ui.setup_like_popover = ($parent, selector) => {
|
|||
return;
|
||||
}
|
||||
|
||||
$parent.on("mouseover", selector, function () {
|
||||
const target_element = $(this);
|
||||
target_element.popover({
|
||||
animation: true,
|
||||
placement: "bottom",
|
||||
trigger: "manual",
|
||||
template: `<div class="liked-by-popover popover">
|
||||
<div class="arrow"></div>
|
||||
let active_target = null;
|
||||
let active_popover = null;
|
||||
let active_popper = null;
|
||||
let hide_timer = null;
|
||||
|
||||
const clear_hide_timer = () => {
|
||||
if (hide_timer) {
|
||||
clearTimeout(hide_timer);
|
||||
hide_timer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const destroy_active_popover = () => {
|
||||
clear_hide_timer();
|
||||
if (active_target) {
|
||||
active_target.off(".likePopover");
|
||||
}
|
||||
if (active_popover) {
|
||||
active_popover.off(".likePopover");
|
||||
active_popover.remove();
|
||||
}
|
||||
if (active_popper) {
|
||||
active_popper.destroy();
|
||||
}
|
||||
active_target = null;
|
||||
active_popover = null;
|
||||
active_popper = null;
|
||||
};
|
||||
|
||||
const schedule_hide = () => {
|
||||
clear_hide_timer();
|
||||
hide_timer = setTimeout(() => {
|
||||
destroy_active_popover();
|
||||
}, 120);
|
||||
};
|
||||
|
||||
const get_liked_by_users = (target_element) => {
|
||||
let liked_by = target_element.parents(".liked-by").attr("data-liked-by");
|
||||
liked_by = liked_by ? decodeURI(liked_by) : "[]";
|
||||
return JSON.parse(liked_by);
|
||||
};
|
||||
|
||||
const get_popover_content = (target_element) => {
|
||||
const liked_by = get_liked_by_users(target_element);
|
||||
const content = $('<div class="liked-by-popover-content"></div>');
|
||||
const like_count = liked_by.length;
|
||||
|
||||
if (like_count > 3) {
|
||||
const like_summary = __("Liked by {0} people", [like_count]);
|
||||
const like_count_html = $(
|
||||
`<div class="liked-by-popover-summary">${like_summary}</div>`
|
||||
);
|
||||
content.append(like_count_html);
|
||||
}
|
||||
|
||||
if (!liked_by.length) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const liked_by_list = $('<ul class="list-unstyled"></ul>');
|
||||
const link_base = "/desk/user/";
|
||||
|
||||
liked_by.forEach((user) => {
|
||||
liked_by_list.append(`
|
||||
<li data-user=${user}>${frappe.avatar(user, "avatar-xs")}
|
||||
<span>${frappe.user.full_name(user)}</span>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
|
||||
liked_by_list.children("li").on("click", (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const user = ev.currentTarget.dataset.user;
|
||||
setTimeout(() => destroy_active_popover(), 0);
|
||||
frappe.set_route(link_base + user);
|
||||
});
|
||||
|
||||
content.append(liked_by_list);
|
||||
return content;
|
||||
};
|
||||
|
||||
const show_popover = (target_element) => {
|
||||
if (!get_liked_by_users(target_element).length) {
|
||||
destroy_active_popover();
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_target?.get(0) === target_element.get(0) && active_popover) {
|
||||
clear_hide_timer();
|
||||
active_popper?.update();
|
||||
return;
|
||||
}
|
||||
|
||||
destroy_active_popover();
|
||||
|
||||
const popover = $(
|
||||
`<div class="liked-by-popover popover show" role="tooltip">
|
||||
<div class="popover-body popover-content"></div>
|
||||
</div>`,
|
||||
content: () => {
|
||||
let liked_by = target_element.parents(".liked-by").attr("data-liked-by");
|
||||
liked_by = liked_by ? decodeURI(liked_by) : "[]";
|
||||
liked_by = JSON.parse(liked_by);
|
||||
</div>`
|
||||
);
|
||||
|
||||
if (!liked_by.length) {
|
||||
return "";
|
||||
}
|
||||
popover.find(".popover-content").append(get_popover_content(target_element));
|
||||
$(document.body).append(popover);
|
||||
|
||||
let liked_by_list = $(`<ul class="list-unstyled"></ul>`);
|
||||
|
||||
// to show social profile of the user
|
||||
let link_base = "/desk/user/";
|
||||
|
||||
liked_by.forEach((user) => {
|
||||
// append user list item
|
||||
liked_by_list.append(`
|
||||
<li data-user=${user}>${frappe.avatar(user, "avatar-xs")}
|
||||
<span>${frappe.user.full_name(user)}</span>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
|
||||
liked_by_list.children("li").click((ev) => {
|
||||
let user = ev.currentTarget.dataset.user;
|
||||
target_element.popover("hide");
|
||||
frappe.set_route(link_base + user);
|
||||
});
|
||||
|
||||
return liked_by_list;
|
||||
},
|
||||
html: true,
|
||||
container: "body",
|
||||
const popper = createPopper(target_element.get(0), popover.get(0), {
|
||||
placement: "bottom",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 8],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
padding: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
padding: 12,
|
||||
fallbackPlacements: ["bottom-start", "top", "top-start"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
target_element.popover("show");
|
||||
active_target = target_element;
|
||||
active_popover = popover;
|
||||
active_popper = popper;
|
||||
|
||||
$(".popover").on("mouseleave", () => {
|
||||
target_element.popover("hide");
|
||||
});
|
||||
target_element
|
||||
.off(".likePopover")
|
||||
.on("mouseenter.likePopover", clear_hide_timer)
|
||||
.on("mouseleave.likePopover", schedule_hide);
|
||||
|
||||
target_element.on("mouseout", () => {
|
||||
setTimeout(() => {
|
||||
if (!$(".popover:hover").length) {
|
||||
target_element.popover("hide");
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
popover
|
||||
.off(".likePopover")
|
||||
.on("mousedown.likePopover click.likePopover", (ev) => {
|
||||
ev.stopPropagation();
|
||||
})
|
||||
.on("mouseenter.likePopover", clear_hide_timer)
|
||||
.on("mouseleave.likePopover", schedule_hide);
|
||||
};
|
||||
|
||||
$parent.on("mouseenter", selector, function () {
|
||||
show_popover($(this));
|
||||
});
|
||||
|
||||
$parent.on("mouseleave", selector, schedule_hide);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ frappe.show_alert = frappe.toast = function (message, seconds = 7, actions = {})
|
|||
<div class="alert-subtitle">${message.subtitle || ""}</div>
|
||||
</div>
|
||||
<div class="alert-body" style="display: none"></div>
|
||||
<a class="close">${frappe.utils.icon("close-alt")}</a>
|
||||
<a class="close">${frappe.utils.icon("x")}</a>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,11 @@
|
|||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="promotional-banners"></div>
|
||||
<p>
|
||||
<a class="onboarding-sidebar">
|
||||
{%= frappe.utils.icon("user-check" , "sm", "", "", "text-ink-gray-7 current-color", true)%}
|
||||
<span> {%= __("Getting started") %} </span>
|
||||
<span> {%= __("Getting Started") %} </span>
|
||||
</a>
|
||||
</p>
|
||||
<a class="collapse-sidebar-link">
|
||||
|
|
|
|||
|
|
@ -78,9 +78,95 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
}
|
||||
}
|
||||
|
||||
setup_promotional_banners() {
|
||||
if (
|
||||
cint(frappe.sys_defaults?.disable_product_suggestion) ||
|
||||
!frappe.user.has_role("System Manager")
|
||||
)
|
||||
return;
|
||||
|
||||
let module = this.all_sidebar_items?.[this.workspace_title]?.["module"] || "";
|
||||
if (!module) return;
|
||||
|
||||
this.$promotional_banners = this.wrapper.find(".promotional-banners");
|
||||
this.$promotional_banners.empty();
|
||||
this.promotional_banners = [];
|
||||
|
||||
this.get_crm_banner(module);
|
||||
this.get_helpdesk_banner(module);
|
||||
|
||||
this.render_promotional_banners();
|
||||
}
|
||||
|
||||
get_crm_banner(module) {
|
||||
if (module != "CRM") return;
|
||||
|
||||
const icon =
|
||||
$(`<svg width="16" height="16" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 11.2C0 7.27963 0 5.31945 0.762954 3.82207C1.43407 2.50493 2.50493 1.43407 3.82207 0.762954C5.31945 0 7.27963 0 11.2 0H16.8C20.7204 0 22.6806 0 24.1779 0.762954C25.4951 1.43407 26.5659 2.50493 27.237 3.82207C28 5.31945 28 7.27963 28 11.2V16.8C28 20.7204 28 22.6806 27.237 24.1779C26.5659 25.4951 25.4951 26.5659 24.1779 27.237C22.6806 28 20.7204 28 16.8 28H11.2C7.27963 28 5.31945 28 3.82207 27.237C2.50493 26.5659 1.43407 25.4951 0.762954 24.1779C0 22.6806 0 20.7204 0 16.8V11.2Z" fill="#DB4EE0"/>
|
||||
<path d="M5.02441 6.58252V9.09486H20.4627V10.9791L15.0135 16.3806V19.3201H12.9676V16.3806C12.9676 16.3806 9.78529 13.1774 8.62962 12.0469H5.03698L10.0156 17.0087C10.3045 17.2851 10.4678 17.6745 10.4678 18.0765V21.041L17.5259 21.0661V18.0765C17.5259 17.6745 17.6892 17.2851 17.9781 17.0087L22.9751 12.0343V6.58252H5.02441Z" fill="#F1FCFF"/>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
const title = __("Switch to Frappe CRM");
|
||||
const message = __(
|
||||
"Sales without complexity, lock-in and per-user costs. Try it for free!"
|
||||
);
|
||||
const link =
|
||||
"https://frappe.io/crm?utm_source=crm-sidebar&utm_medium=sidebar&utm_campaign=frappe-ad";
|
||||
|
||||
this.promotional_banners.push({ title, message, link, icon });
|
||||
}
|
||||
|
||||
get_helpdesk_banner(module) {
|
||||
if (module != "Support") return;
|
||||
|
||||
const icon =
|
||||
$(`<svg width="16" height="16" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 11.2C0 7.27963 0 5.31945 0.762954 3.82207C1.43407 2.50493 2.50493 1.43407 3.82207 0.762954C5.31945 0 7.27963 0 11.2 0H16.8C20.7204 0 22.6806 0 24.1779 0.762954C25.4951 1.43407 26.5659 2.50493 27.237 3.82207C28 5.31945 28 7.27963 28 11.2V16.8C28 20.7204 28 22.6806 27.237 24.1779C26.5659 25.4951 25.4951 26.5659 24.1779 27.237C22.6806 28 20.7204 28 16.8 28H11.2C7.27963 28 5.31945 28 3.82207 27.237C2.50493 26.5659 1.43407 25.4951 0.762954 24.1779C0 22.6806 0 20.7204 0 16.8V11.2Z" fill="#7D42FB"/>
|
||||
<path d="M22.7237 12.1723V6.65771H5.26367V9.17005H20.2239V11.5568C19.2189 11.8457 18.4904 12.7753 18.4904 13.8681C18.4904 14.961 19.2189 15.878 20.2239 16.1669V18.5536H7.77601V11.9964H5.26367V21.066H22.7362V15.5514L21.2414 14.4836V13.2526L22.7362 12.1849L22.7237 12.1723Z" fill="#EDF7FF"/>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
const title = __("Switch to Helpdesk");
|
||||
const message = __(
|
||||
"Support without complexity, lock-in and per-user costs. Try it for free!"
|
||||
);
|
||||
const link =
|
||||
"https://frappe.io/helpdesk?utm_source=support-sidebar&utm_medium=sidebar&utm_campaign=frappe-ad";
|
||||
|
||||
this.promotional_banners.push({ title, message, link, icon });
|
||||
}
|
||||
|
||||
render_promotional_banners() {
|
||||
let me = this;
|
||||
|
||||
if (this.promotional_banners.length === 0) {
|
||||
this.$promotional_banners.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$promotional_banners.show();
|
||||
|
||||
this.promotional_banners.forEach((banner) => {
|
||||
let banner_html = $(`
|
||||
<a href="${banner.link}" class="promotional-banner" target="_blank" title="${banner.message}">
|
||||
<span>${banner.title}</span>
|
||||
</a>
|
||||
`);
|
||||
|
||||
banner_html.prepend(banner.icon);
|
||||
me.$promotional_banners.append(banner_html);
|
||||
});
|
||||
}
|
||||
|
||||
remove_onboarding_wrapper() {
|
||||
this.$onboarding.empty();
|
||||
this.wrapper.find(".onboarding-sidebar").removeClass("hidden");
|
||||
|
||||
if (!this.sidebar_data?.module_onboarding) {
|
||||
this.wrapper.find(".onboarding-sidebar").addClass("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
setup_onboarding() {
|
||||
|
|
@ -170,6 +256,7 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
this.sidebar_header = new frappe.ui.SidebarHeader(this);
|
||||
this.make_sidebar();
|
||||
this.add_sidebar_cards();
|
||||
this.setup_promotional_banners();
|
||||
this.setup_onboarding();
|
||||
|
||||
this.wrapper.find(".onboarding-sidebar").click(() => {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ function updateSettings(step) {
|
|||
};
|
||||
|
||||
frappe.set_route("Form", step.reference_document);
|
||||
markComplete(step);
|
||||
}
|
||||
|
||||
async function createEntry(step) {
|
||||
|
|
@ -234,7 +235,7 @@ function markReset(step) {
|
|||
<!-- Header -->
|
||||
|
||||
<div class="header onb-header-main">
|
||||
<div class="text-base font-medium">Getting started</div>
|
||||
<div class="text-base font-medium">{{ __("Getting Started") }}</div>
|
||||
<div class="onb-header-actions">
|
||||
<button @click="toggleCollapse" v-html="minimizeIcon"></button>
|
||||
<button @click="close" v-html="closeIcon"></button>
|
||||
|
|
@ -248,7 +249,7 @@ function markReset(step) {
|
|||
<div class="text-base font-medium">{{ title }}</div>
|
||||
|
||||
<div class="onb-title-steps">
|
||||
{{ completedCount }}/{{ steps.length }} steps completed
|
||||
{{ completedCount }}/{{ steps.length }} {{ __("steps completed") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -263,10 +264,10 @@ function markReset(step) {
|
|||
</div>
|
||||
|
||||
<div v-if="skippAll">
|
||||
<span class="onb-skip" @click="resetAll(steps)"> {{ __("Reset all") }}</span>
|
||||
<span class="onb-skip" @click="resetAll(steps)"> {{ __("Reset All") }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="onb-skip" @click="skipAll(steps)">Skip all</span>
|
||||
<span class="onb-skip" @click="skipAll(steps)">{{ __("Skip All") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -296,8 +297,11 @@ function markReset(step) {
|
|||
</div>
|
||||
|
||||
<div v-if="!step.is_skipped">
|
||||
<span class="text-base onb-step-text">
|
||||
{{ step.action_label }}
|
||||
<span
|
||||
class="text-base onb-step-text"
|
||||
:class="step.is_complete ? 'text-extra-muted' : ''"
|
||||
>
|
||||
{{ __(step.action_label) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
|
@ -305,7 +309,7 @@ function markReset(step) {
|
|||
class="text-base onb-step-text"
|
||||
style="text-decoration-line: line-through"
|
||||
>
|
||||
{{ step.action_label }}
|
||||
{{ __(step.action_label) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function addStyles() {
|
|||
position: fixed;
|
||||
right: 24px;
|
||||
bottom: 24px;
|
||||
width: 380px;
|
||||
width: 310px;
|
||||
max-height: 80vh;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
|
|
@ -215,6 +215,46 @@ function addStyles() {
|
|||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-panel {
|
||||
background-color: #232323;
|
||||
color: #e5e7eb;
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .text-base {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-skip {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-skip:hover {
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-title-steps,
|
||||
[data-theme="dark"] .onb-progress-text {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-group:hover {
|
||||
background: #374151;
|
||||
color: #f3f4f6;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-progress-badge {
|
||||
background: rgba(245,158,11,0.15);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .onb-progress-badge-complete {
|
||||
background: rgba(16,185,129,0.15);
|
||||
color: #34d399;
|
||||
}
|
||||
|
||||
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
|
|
|
|||
|
|
@ -1575,7 +1575,8 @@ Object.assign(frappe.utils, {
|
|||
if (item.is_query_report) {
|
||||
route = "query-report/" + item.name;
|
||||
} else if (!item.is_query_report && item.report_ref_doctype) {
|
||||
route = frappe.router.slug(item.report_ref_doctype) + "/view/report/";
|
||||
route =
|
||||
frappe.router.slug(item.report_ref_doctype) + "/view/report/" + item.name;
|
||||
} else {
|
||||
route = "report/" + item.name;
|
||||
}
|
||||
|
|
@ -1909,7 +1910,13 @@ Object.assign(frappe.utils, {
|
|||
|
||||
process_filter_expression(filter) {
|
||||
let filters = [];
|
||||
filters = filter ? new Function(`return ${filter}`)() : [];
|
||||
if (filter) {
|
||||
try {
|
||||
filters = JSON.parse(filter);
|
||||
} catch {
|
||||
console.warn("Invalid JSON in filter expression", filter);
|
||||
}
|
||||
}
|
||||
return this.cleanup_filters(filters);
|
||||
},
|
||||
cleanup_filters(filters) {
|
||||
|
|
@ -2222,4 +2229,16 @@ Object.assign(frappe.utils, {
|
|||
}
|
||||
return value;
|
||||
},
|
||||
get_installed_apps() {
|
||||
return frappe.boot.app_data.map((app) => {
|
||||
return app.app_name;
|
||||
});
|
||||
},
|
||||
is_sub_array(big, small) {
|
||||
let i = 0;
|
||||
for (let num of big) {
|
||||
if (num === small[i]) i++;
|
||||
}
|
||||
return i === small.length;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ frappe.views.CommunicationComposer = class {
|
|||
{
|
||||
fieldtype: "Button",
|
||||
label: frappe.utils.icon("down", "xs"),
|
||||
title: __("More Options"),
|
||||
fieldname: "option_toggle_button",
|
||||
click: () => {
|
||||
this.toggle_more_options();
|
||||
|
|
@ -496,7 +497,11 @@ frappe.views.CommunicationComposer = class {
|
|||
},
|
||||
];
|
||||
|
||||
frappe.utils.add_select_group_button(clear_and_add_template, email_template_actions);
|
||||
frappe.utils.add_select_group_button(
|
||||
clear_and_add_template,
|
||||
email_template_actions,
|
||||
"btn-default"
|
||||
);
|
||||
$(fields.use_html.wrapper).addClass("mt-2 text-center").appendTo(clear_and_add_template);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -493,6 +493,7 @@ frappe.views.Workspace = class Workspace {
|
|||
let blocks = [
|
||||
{
|
||||
type: "header",
|
||||
|
||||
data: { text: values.title },
|
||||
},
|
||||
];
|
||||
|
|
@ -666,7 +667,6 @@ frappe.views.Workspace = class Workspace {
|
|||
spacer: this.blocks["spacer"],
|
||||
HeaderSize: frappe.workspace_block.tunes["header_size"],
|
||||
};
|
||||
|
||||
this.editor = new EditorJS({
|
||||
data: {
|
||||
blocks: blocks || [],
|
||||
|
|
@ -676,6 +676,26 @@ frappe.views.Workspace = class Workspace {
|
|||
readOnly: true,
|
||||
logLevel: "ERROR",
|
||||
});
|
||||
if (blocks.length == 0) {
|
||||
let message = __("Welcome to the {0} workspace", [this.page.title]);
|
||||
let default_block = [
|
||||
{
|
||||
type: "header",
|
||||
data: { text: message },
|
||||
},
|
||||
];
|
||||
if (this.has_access) {
|
||||
default_block.push({
|
||||
type: "paragraph",
|
||||
data: {
|
||||
text: __("Click on {0} to edit", [frappe.utils.icon("ellipsis")]),
|
||||
},
|
||||
});
|
||||
}
|
||||
this.editor.isReady.then(() => {
|
||||
this.editor.render({ blocks: default_block });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
save_page(page) {
|
||||
|
|
|
|||
|
|
@ -243,10 +243,6 @@ body.modal-open[style^="padding-right"] {
|
|||
}
|
||||
.frappe-control:last-child {
|
||||
margin-left: 10px;
|
||||
button {
|
||||
// same as form-control input
|
||||
height: calc(1.5em + 0.7rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -268,7 +264,19 @@ body.modal-open[style^="padding-right"] {
|
|||
}
|
||||
|
||||
.frappe-control:last-child {
|
||||
margin-top: -14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal .frappe-control[data-fieldname="option_toggle_button"] {
|
||||
margin-top: 10px;
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
|
||||
button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -299,6 +307,9 @@ body.modal-open[style^="padding-right"] {
|
|||
}
|
||||
.assignee {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
&:hover {
|
||||
.btn-group {
|
||||
|
|
@ -306,9 +317,6 @@ body.modal-open[style^="padding-right"] {
|
|||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
margin-right: var(--margin-md);
|
||||
}
|
||||
}
|
||||
|
||||
// Stack minimized modals
|
||||
|
|
|
|||
|
|
@ -98,6 +98,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.avatar-smaller {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
text-align: center;
|
||||
|
||||
.standard-image {
|
||||
@include get_textstyle("xs", "regular");
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-medium {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@
|
|||
flex-wrap: wrap;
|
||||
color: var(--text-light);
|
||||
|
||||
.icon {
|
||||
stroke: var(--text-light);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
height: unset;
|
||||
}
|
||||
|
|
@ -38,6 +42,82 @@
|
|||
}
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: var(--padding-md);
|
||||
|
||||
.user-actions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.user-action-row {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user-action-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: underline;
|
||||
justify-content: space-between;
|
||||
gap: var(--margin-sm);
|
||||
width: 100%;
|
||||
padding: 4px 8px;
|
||||
margin-left: -6px;
|
||||
margin-right: -8px;
|
||||
border-radius: var(--border-radius-md);
|
||||
transition: background-color 120ms ease;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background: var(--subtle-fg);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.user-action-external-icon {
|
||||
display: none;
|
||||
line-height: 0;
|
||||
|
||||
.icon {
|
||||
margin: 0;
|
||||
--icon-stroke: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
&[target="_blank"] .user-action-external-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transform: translateX(-2px);
|
||||
transition: opacity 120ms ease, transform 120ms ease;
|
||||
}
|
||||
|
||||
&[target="_blank"]:hover .user-action-external-icon,
|
||||
&[target="_blank"]:focus-visible .user-action-external-icon {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.user-action-label {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-section.user-actions.border-bottom {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-tags {
|
||||
.tag-area {
|
||||
margin-top: -3px;
|
||||
|
|
@ -141,8 +221,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.form-title-text {
|
||||
// to match the actions button height for center alignment
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.form-stats-likes {
|
||||
gap: 8px;
|
||||
gap: 2px;
|
||||
.form-print {
|
||||
button:hover {
|
||||
background: var(--btn-default-hover-bg);
|
||||
|
|
@ -318,30 +403,56 @@ body[data-route^="Form"] {
|
|||
|
||||
.attachment-row,
|
||||
.form-tag-row {
|
||||
margin: var(--margin-xs) 0;
|
||||
max-width: 100%;
|
||||
margin: 4px 0;
|
||||
|
||||
.data-pill {
|
||||
@include get_textstyle("sm", "regular");
|
||||
justify-content: space-between;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0px 6px !important;
|
||||
|
||||
.pill-label {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
stroke: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.attachment-row {
|
||||
margin-left: -6px;
|
||||
margin-right: 0px;
|
||||
|
||||
.data-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 0px 6px !important;
|
||||
background-color: unset;
|
||||
box-shadow: none;
|
||||
padding-left: 0px !important;
|
||||
width: 100%;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: var(--subtle-fg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
> div {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.attachment-file-label {
|
||||
display: block;
|
||||
margin-left: var(--margin-xs);
|
||||
padding-right: var(--padding-xs);
|
||||
text-align: left;
|
||||
}
|
||||
.attachment-icon {
|
||||
|
|
@ -377,13 +488,58 @@ body[data-route^="Form"] {
|
|||
.form-attachments,
|
||||
.form-tags,
|
||||
.form-shared {
|
||||
padding: 8px;
|
||||
padding: var(--padding-sm) var(--padding-md);
|
||||
}
|
||||
|
||||
.form-attachments {
|
||||
// to add gap between attachment section label and attachments
|
||||
// without affecting empty state
|
||||
.attachments-actions + .attachment-row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tags {
|
||||
// to add gap between tag section label and tags
|
||||
// without affecting empty state
|
||||
:not(.form-tag-row) + .form-tag-row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-assignments,
|
||||
.form-shared {
|
||||
.assignments,
|
||||
.shares {
|
||||
margin: var(--margin-xs) 0px;
|
||||
margin-top: 8px;
|
||||
|
||||
.dialog-assignment-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 0px 6px;
|
||||
margin-left: -8px;
|
||||
margin-right: 0px;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: var(--subtle-fg);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.view-all-assignment {
|
||||
display: block;
|
||||
margin-top: var(--padding-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
.add-assignment-btn,
|
||||
|
|
@ -415,17 +571,43 @@ body[data-route^="Form"] {
|
|||
}
|
||||
}
|
||||
|
||||
.liked-by {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.liked-by-popover {
|
||||
max-width: 240px;
|
||||
border-radius: var(--border-radius-md);
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
|
||||
.popover-body {
|
||||
min-height: 30px;
|
||||
padding: 0px;
|
||||
|
||||
.liked-by-popover-summary {
|
||||
padding: 4px 10px;
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 1px solid var(--subtle-accent);
|
||||
@include get_textstyle("sm", "regular");
|
||||
}
|
||||
|
||||
ul.list-unstyled {
|
||||
margin-bottom: 0px;
|
||||
padding: 4px;
|
||||
|
||||
li {
|
||||
padding: var(--padding-xs) var(--padding-sm);
|
||||
margin: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--padding-xs);
|
||||
padding: var(--padding-xs);
|
||||
margin: 0;
|
||||
border-radius: var(--border-radius-sm);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--fg-hover-color);
|
||||
|
|
|
|||
|
|
@ -127,7 +127,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.onboarding-sidebar {
|
||||
.promotional-banners {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin: var(--margin-sm) 0;
|
||||
}
|
||||
|
||||
.onboarding-sidebar,
|
||||
.promotional-banner {
|
||||
text-decoration: none;
|
||||
font-size: var(--text-sm);
|
||||
display: flex;
|
||||
|
|
@ -287,9 +295,8 @@
|
|||
width: auto;
|
||||
}
|
||||
}
|
||||
.collapse-sidebar-link {
|
||||
display: none;
|
||||
}
|
||||
.promotional-banners,
|
||||
.collapse-sidebar-link,
|
||||
.dropdown-navbar-user {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ $threshold: 34;
|
|||
max-width: var(--timeline-content-max-width);
|
||||
padding: var(--padding-sm);
|
||||
margin-left: var(--margin-md);
|
||||
|
||||
> .ql-editor {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&.frappe-card {
|
||||
color: var(--text-neutral);
|
||||
background-color: var(--bg-color);
|
||||
|
|
|
|||
2
frappe/website/js/bootstrap-4.js
vendored
2
frappe/website/js/bootstrap-4.js
vendored
|
|
@ -27,7 +27,7 @@ frappe.get_modal = function (title, content) {
|
|||
<div class="modal-header">
|
||||
<h5 class="modal-title">${title}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
${frappe.utils.icon("close-alt", "sm", "close-alt")}
|
||||
${frappe.utils.icon("x", "sm", "close-alt")}
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
from urllib.parse import urlparse
|
||||
|
||||
import frappe
|
||||
from frappe.website.page_renderers.document_page import _find_matching_document_webview
|
||||
from frappe.website.page_renderers.template_page import TemplatePage
|
||||
from frappe.website.utils import can_cache
|
||||
|
||||
|
|
@ -26,10 +27,26 @@ class NotFoundPage(TemplatePage):
|
|||
|
||||
def can_cache_404(self):
|
||||
# do not cache 404 for custom homepages
|
||||
return can_cache() and self.request_url and not self.is_custom_home_page()
|
||||
# also skip caching docs with website permission checks (access is dynamic)
|
||||
return (
|
||||
can_cache()
|
||||
and self.request_url
|
||||
and not self.is_custom_home_page()
|
||||
and not self.has_website_permission_check()
|
||||
)
|
||||
|
||||
def is_custom_home_page(self):
|
||||
url_parts = urlparse(self.request_url)
|
||||
request_url = os.path.splitext(url_parts.path)[0]
|
||||
request_path = os.path.splitext(self.request_path)[0]
|
||||
return request_url in HOMEPAGE_PATHS and request_path not in HOMEPAGE_PATHS
|
||||
|
||||
def has_website_permission_check(self):
|
||||
request_path = os.path.splitext(self.request_path)[0]
|
||||
if not (document := _find_matching_document_webview(request_path)):
|
||||
return False
|
||||
doctype, docname = document
|
||||
doc = frappe.get_cached_doc(doctype, docname)
|
||||
return hasattr(doc, "has_website_permission") or bool(
|
||||
frappe.get_hooks("has_website_permission", {}).get(doctype)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -207,17 +207,61 @@ def return_action_confirmation_page(doc, action, action_link, alert_doc_change=F
|
|||
|
||||
|
||||
def return_link_expired_page(doc, doc_workflow_state):
|
||||
user_full_name = get_user_who_set_workflow_state(doc, doc_workflow_state) or frappe.get_value(
|
||||
"User", doc.get("modified_by"), "full_name"
|
||||
)
|
||||
frappe.respond_as_web_page(
|
||||
_("Link Expired"),
|
||||
_("Document {0} has been set to state {1} by {2}").format(
|
||||
frappe.bold(doc.get("name")),
|
||||
frappe.bold(doc_workflow_state),
|
||||
frappe.bold(frappe.get_value("User", doc.get("modified_by"), "full_name")),
|
||||
frappe.bold(
|
||||
user_full_name
|
||||
if user_full_name
|
||||
else frappe.get_value("User", doc.get("modified_by"), "full_name")
|
||||
),
|
||||
),
|
||||
indicator_color="blue",
|
||||
)
|
||||
|
||||
|
||||
def get_user_who_set_workflow_state(doc, doc_workflow_state):
|
||||
"""Get the full name of the user who triggered the workflow action that set the document to the given state.
|
||||
Falls back to None if no completed Workflow Action is found (e.g. state was set without workflow).
|
||||
"""
|
||||
workflow_name = get_workflow_name(doc.get("doctype"))
|
||||
if not workflow_name:
|
||||
return None
|
||||
|
||||
# Get states that have a transition to the current workflow state
|
||||
from_states = frappe.get_all(
|
||||
"Workflow Transition",
|
||||
filters={"parent": workflow_name, "next_state": doc_workflow_state},
|
||||
pluck="state",
|
||||
)
|
||||
if not from_states:
|
||||
return None
|
||||
|
||||
# Find the most recently completed Workflow Action that led to this state
|
||||
WorkflowAction = DocType("Workflow Action")
|
||||
completed_by = (
|
||||
frappe.qb.from_(WorkflowAction)
|
||||
.select(WorkflowAction.completed_by)
|
||||
.where(
|
||||
(WorkflowAction.reference_doctype == doc.get("doctype"))
|
||||
& (WorkflowAction.reference_name == doc.get("name"))
|
||||
& (WorkflowAction.status == "Completed")
|
||||
& (WorkflowAction.workflow_state.isin(from_states))
|
||||
)
|
||||
.orderby(WorkflowAction.modified, order=frappe.qb.desc)
|
||||
.limit(1)
|
||||
).run()
|
||||
|
||||
if completed_by and completed_by[0][0]:
|
||||
return frappe.get_value("User", completed_by[0][0], "full_name")
|
||||
return None
|
||||
|
||||
|
||||
def update_completed_workflow_actions(doc, user=None, workflow=None, workflow_state=None):
|
||||
allowed_roles = get_allowed_roles(user, workflow, workflow_state)
|
||||
# There is no transaction leading upto this state
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
{{ _("Print") }}
|
||||
</a>
|
||||
<a class="p-2"
|
||||
href="/api/method/frappe.utils.print_format.download_pdf?doctype={{doctype|e}}&name={{name|e}}&format={{print_format|e}}&letterhead={{letterhead|e}}&no_letterhead={{no_letterhead|e}}&_lang={{lang|e}}&key={{key|e}}">
|
||||
href="/api/method/frappe.utils.print_format.download_pdf?doctype={{doctype|e}}&name={{name|e}}&format={{print_format|e}}&letterhead={{letterhead|e}}&no_letterhead={{no_letterhead|e}}&_lang={{lang|e}}&key={{key|e}}&pdf_generator={{pdf_generator|e}}">
|
||||
{{ _('Get PDF') }}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ def get_context(context) -> PrintContext:
|
|||
|
||||
# Include selected print format name in access log
|
||||
print_format_name = getattr(print_format, "name", "Standard")
|
||||
pdf_generator = getattr(print_format, "pdf_generator", "wkhtmltopdf")
|
||||
|
||||
make_access_log(
|
||||
doctype=frappe.form_dict.doctype,
|
||||
|
|
@ -114,7 +115,7 @@ def get_context(context) -> PrintContext:
|
|||
"print_format": print_format_name,
|
||||
"letterhead": letterhead,
|
||||
"no_letterhead": frappe.form_dict.no_letterhead,
|
||||
"pdf_generator": frappe.form_dict.get("pdf_generator", "wkhtmltopdf"),
|
||||
"pdf_generator": frappe.form_dict.get("pdf_generator", pdf_generator),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -342,7 +343,8 @@ def get_html_and_style(
|
|||
if isinstance(name, str):
|
||||
document = frappe.get_lazy_doc(doc, name, check_permission=True)
|
||||
else:
|
||||
document = frappe.get_doc(json.loads(doc), check_permission=True)
|
||||
details = json.loads(doc)
|
||||
document = frappe.get_cached_doc(details["doctype"], details["name"], check_permission=True)
|
||||
|
||||
print_format = get_print_format_doc(print_format, meta=document.meta)
|
||||
set_link_titles(document)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ dependencies = [
|
|||
# We depend on internal attributes,
|
||||
# do NOT add loose requirements on PyMySQL versions.
|
||||
"PyMySQL==1.1.2",
|
||||
"pypdf==6.7.1",
|
||||
"pypdf==6.7.2",
|
||||
"PyPika @ git+https://github.com/frappe/pypika@2c50e6142b2d61d2d243e466fdd5dc03b3d918f2",
|
||||
"mysqlclient==2.2.7",
|
||||
"PyQRCode~=1.2.1",
|
||||
|
|
|
|||
|
|
@ -2055,9 +2055,9 @@ mime@^1.4.1:
|
|||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
minimatch@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.3.tgz#6a5cba9b31f503887018f579c89f81f61162e624"
|
||||
integrity sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue