@@ -10,6 +10,7 @@ concurrency: | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-latest | runs-on: ubuntu-latest | ||||
timeout-minutes: 60 | |||||
name: Patch Test | name: Patch Test | ||||
@@ -14,6 +14,7 @@ concurrency: | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-latest | runs-on: ubuntu-latest | ||||
timeout-minutes: 60 | |||||
strategy: | strategy: | ||||
fail-fast: false | fail-fast: false | ||||
@@ -128,4 +129,4 @@ jobs: | |||||
fail_ci_if_error: true | fail_ci_if_error: true | ||||
files: /home/runner/frappe-bench/sites/coverage.xml | files: /home/runner/frappe-bench/sites/coverage.xml | ||||
verbose: true | verbose: true | ||||
flags: server | |||||
flags: server |
@@ -13,6 +13,7 @@ concurrency: | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-latest | runs-on: ubuntu-latest | ||||
timeout-minutes: 60 | |||||
strategy: | strategy: | ||||
fail-fast: false | fail-fast: false | ||||
@@ -13,6 +13,7 @@ concurrency: | |||||
jobs: | jobs: | ||||
test: | test: | ||||
runs-on: ubuntu-latest | runs-on: ubuntu-latest | ||||
timeout-minutes: 60 | |||||
strategy: | strategy: | ||||
fail-fast: false | fail-fast: false | ||||
@@ -13,7 +13,7 @@ context('Grid Pagination', () => { | |||||
it('creates pages for child table', () => { | it('creates pages for child table', () => { | ||||
cy.visit('/app/contact/Test Contact'); | cy.visit('/app/contact/Test Contact'); | ||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | ||||
cy.get('@table').find('.current-page-number').should('contain', '1'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '1'); | |||||
cy.get('@table').find('.total-page-number').should('contain', '20'); | cy.get('@table').find('.total-page-number').should('contain', '20'); | ||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50); | cy.get('@table').find('.grid-body .grid-row').should('have.length', 50); | ||||
}); | }); | ||||
@@ -21,10 +21,10 @@ context('Grid Pagination', () => { | |||||
cy.visit('/app/contact/Test Contact'); | cy.visit('/app/contact/Test Contact'); | ||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | ||||
cy.get('@table').find('.next-page').click(); | cy.get('@table').find('.next-page').click(); | ||||
cy.get('@table').find('.current-page-number').should('contain', '2'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '2'); | |||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51'); | cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51'); | ||||
cy.get('@table').find('.prev-page').click(); | cy.get('@table').find('.prev-page').click(); | ||||
cy.get('@table').find('.current-page-number').should('contain', '1'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '1'); | |||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1'); | cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1'); | ||||
}); | }); | ||||
it('adds and deletes rows and changes page', () => { | it('adds and deletes rows and changes page', () => { | ||||
@@ -32,14 +32,35 @@ context('Grid Pagination', () => { | |||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | ||||
cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); | cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); | ||||
cy.get('@table').find('.grid-body .row-index').should('contain', 1001); | cy.get('@table').find('.grid-body .row-index').should('contain', 1001); | ||||
cy.get('@table').find('.current-page-number').should('contain', '21'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '21'); | |||||
cy.get('@table').find('.total-page-number').should('contain', '21'); | cy.get('@table').find('.total-page-number').should('contain', '21'); | ||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true }); | cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true }); | ||||
cy.get('@table').findByRole('button', {name: 'Delete'}).click(); | cy.get('@table').findByRole('button', {name: 'Delete'}).click(); | ||||
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000); | cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000); | ||||
cy.get('@table').find('.current-page-number').should('contain', '20'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '20'); | |||||
cy.get('@table').find('.total-page-number').should('contain', '20'); | cy.get('@table').find('.total-page-number').should('contain', '20'); | ||||
}); | }); | ||||
it('go to specific page, use up and down arrow, type characters, 0 page and more than existing page', () => { | |||||
cy.visit('/app/contact/Test Contact'); | |||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | |||||
cy.get('@table').find('.current-page-number').focus().clear().type('17').blur(); | |||||
cy.get('@table').find('.grid-body .row-index').should('contain', 801); | |||||
cy.get('@table').find('.current-page-number').focus().type('{uparrow}{uparrow}'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '19'); | |||||
cy.get('@table').find('.current-page-number').focus().type('{downarrow}{downarrow}'); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '17'); | |||||
cy.get('@table').find('.current-page-number').focus().clear().type('700').blur(); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '20'); | |||||
cy.get('@table').find('.current-page-number').focus().clear().type('0').blur(); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '1'); | |||||
cy.get('@table').find('.current-page-number').focus().clear().type('abc').blur(); | |||||
cy.get('@table').find('.current-page-number').should('have.value', '1'); | |||||
}); | |||||
// it('deletes all rows', ()=> { | // it('deletes all rows', ()=> { | ||||
// cy.visit('/app/contact/Test Contact'); | // cy.visit('/app/contact/Test Contact'); | ||||
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | // cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); | ||||
@@ -7,6 +7,8 @@ context('Report View', () => { | |||||
cy.visit('/app/website'); | cy.visit('/app/website'); | ||||
cy.insert_doc('DocType', custom_submittable_doctype, true); | cy.insert_doc('DocType', custom_submittable_doctype, true); | ||||
cy.clear_cache(); | cy.clear_cache(); | ||||
}); | |||||
it('Field with enabled allow_on_submit should be editable.', () => { | |||||
cy.insert_doc(doctype_name, { | cy.insert_doc(doctype_name, { | ||||
'title': 'Doc 1', | 'title': 'Doc 1', | ||||
'description': 'Random Text', | 'description': 'Random Text', | ||||
@@ -14,8 +16,6 @@ context('Report View', () => { | |||||
// submit document | // submit document | ||||
'docstatus': 1 | 'docstatus': 1 | ||||
}, true).as('doc'); | }, true).as('doc'); | ||||
}); | |||||
it('Field with enabled allow_on_submit should be editable.', () => { | |||||
cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update'); | cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update'); | ||||
cy.visit(`/app/List/${doctype_name}/Report`); | cy.visit(`/app/List/${doctype_name}/Report`); | ||||
// check status column added from docstatus | // check status column added from docstatus | ||||
@@ -5,6 +5,15 @@ import frappe, json | |||||
import unittest | import unittest | ||||
class TestComment(unittest.TestCase): | class TestComment(unittest.TestCase): | ||||
def tearDown(self): | |||||
frappe.form_dict.comment = None | |||||
frappe.form_dict.comment_email = None | |||||
frappe.form_dict.comment_by = None | |||||
frappe.form_dict.reference_doctype = None | |||||
frappe.form_dict.reference_name = None | |||||
frappe.form_dict.route = None | |||||
frappe.local.request_ip = None | |||||
def test_comment_creation(self): | def test_comment_creation(self): | ||||
test_doc = frappe.get_doc(dict(doctype = 'ToDo', description = 'test')) | test_doc = frappe.get_doc(dict(doctype = 'ToDo', description = 'test')) | ||||
test_doc.insert() | test_doc.insert() | ||||
@@ -33,8 +42,16 @@ class TestComment(unittest.TestCase): | |||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | ||||
from frappe.templates.includes.comments.comments import add_comment | from frappe.templates.includes.comments.comments import add_comment | ||||
add_comment('Good comment with 10 chars', 'test@test.com', 'Good Tester', | |||||
'Blog Post', test_blog.name, test_blog.route) | |||||
frappe.form_dict.comment = 'Good comment with 10 chars' | |||||
frappe.form_dict.comment_email = 'test@test.com' | |||||
frappe.form_dict.comment_by = 'Good Tester' | |||||
frappe.form_dict.reference_doctype = 'Blog Post' | |||||
frappe.form_dict.reference_name = test_blog.name | |||||
frappe.form_dict.route = test_blog.route | |||||
frappe.local.request_ip = '127.0.0.1' | |||||
add_comment() | |||||
self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict( | self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict( | ||||
reference_doctype = test_blog.doctype, | reference_doctype = test_blog.doctype, | ||||
@@ -43,8 +60,10 @@ class TestComment(unittest.TestCase): | |||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) | ||||
add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor', | |||||
'Blog Post', test_blog.name, test_blog.route) | |||||
frappe.form_dict.comment = 'pleez vizits my site http://mysite.com' | |||||
frappe.form_dict.comment_by = 'bad commentor' | |||||
add_comment() | |||||
self.assertEqual(len(frappe.get_all('Comment', fields = ['*'], filters = dict( | self.assertEqual(len(frappe.get_all('Comment', fields = ['*'], filters = dict( | ||||
reference_doctype = test_blog.doctype, | reference_doctype = test_blog.doctype, | ||||
@@ -146,25 +146,43 @@ def add_attachments(name, attachments): | |||||
}) | }) | ||||
_file.save(ignore_permissions=True) | _file.save(ignore_permissions=True) | ||||
@frappe.whitelist(allow_guest=True) | |||||
def mark_email_as_seen(name=None): | |||||
@frappe.whitelist(allow_guest=True, methods=("GET",)) | |||||
def mark_email_as_seen(name: str = None): | |||||
try: | try: | ||||
if name and frappe.db.exists("Communication", name) and not frappe.db.get_value("Communication", name, "read_by_recipient"): | |||||
frappe.db.set_value("Communication", name, "read_by_recipient", 1) | |||||
frappe.db.set_value("Communication", name, "delivery_status", "Read") | |||||
frappe.db.set_value("Communication", name, "read_by_recipient_on", get_datetime()) | |||||
frappe.db.commit() | |||||
update_communication_as_read(name) | |||||
frappe.db.commit() # nosemgrep: this will be called in a GET request | |||||
except Exception: | except Exception: | ||||
frappe.log_error(frappe.get_traceback()) | frappe.log_error(frappe.get_traceback()) | ||||
finally: | finally: | ||||
# Return image as response under all circumstances | |||||
from PIL import Image | |||||
import io | |||||
im = Image.new('RGBA', (1, 1)) | |||||
im.putdata([(255,255,255,0)]) | |||||
buffered_obj = io.BytesIO() | |||||
im.save(buffered_obj, format="PNG") | |||||
frappe.response["type"] = 'binary' | |||||
frappe.response["filename"] = "imaginary_pixel.png" | |||||
frappe.response["filecontent"] = buffered_obj.getvalue() | |||||
frappe.response.update({ | |||||
"type": "binary", | |||||
"filename": "imaginary_pixel.png", | |||||
"filecontent": ( | |||||
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00" | |||||
b"\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\r" | |||||
b"IDATx\x9cc\xf8\xff\xff?\x03\x00\x08\xfc\x02\xfe\xa7\x9a\xa0" | |||||
b"\xa0\x00\x00\x00\x00IEND\xaeB`\x82" | |||||
) | |||||
}) | |||||
def update_communication_as_read(name): | |||||
if not name or not isinstance(name, str): | |||||
return | |||||
communication = frappe.db.get_value( | |||||
"Communication", | |||||
name, | |||||
"read_by_recipient", | |||||
as_dict=True | |||||
) | |||||
if not communication or communication.read_by_recipient: | |||||
return | |||||
frappe.db.set_value("Communication", name, { | |||||
"read_by_recipient": 1, | |||||
"delivery_status": "Read", | |||||
"read_by_recipient_on": get_datetime() | |||||
}) |
@@ -8,34 +8,14 @@ | |||||
"reference_doctype", | "reference_doctype", | ||||
"reference_name", | "reference_name", | ||||
"column_break_3", | "column_break_3", | ||||
"rating", | |||||
"ip_address", | |||||
"section_break_6", | |||||
"feedback" | |||||
"like", | |||||
"ip_address" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "column_break_3", | "fieldname": "column_break_3", | ||||
"fieldtype": "Column Break" | "fieldtype": "Column Break" | ||||
}, | }, | ||||
{ | |||||
"fieldname": "rating", | |||||
"fieldtype": "Float", | |||||
"in_list_view": 1, | |||||
"label": "Rating", | |||||
"precision": "1", | |||||
"reqd": 1 | |||||
}, | |||||
{ | |||||
"fieldname": "section_break_6", | |||||
"fieldtype": "Section Break" | |||||
}, | |||||
{ | |||||
"fieldname": "feedback", | |||||
"fieldtype": "Small Text", | |||||
"label": "Feedback", | |||||
"reqd": 1 | |||||
}, | |||||
{ | { | ||||
"fieldname": "reference_doctype", | "fieldname": "reference_doctype", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
@@ -57,11 +37,17 @@ | |||||
"hidden": 1, | "hidden": 1, | ||||
"label": "IP Address", | "label": "IP Address", | ||||
"read_only": 1 | "read_only": 1 | ||||
}, | |||||
{ | |||||
"default": "0", | |||||
"fieldname": "like", | |||||
"fieldtype": "Check", | |||||
"label": "Like" | |||||
} | } | ||||
], | ], | ||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-06-23 12:45:42.045696", | |||||
"modified": "2021-11-10 20:53:21.255593", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Feedback", | "name": "Feedback", | ||||
@@ -8,8 +8,7 @@ class TestFeedback(unittest.TestCase): | |||||
def tearDown(self): | def tearDown(self): | ||||
frappe.form_dict.reference_doctype = None | frappe.form_dict.reference_doctype = None | ||||
frappe.form_dict.reference_name = None | frappe.form_dict.reference_name = None | ||||
frappe.form_dict.rating = None | |||||
frappe.form_dict.feedback = None | |||||
frappe.form_dict.like = None | |||||
frappe.local.request_ip = None | frappe.local.request_ip = None | ||||
def test_feedback_creation_updation(self): | def test_feedback_creation_updation(self): | ||||
@@ -18,23 +17,22 @@ class TestFeedback(unittest.TestCase): | |||||
frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) | frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) | ||||
from frappe.templates.includes.feedback.feedback import add_feedback, update_feedback | |||||
from frappe.templates.includes.feedback.feedback import give_feedback | |||||
frappe.form_dict.reference_doctype = 'Blog Post' | frappe.form_dict.reference_doctype = 'Blog Post' | ||||
frappe.form_dict.reference_name = test_blog.name | frappe.form_dict.reference_name = test_blog.name | ||||
frappe.form_dict.rating = 5 | |||||
frappe.form_dict.feedback = 'New feedback' | |||||
frappe.form_dict.like = True | |||||
frappe.local.request_ip = '127.0.0.1' | frappe.local.request_ip = '127.0.0.1' | ||||
feedback = add_feedback() | |||||
feedback = give_feedback() | |||||
self.assertEqual(feedback.feedback, 'New feedback') | |||||
self.assertEqual(feedback.rating, 5) | |||||
self.assertEqual(feedback.like, True) | |||||
updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback') | |||||
frappe.form_dict.like = False | |||||
self.assertEqual(updated_feedback.feedback, 'Updated feedback') | |||||
self.assertEqual(updated_feedback.rating, 6) | |||||
updated_feedback = give_feedback() | |||||
self.assertEqual(updated_feedback.like, False) | |||||
frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) | frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) | ||||
@@ -599,7 +599,7 @@ | |||||
"fieldname": "desk_theme", | "fieldname": "desk_theme", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"label": "Desk Theme", | "label": "Desk Theme", | ||||
"options": "Light\nDark" | |||||
"options": "Light\nDark\nAutomatic" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "module_profile", | "fieldname": "module_profile", | ||||
@@ -669,7 +669,7 @@ | |||||
} | } | ||||
], | ], | ||||
"max_attachments": 5, | "max_attachments": 5, | ||||
"modified": "2021-10-27 17:17:16.098457", | |||||
"modified": "2021-11-17 17:17:16.098457", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "User", | "name": "User", | ||||
@@ -1046,7 +1046,7 @@ def generate_keys(user): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def switch_theme(theme): | def switch_theme(theme): | ||||
if theme in ["Dark", "Light"]: | |||||
if theme in ["Dark", "Light", "Automatic"]: | |||||
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme) | frappe.db.set_value("User", frappe.session.user, "desk_theme", theme) | ||||
def get_enabled_users(): | def get_enabled_users(): | ||||
@@ -570,12 +570,11 @@ class Database(object): | |||||
def _get_value_for_many_names(self, doctype, names, field, debug=False, run=True, **kwargs): | def _get_value_for_many_names(self, doctype, names, field, debug=False, run=True, **kwargs): | ||||
names = list(filter(None, names)) | names = list(filter(None, names)) | ||||
if names: | if names: | ||||
return self.get_all(doctype, | return self.get_all(doctype, | ||||
fields=['name', field], | |||||
filters=[['name', 'in', names]], | |||||
debug=debug, as_list=1, run=run, **kwargs), | |||||
fields=field, | |||||
filters=names, | |||||
debug=debug, as_list=1, run=run) | |||||
else: | else: | ||||
return {} | return {} | ||||
@@ -15,7 +15,9 @@ | |||||
"enable_email_energy_point", | "enable_email_energy_point", | ||||
"enable_email_share", | "enable_email_share", | ||||
"user", | "user", | ||||
"seen" | |||||
"seen", | |||||
"system_notifications_section", | |||||
"energy_points_system_notifications" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
@@ -84,15 +86,27 @@ | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"hidden": 1, | "hidden": 1, | ||||
"label": "Seen" | "label": "Seen" | ||||
}, | |||||
{ | |||||
"fieldname": "system_notifications_section", | |||||
"fieldtype": "Section Break", | |||||
"label": "System Notifications" | |||||
}, | |||||
{ | |||||
"default": "1", | |||||
"fieldname": "energy_points_system_notifications", | |||||
"fieldtype": "Check", | |||||
"label": "Energy Points" | |||||
} | } | ||||
], | ], | ||||
"in_create": 1, | "in_create": 1, | ||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2020-11-04 12:54:57.989317", | |||||
"modified": "2021-11-16 12:18:46.955501", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "Notification Settings", | "name": "Notification Settings", | ||||
"naming_rule": "Set by user", | |||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
@@ -111,4 +125,4 @@ | |||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "DESC", | "sort_order": "DESC", | ||||
"track_changes": 1 | "track_changes": 1 | ||||
} | |||||
} |
@@ -1,9 +1,13 @@ | |||||
import frappe | import frappe | ||||
from frappe.model.rename_doc import rename_doc | |||||
def execute(): | def execute(): | ||||
if frappe.db.exists("DocType", "Client Script"): | if frappe.db.exists("DocType", "Client Script"): | ||||
return | return | ||||
frappe.rename_doc("DocType", "Custom Script", "Client Script") | |||||
frappe.flags.ignore_route_conflict_validation = True | |||||
rename_doc("DocType", "Custom Script", "Client Script") | |||||
frappe.flags.ignore_route_conflict_validation = False | |||||
frappe.reload_doctype("Client Script", force=True) | frappe.reload_doctype("Client Script", force=True) |
@@ -64,6 +64,19 @@ frappe.Application = class Application { | |||||
} | } | ||||
}); | }); | ||||
frappe.ui.add_system_theme_switch_listener(); | |||||
const root = document.documentElement; | |||||
const observer = new MutationObserver(() => { | |||||
frappe.ui.set_theme(); | |||||
}); | |||||
observer.observe(root, { | |||||
attributes: true, | |||||
attributeFilter: ['data-theme-mode'] | |||||
}); | |||||
frappe.ui.set_theme(); | |||||
// page container | // page container | ||||
this.make_page_container(); | this.make_page_container(); | ||||
this.set_route(); | this.set_route(); | ||||
@@ -14,6 +14,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
this.progress_area = this.make_section({ | this.progress_area = this.make_section({ | ||||
css_class: 'progress-area', | css_class: 'progress-area', | ||||
hidden: 1, | hidden: 1, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1, | is_dashboard_section: 1, | ||||
}); | }); | ||||
@@ -21,6 +22,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
label: __("Overview"), | label: __("Overview"), | ||||
css_class: 'form-heatmap', | css_class: 'form-heatmap', | ||||
hidden: 1, | hidden: 1, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1, | is_dashboard_section: 1, | ||||
body_html: ` | body_html: ` | ||||
<div id="heatmap-${frappe.model.scrub(this.frm.doctype)}" class="heatmap"></div> | <div id="heatmap-${frappe.model.scrub(this.frm.doctype)}" class="heatmap"></div> | ||||
@@ -32,6 +34,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
label: __("Graph"), | label: __("Graph"), | ||||
css_class: 'form-graph', | css_class: 'form-graph', | ||||
hidden: 1, | hidden: 1, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1 | is_dashboard_section: 1 | ||||
}); | }); | ||||
@@ -40,6 +43,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
label: __("Stats"), | label: __("Stats"), | ||||
css_class: 'form-stats', | css_class: 'form-stats', | ||||
hidden: 1, | hidden: 1, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1, | is_dashboard_section: 1, | ||||
body_html: this.stats_area_row | body_html: this.stats_area_row | ||||
}); | }); | ||||
@@ -50,6 +54,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
label: __("Connections"), | label: __("Connections"), | ||||
css_class: 'form-links', | css_class: 'form-links', | ||||
hidden: 1, | hidden: 1, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1, | is_dashboard_section: 1, | ||||
body_html: this.transactions_area | body_html: this.transactions_area | ||||
}); | }); | ||||
@@ -84,9 +89,10 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
hidden, | hidden, | ||||
body_html, | body_html, | ||||
make_card: true, | make_card: true, | ||||
collapsible: 1, | |||||
is_dashboard_section: 1 | is_dashboard_section: 1 | ||||
}; | }; | ||||
return new Section(this.frm.layout.wrapper, options).body; | |||||
return new Section(this.parent, options).body; | |||||
} | } | ||||
add_progress(title, percent, message) { | add_progress(title, percent, message) { | ||||
@@ -203,7 +209,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
after_refresh() { | after_refresh() { | ||||
// show / hide new buttons (if allowed) | // show / hide new buttons (if allowed) | ||||
this.links_area.body.find('.btn-new').each((i, el) => { | this.links_area.body.find('.btn-new').each((i, el) => { | ||||
if (this.frm.can_create($(this).attr('data-doctype'))) { | |||||
if (this.frm.can_create($(el).attr('data-doctype'))) { | |||||
$(el).removeClass('hidden'); | $(el).removeClass('hidden'); | ||||
} | } | ||||
}); | }); | ||||
@@ -156,8 +156,11 @@ frappe.ui.form.Form = class FrappeForm { | |||||
let dashboard_parent = $('<div class="form-dashboard">'); | let dashboard_parent = $('<div class="form-dashboard">'); | ||||
let main_page = this.layout.tabs.length ? this.layout.tabs[0].wrapper : this.layout.wrapper; | |||||
main_page.prepend(dashboard_parent); | |||||
if (this.layout.tabs.length) { | |||||
this.layout.tabs[0].wrapper.prepend(dashboard_parent); | |||||
} else { | |||||
dashboard_parent.insertAfter(this.layout.wrapper.find('.form-message')); | |||||
} | |||||
this.dashboard = new frappe.ui.form.Dashboard(dashboard_parent, this); | this.dashboard = new frappe.ui.form.Dashboard(dashboard_parent, this); | ||||
this.tour = new frappe.ui.form.FormTour({ | this.tour = new frappe.ui.form.FormTour({ | ||||
@@ -46,8 +46,55 @@ export default class GridPagination { | |||||
this.last_page_button.on('click', () => { | this.last_page_button.on('click', () => { | ||||
this.go_to_page(this.total_pages); | this.go_to_page(this.total_pages); | ||||
}); | }); | ||||
this.$page_number.on('keyup', (e) => { | |||||
e.currentTarget.style.width = ((e.currentTarget.value.length + 1) * 8) + 'px'; | |||||
}); | |||||
this.$page_number.on('keydown', (e) => { | |||||
e = (e) ? e : window.event; | |||||
var charCode = (e.which) ? e.which : e.keyCode; | |||||
let arrow = { up: 38, down: 40 }; | |||||
switch (charCode) { | |||||
case arrow.up: | |||||
this.inc_dec_number(true); | |||||
break; | |||||
case arrow.down: | |||||
this.inc_dec_number(false); | |||||
break; | |||||
} | |||||
// only allow numbers from 0-9 and up, down, left, right arrow keys | |||||
if (charCode > 31 && (charCode < 48 || charCode > 57) && | |||||
![37, 38, 39, 40].includes(charCode)) { | |||||
return false; | |||||
} | |||||
return true; | |||||
}); | |||||
this.$page_number.on('focusout', (e) => { | |||||
if (this.page_index == e.currentTarget.value) return; | |||||
this.page_index = e.currentTarget.value; | |||||
if (this.page_index < 1) { | |||||
this.page_index = 1; | |||||
} else if (this.page_index > this.total_pages) { | |||||
this.page_index = this.total_pages; | |||||
} | |||||
this.go_to_page(); | |||||
}); | |||||
} | } | ||||
inc_dec_number(increment) { | |||||
let new_value = parseInt(this.$page_number.val()); | |||||
increment ? new_value++ : new_value--; | |||||
if (new_value < 1 || new_value > this.total_pages) return; | |||||
this.$page_number.val(new_value); | |||||
} | |||||
update_page_numbers() { | update_page_numbers() { | ||||
let total_pages = Math.ceil(this.grid.data.length/this.page_length); | let total_pages = Math.ceil(this.grid.data.length/this.page_length); | ||||
@@ -65,7 +112,7 @@ export default class GridPagination { | |||||
get_pagination_html() { | get_pagination_html() { | ||||
let page_text_html = `<div class="page-text"> | let page_text_html = `<div class="page-text"> | ||||
<span class="current-page-number page-number">${__(this.page_index)}</span> | |||||
<input class="current-page-number page-number" type="text" value="${__(this.page_index)}"/> | |||||
<span>${__('of')}</span> | <span>${__('of')}</span> | ||||
<span class="total-page-number page-number"> ${__(this.total_pages)} </span> | <span class="total-page-number page-number"> ${__(this.total_pages)} </span> | ||||
</div>`; | </div>`; | ||||
@@ -104,7 +151,8 @@ export default class GridPagination { | |||||
let $rows = $(this.grid.parent).find(".rows").empty(); | let $rows = $(this.grid.parent).find(".rows").empty(); | ||||
this.grid.render_result_rows($rows, true); | this.grid.render_result_rows($rows, true); | ||||
if (this.$page_number) { | if (this.$page_number) { | ||||
this.$page_number.text(index); | |||||
this.$page_number.val(index); | |||||
this.$page_number.css('width', ((index.toString().length + 1) * 8) + 'px'); | |||||
} | } | ||||
this.update_page_numbers(); | this.update_page_numbers(); | ||||
@@ -245,7 +245,7 @@ frappe.ui.form.Layout = class Layout { | |||||
} | } | ||||
make_section(df) { | make_section(df) { | ||||
this.section = new Section(this.current_tab ? this.current_tab.wrapper : this.page, df, this.card_layout); | |||||
this.section = new Section(this.current_tab ? this.current_tab.wrapper : this.page, df, this.card_layout, this); | |||||
// append to layout fields | // append to layout fields | ||||
if (df) { | if (df) { | ||||
@@ -1,5 +1,6 @@ | |||||
export default class Section { | export default class Section { | ||||
constructor(parent, df, card_layout) { | |||||
constructor(parent, df, card_layout, layout) { | |||||
this.layout = layout; | |||||
this.card_layout = card_layout; | this.card_layout = card_layout; | ||||
this.parent = parent; | this.parent = parent; | ||||
this.df = df || {}; | this.df = df || {}; | ||||
@@ -25,6 +26,7 @@ export default class Section { | |||||
${this.df.is_dashboard_section ? "form-dashboard-section" : "form-section"} | ${this.df.is_dashboard_section ? "form-dashboard-section" : "form-section"} | ||||
${ make_card ? "card-section" : "" }"> | ${ make_card ? "card-section" : "" }"> | ||||
`).appendTo(this.parent); | `).appendTo(this.parent); | ||||
this.layout && this.layout.sections.push(this); | |||||
if (this.df) { | if (this.df) { | ||||
if (this.df.label) { | if (this.df.label) { | ||||
@@ -307,6 +307,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
} | } | ||||
update_checkbox(target) { | update_checkbox(target) { | ||||
if (!this.$checkbox_actions) return; | |||||
let $check_all_checkbox = this.$checkbox_actions.find(".list-check-all"); | let $check_all_checkbox = this.$checkbox_actions.find(".list-check-all"); | ||||
if ($check_all_checkbox.prop("checked") && target && !target.prop("checked")) { | if ($check_all_checkbox.prop("checked") && target && !target.prop("checked")) { | ||||
@@ -42,7 +42,7 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { | |||||
} | } | ||||
refresh() { | refresh() { | ||||
this.current_theme = document.documentElement.getAttribute("data-theme") || "light"; | |||||
this.current_theme = document.documentElement.getAttribute("data-theme-mode") || "light"; | |||||
this.fetch_themes().then(() => { | this.fetch_themes().then(() => { | ||||
this.render(); | this.render(); | ||||
}); | }); | ||||
@@ -54,10 +54,17 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { | |||||
{ | { | ||||
name: "light", | name: "light", | ||||
label: __("Frappe Light"), | label: __("Frappe Light"), | ||||
info: __("Light Theme") | |||||
}, | }, | ||||
{ | { | ||||
name: "dark", | name: "dark", | ||||
label: __("Timeless Night"), | label: __("Timeless Night"), | ||||
info: __("Dark Theme") | |||||
}, | |||||
{ | |||||
name: "automatic", | |||||
label: __("Automatic"), | |||||
info: __("Uses system's theme to switch between light and dark mode") | |||||
} | } | ||||
]; | ]; | ||||
@@ -74,11 +81,15 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { | |||||
} | } | ||||
get_preview_html(theme) { | get_preview_html(theme) { | ||||
const is_auto_theme = theme.name === "automatic"; | |||||
const preview = $(`<div class="${this.current_theme == theme.name ? "selected" : "" }"> | const preview = $(`<div class="${this.current_theme == theme.name ? "selected" : "" }"> | ||||
<div data-theme=${theme.name}> | |||||
<div data-theme=${is_auto_theme ? "light" : theme.name} | |||||
data-is-auto-theme="${is_auto_theme}" title="${theme.info}"> | |||||
<div class="background"> | <div class="background"> | ||||
<div> | <div> | ||||
<div class="preview-check">${frappe.utils.icon('tick', 'xs')}</div> | |||||
<div class="preview-check" data-theme=${is_auto_theme ? "dark" : theme.name}> | |||||
${frappe.utils.icon('tick', 'xs')} | |||||
</div> | |||||
</div> | </div> | ||||
<div class="navbar"></div> | <div class="navbar"></div> | ||||
<div class="p-2"> | <div class="p-2"> | ||||
@@ -112,13 +123,14 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { | |||||
toggle_theme(theme) { | toggle_theme(theme) { | ||||
this.current_theme = theme.toLowerCase(); | this.current_theme = theme.toLowerCase(); | ||||
document.documentElement.setAttribute("data-theme", this.current_theme); | |||||
document.documentElement.setAttribute("data-theme-mode", this.current_theme); | |||||
frappe.show_alert("Theme Changed", 3); | frappe.show_alert("Theme Changed", 3); | ||||
frappe.xcall("frappe.core.doctype.user.user.switch_theme", { | frappe.xcall("frappe.core.doctype.user.user.switch_theme", { | ||||
theme: toTitle(theme) | theme: toTitle(theme) | ||||
}); | }); | ||||
} | } | ||||
show() { | show() { | ||||
this.dialog.show(); | this.dialog.show(); | ||||
} | } | ||||
@@ -127,3 +139,22 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { | |||||
this.dialog.hide(); | this.dialog.hide(); | ||||
} | } | ||||
}; | }; | ||||
frappe.ui.add_system_theme_switch_listener = () => { | |||||
frappe.ui.dark_theme_media_query.addEventListener('change', () => { | |||||
frappe.ui.set_theme(); | |||||
}); | |||||
}; | |||||
frappe.ui.dark_theme_media_query = window.matchMedia("(prefers-color-scheme: dark)"); | |||||
frappe.ui.set_theme = (theme) => { | |||||
const root = document.documentElement; | |||||
let theme_mode = root.getAttribute("data-theme-mode"); | |||||
if (!theme) { | |||||
if (theme_mode === "automatic") { | |||||
theme = frappe.ui.dark_theme_media_query.matches ? 'dark' : 'light'; | |||||
} | |||||
} | |||||
root.setAttribute("data-theme", theme || theme_mode); | |||||
}; |
@@ -12,7 +12,7 @@ frappe.user_info = function(uid) { | |||||
if(!(frappe.boot.user_info && frappe.boot.user_info[uid])) { | if(!(frappe.boot.user_info && frappe.boot.user_info[uid])) { | ||||
var user_info = { | var user_info = { | ||||
fullname: frappe.utils.capitalize(uid.split("@")[0]) || "Unknown" | |||||
fullname: frappe.utils.to_title_case(uid.split("@")[0]) || "Unknown" | |||||
}; | }; | ||||
} else { | } else { | ||||
var user_info = frappe.boot.user_info[uid]; | var user_info = frappe.boot.user_info[uid]; | ||||
@@ -224,6 +224,9 @@ frappe.views.InteractionComposer = class InteractionComposer { | |||||
if (!("owner" in interaction_values)){ | if (!("owner" in interaction_values)){ | ||||
interaction_values["owner"] = frappe.session.user; | interaction_values["owner"] = frappe.session.user; | ||||
} | } | ||||
if (!("assigned_by" in interaction_values) && interaction_values["doctype"] == "ToDo") { | |||||
interaction_values["assigned_by"] = frappe.session.user; | |||||
} | |||||
return frappe.call({ | return frappe.call({ | ||||
method:"frappe.client.insert", | method:"frappe.client.insert", | ||||
args: { doc: interaction_values}, | args: { doc: interaction_values}, | ||||
@@ -634,6 +634,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { | |||||
this.render_datatable(); | this.render_datatable(); | ||||
this.add_chart_buttons_to_toolbar(true); | this.add_chart_buttons_to_toolbar(true); | ||||
this.add_card_button_to_toolbar(); | this.add_card_button_to_toolbar(); | ||||
this.$report.show(); | |||||
} else { | } else { | ||||
this.data = []; | this.data = []; | ||||
this.toggle_nothing_to_show(true); | this.toggle_nothing_to_show(true); | ||||
@@ -882,7 +883,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { | |||||
hide_loading_screen() { | hide_loading_screen() { | ||||
this.$loading.hide(); | this.$loading.hide(); | ||||
this.$report.show(); | |||||
} | } | ||||
get_chart_options(data) { | get_chart_options(data) { | ||||
@@ -106,6 +106,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { | |||||
get_args() { | get_args() { | ||||
const args = super.get_args(); | const args = super.get_args(); | ||||
args.group_by = null; | |||||
this.group_by_control.set_args(args); | this.group_by_control.set_args(args); | ||||
return args; | return args; | ||||
@@ -373,6 +373,18 @@ | |||||
.page-text { | .page-text { | ||||
display: inline-block; | display: inline-block; | ||||
cursor: default; | |||||
} | |||||
.current-page-number { | |||||
width: 16px; | |||||
text-align: center; | |||||
border: none; | |||||
cursor: text; | |||||
&:focus { | |||||
outline: none; | |||||
} | |||||
} | } | ||||
.prev-page, | .prev-page, | ||||
@@ -1,6 +1,6 @@ | |||||
.modal-body .theme-grid { | .modal-body .theme-grid { | ||||
display: grid; | display: grid; | ||||
grid-template-columns: repeat(2, minmax(0, 1fr)); | |||||
grid-template-columns: repeat(3, minmax(0, 1fr)); | |||||
grid-gap: 18px; | grid-gap: 18px; | ||||
.background { | .background { | ||||
@@ -9,7 +9,7 @@ | |||||
border-radius: var(--border-radius-lg); | border-radius: var(--border-radius-lg); | ||||
overflow: hidden; | overflow: hidden; | ||||
cursor: pointer; | cursor: pointer; | ||||
height: 160px; | |||||
height: 120px; | |||||
position: relative; | position: relative; | ||||
&:hover { | &:hover { | ||||
@@ -28,6 +28,7 @@ | |||||
margin-right: var(--margin-sm); | margin-right: var(--margin-sm); | ||||
border-radius: var(--border-radius-full); | border-radius: var(--border-radius-full); | ||||
z-index: 1; | |||||
} | } | ||||
} | } | ||||
@@ -72,6 +73,7 @@ | |||||
border-radius: var(--border-radius-sm); | border-radius: var(--border-radius-sm); | ||||
height: 10px; | height: 10px; | ||||
width: 20px; | width: 20px; | ||||
z-index: 1; | |||||
} | } | ||||
.text { | .text { | ||||
@@ -80,4 +82,17 @@ | |||||
height: 10px; | height: 10px; | ||||
width: 40px; | width: 40px; | ||||
} | } | ||||
} | |||||
// TODO: Replace with better alternative | |||||
[data-is-auto-theme="true"] { | |||||
.background::after { | |||||
content: ""; | |||||
top: 0; | |||||
right: 0; | |||||
height: 100%; | |||||
width: 50%; | |||||
background: var(--gray-900); | |||||
position: absolute; | |||||
} | |||||
} | } |
@@ -1,3 +1,8 @@ | |||||
:root { | |||||
--comment-timeline-bottom: 60px; | |||||
--comment-timeline-top: 8px; | |||||
} | |||||
.blog-list { | .blog-list { | ||||
display: flex; | display: flex; | ||||
flex-wrap: wrap; | flex-wrap: wrap; | ||||
@@ -96,4 +101,124 @@ | |||||
margin-top: 3rem; | margin-top: 3rem; | ||||
} | } | ||||
} | } | ||||
.feedback-item svg { | |||||
vertical-align: sub; | |||||
} | |||||
.blog-feedback { | |||||
display: flex; | |||||
.like-icon { | |||||
cursor: pointer; | |||||
&.gray use { | |||||
fill: var(--gray-600); | |||||
stroke: none; | |||||
} | |||||
} | |||||
} | |||||
.add-comment-button { | |||||
margin-left: 35px; | |||||
} | |||||
.timeline-dot { | |||||
width: 16px; | |||||
height: 16px; | |||||
border-radius: 50%; | |||||
position: absolute; | |||||
top: 8px; | |||||
left: 22px; | |||||
background-color: var(--fg-color); | |||||
border: 1px solid var(--dark-border-color); | |||||
&:before { | |||||
content: ' '; | |||||
background: var(--gray-600); | |||||
position: absolute; | |||||
top: 5px; | |||||
left: 5px; | |||||
border-radius: 50%; | |||||
height: 4px; | |||||
width: 4px; | |||||
} | |||||
} | |||||
.blog-comments { | |||||
.comment-form-wrapper { | |||||
display: none; | |||||
} | |||||
.add-comment-section { | |||||
.login-required { | |||||
padding: var(--padding-sm); | |||||
border-radius: var(--border-radius-sm); | |||||
box-shadow: var(--card-shadow); | |||||
} | |||||
.new-comment { | |||||
display: flex; | |||||
padding: var(--padding-lg); | |||||
box-shadow: var(--card-shadow); | |||||
border-radius: var(--border-radius-md); | |||||
.new-comment-fields { | |||||
flex: 1; | |||||
.form-label { | |||||
font-weight: var(--text-bold); | |||||
} | |||||
.comment-text-area textarea { | |||||
resize: none; | |||||
} | |||||
@media (min-width: 576px) { | |||||
.comment-by { | |||||
padding-right: 0px !important; | |||||
padding-bottom: 0px !important; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
#comment-list { | |||||
position: relative; | |||||
padding-left: var(--padding-xl); | |||||
&:before { | |||||
content: " "; | |||||
position: absolute; | |||||
top: var(--comment-timeline-top); | |||||
bottom: var(--comment-timeline-bottom); | |||||
border-left: 1px solid var(--dark-border-color); | |||||
} | |||||
.comment-row { | |||||
position: relative; | |||||
.comment-avatar { | |||||
position: absolute; | |||||
top: 10px; | |||||
left: -17px; | |||||
} | |||||
.comment-content { | |||||
box-shadow: var(--card-shadow); | |||||
border-radius: var(--border-radius-md); | |||||
padding: var(--padding-md); | |||||
margin-left: 35px; | |||||
flex: 1; | |||||
.content p{ | |||||
margin-bottom: 0px; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |
@@ -99,6 +99,12 @@ | |||||
} | } | ||||
} | } | ||||
.page-header-wrapper { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
} | |||||
.breadcrumb-container { | .breadcrumb-container { | ||||
margin-top: 1rem; | margin-top: 1rem; | ||||
padding-top: 0.25rem; | padding-top: 0.25rem; | ||||
@@ -171,6 +171,8 @@ def get(): | |||||
bootinfo["setup_complete"] = cint(frappe.db.get_single_value('System Settings', 'setup_complete')) | bootinfo["setup_complete"] = cint(frappe.db.get_single_value('System Settings', 'setup_complete')) | ||||
bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup')) | bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup')) | ||||
bootinfo['desk_theme'] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or 'Light' | |||||
return bootinfo | return bootinfo | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
@@ -32,7 +32,9 @@ class EnergyPointLog(Document): | |||||
frappe.cache().hdel('energy_points', self.user) | frappe.cache().hdel('energy_points', self.user) | ||||
frappe.publish_realtime('update_points', after_commit=True) | frappe.publish_realtime('update_points', after_commit=True) | ||||
if self.type != 'Review': | |||||
if self.type != 'Review' and \ | |||||
frappe.get_cached_value('Notification Settings', self.user, 'energy_points_system_notifications'): | |||||
reference_user = self.user if self.type == 'Auto' else self.owner | reference_user = self.user if self.type == 'Auto' else self.owner | ||||
notification_doc = { | notification_doc = { | ||||
'type': 'Energy Point', | 'type': 'Energy Point', | ||||
@@ -8,6 +8,18 @@ from frappe.utils.testutils import add_custom_field, clear_custom_fields | |||||
from frappe.desk.form.assign_to import add as assign_to | from frappe.desk.form.assign_to import add as assign_to | ||||
class TestEnergyPointLog(unittest.TestCase): | class TestEnergyPointLog(unittest.TestCase): | ||||
@classmethod | |||||
def setUpClass(cls): | |||||
settings = frappe.get_single('Energy Point Settings') | |||||
settings.enabled = 1 | |||||
settings.save() | |||||
@classmethod | |||||
def tearDownClass(cls): | |||||
settings = frappe.get_single('Energy Point Settings') | |||||
settings.enabled = 0 | |||||
settings.save() | |||||
def setUp(self): | def setUp(self): | ||||
frappe.cache().delete_value('energy_point_rule_map') | frappe.cache().delete_value('energy_point_rule_map') | ||||
@@ -336,4 +348,4 @@ def assign_users_to_todo(todo_name, users): | |||||
'assign_to': [user], | 'assign_to': [user], | ||||
'doctype': 'ToDo', | 'doctype': 'ToDo', | ||||
'name': todo_name | 'name': todo_name | ||||
}) | |||||
}) |
@@ -1,229 +1,70 @@ | |||||
{ | { | ||||
"allow_copy": 0, | |||||
"allow_events_in_timeline": 0, | |||||
"allow_guest_to_view": 0, | |||||
"allow_import": 0, | |||||
"allow_rename": 0, | |||||
"beta": 0, | |||||
"actions": [], | |||||
"creation": "2019-03-19 13:17:51.710241", | "creation": "2019-03-19 13:17:51.710241", | ||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "", | |||||
"editable_grid": 1, | "editable_grid": 1, | ||||
"engine": "InnoDB", | "engine": "InnoDB", | ||||
"field_order": [ | |||||
"enabled", | |||||
"section_break_2", | |||||
"review_levels", | |||||
"point_allocation_periodicity", | |||||
"last_point_allocation_date" | |||||
], | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_in_quick_entry": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"default": "1", | |||||
"fetch_if_empty": 0, | |||||
"default": "0", | |||||
"fieldname": "enabled", | "fieldname": "enabled", | ||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "Enabled", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"translatable": 0, | |||||
"unique": 0 | |||||
"label": "Enabled" | |||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_in_quick_entry": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"depends_on": "enabled", | "depends_on": "enabled", | ||||
"fetch_if_empty": 0, | |||||
"fieldname": "section_break_2", | "fieldname": "section_break_2", | ||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"translatable": 0, | |||||
"unique": 0 | |||||
"fieldtype": "Section Break" | |||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_in_quick_entry": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fetch_if_empty": 0, | |||||
"fieldname": "review_levels", | "fieldname": "review_levels", | ||||
"fieldtype": "Table", | "fieldtype": "Table", | ||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "Review Levels", | "label": "Review Levels", | ||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "Review Level", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"translatable": 0, | |||||
"unique": 0 | |||||
"options": "Review Level" | |||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_in_quick_entry": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"default": "Weekly", | "default": "Weekly", | ||||
"fetch_if_empty": 0, | |||||
"fieldname": "point_allocation_periodicity", | "fieldname": "point_allocation_periodicity", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "Point Allocation Periodicity", | "label": "Point Allocation Periodicity", | ||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "Daily\nWeekly\nMonthly", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"translatable": 0, | |||||
"unique": 0 | |||||
"options": "Daily\nWeekly\nMonthly" | |||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_in_quick_entry": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fetch_if_empty": 0, | |||||
"fieldname": "last_point_allocation_date", | "fieldname": "last_point_allocation_date", | ||||
"fieldtype": "Date", | "fieldtype": "Date", | ||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "Last Point Allocation Date", | "label": "Last Point Allocation Date", | ||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"translatable": 0, | |||||
"unique": 0 | |||||
"read_only": 1 | |||||
} | } | ||||
], | ], | ||||
"has_web_view": 0, | |||||
"hide_toolbar": 1, | "hide_toolbar": 1, | ||||
"idx": 0, | |||||
"in_create": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 1, | "issingle": 1, | ||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"modified": "2019-03-26 19:10:14.087840", | |||||
"links": [], | |||||
"modified": "2021-11-16 23:24:01.366928", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Social", | "module": "Social", | ||||
"name": "Energy Point Settings", | "name": "Energy Point Settings", | ||||
"name_case": "", | |||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"amend": 0, | |||||
"cancel": 0, | |||||
"create": 1, | "create": 1, | ||||
"delete": 1, | "delete": 1, | ||||
"email": 1, | "email": 1, | ||||
"export": 0, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | "print": 1, | ||||
"read": 1, | "read": 1, | ||||
"report": 0, | |||||
"role": "System Manager", | "role": "System Manager", | ||||
"set_user_permissions": 0, | |||||
"share": 1, | "share": 1, | ||||
"submit": 0, | |||||
"write": 1 | "write": 1 | ||||
} | } | ||||
], | ], | ||||
"quick_entry": 1, | "quick_entry": 1, | ||||
"read_only": 0, | |||||
"show_name_in_global_search": 0, | |||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "ASC", | "sort_order": "ASC", | ||||
"track_changes": 1, | |||||
"track_seen": 0, | |||||
"track_views": 0 | |||||
"track_changes": 1 | |||||
} | } |
@@ -0,0 +1,8 @@ | |||||
# Copyright (c) 2021, Frappe Technologies and Contributors | |||||
# See license.txt | |||||
# import frappe | |||||
import unittest | |||||
class TestEnergyPointSettings(unittest.TestCase): | |||||
pass |
@@ -1,6 +1,6 @@ | |||||
{% macro avatar(user_id=None, css_style=None) %} | |||||
{% macro avatar(user_id=None, css_style=None, size="avatar-small") %} | |||||
{% set user_info = frappe.utils.get_user_info_for_avatar(user_id) %} | {% set user_info = frappe.utils.get_user_info_for_avatar(user_id) %} | ||||
<span class="avatar avatar-small" title="{{ user_info.name }}" style="{{ css_style or '' }}"> | |||||
<span class="avatar {{ size }}" title="{{ user_info.name }}" style="{{ css_style or '' }}"> | |||||
{% if user_info.image %} | {% if user_info.image %} | ||||
<img | <img | ||||
class="avatar-frame standard-image" | class="avatar-frame standard-image" | ||||
@@ -11,7 +11,7 @@ | |||||
<span | <span | ||||
class="avatar-frame standard-image" | class="avatar-frame standard-image" | ||||
title="{{ user_info.name }}"> | title="{{ user_info.name }}"> | ||||
{{ frappe.utils.get_abbr(user_info.name) }} | |||||
{{ frappe.utils.get_abbr(user_info.name).upper() }} | |||||
</span> | </span> | ||||
{% endif %} | {% endif %} | ||||
</span> | </span> |
@@ -1,18 +1,14 @@ | |||||
{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %} | |||||
{% from "frappe/templates/includes/avatar_macro.html" import avatar %} | |||||
<div class="comment-row media"> | |||||
{{ square_image_with_fallback(src=frappe.get_gravatar(comment.comment_email or comment.sender), size='extra-small', alt=comment.sender_full_name, class='align-self-start mr-4') }} | |||||
<div class="media-body"> | |||||
<div class="d-flex justify-content-between align-items-start"> | |||||
<span class="font-weight-bold text-muted"> | |||||
{{ comment.sender_full_name or comment.comment_by }} | |||||
</span> | |||||
<span class="text-muted small"> | |||||
{{ comment.creation | global_date_format }} | |||||
</span> | |||||
</div> | |||||
<div class="text-muted"> | |||||
{{ comment.content | markdown }} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="comment-row media my-5"> | |||||
<div class="comment-avatar"> | |||||
{{ avatar(user_id=(comment.comment_email or comment.sender), size='avatar-medium') }} | |||||
</div> | |||||
<div class="comment-content"> | |||||
<div class="head mb-2"> | |||||
<span class="title font-weight-bold mr-2">{{ comment.sender_full_name or comment.comment_by }}</span> | |||||
<span class="time small text-muted">{{ frappe.utils.pretty_date(comment.creation) }}</span> | |||||
</div> | |||||
<div class="content">{{ comment.content | markdown }}</div> | |||||
</div> | |||||
</div> |
@@ -1,86 +1,146 @@ | |||||
<div class="comment-view mb-6"> | <div class="comment-view mb-6"> | ||||
{% if comment_text %} | |||||
<div class="comment-header mb-6">{{ comment_text }}</div> | |||||
{% endif %} | |||||
{% if not comment_list %} | {% if not comment_list %} | ||||
<div class="no-comment"> | |||||
<p class="text-muted small">{{ _("No comments yet. Start a new discussion.") }}</p> | |||||
</div> | |||||
<div class="no-comment"> | |||||
<p class="text-muted small">{{ _("No comments yet. ") }} | |||||
<span class="hidden login-required"> | |||||
<a href="/login?redirect-to={{ pathname }}">{{ _("Login to start a new discussion") }}</a> | |||||
</span> | |||||
<span class="hidden start-discussion">{{ _("Start a new discussion") }}</span> | |||||
</p> | |||||
</div> | |||||
{% endif %} | {% endif %} | ||||
<div itemscope itemtype="http://schema.org/UserComments" id="comment-list"> | |||||
{% for comment in comment_list %} | |||||
<div class="my-3"> | |||||
{% include "templates/includes/comments/comment.html" %} | |||||
{% if not is_communication %} | |||||
<div class="add-comment-section mb-5"> | |||||
<div class="comment-form-wrapper"> | |||||
<div id="comment-form"> | |||||
<form class="new-comment"> | |||||
<fieldset class="new-comment-fields"> | |||||
<div class="user-details row" style="margin-bottom: 15px; display:none;"> | |||||
<div class="comment-by col-sm-6 pb-4"> | |||||
<div class="form-label mb-1">{{ _("Your Name") }}</div> | |||||
<input class="form-control comment_by" name="comment_by" type="text"> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<div class="form-label mb-1">{{ _("Email") }}</div> | |||||
<input class="form-control comment_email" name="comment_email" type="email"> | |||||
</div> | |||||
</div> | |||||
<div class="comment-text-area"> | |||||
<div class="form-label mb-1">{{ _("Add a comment") }}</div> | |||||
<textarea class="form-control" name="comment" rows=5 ></textarea> | |||||
<div class="text-muted small mt-1 mb-4">{{ _("Ctrl+Enter to add comment") }}</div> | |||||
</div> | |||||
<button class="btn btn-sm small" id="submit-comment">{{ _("Comment") }}</button> | |||||
</fieldset> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
{% endif %} | |||||
{% if not is_communication %} | |||||
<div class="add-comment-section"> | |||||
<div class="text-muted hidden login-required"> | |||||
<a href="/login?redirect-to={{ pathname }}">{{ _("Login to comment") }}</a> | |||||
</div> | |||||
<hr class="add-comment-hr my-5"> | |||||
<div class="comment-form-wrapper"> | |||||
<a class="add-comment btn btn-light btn-sm">{{ _("Add Comment") }}</a> | |||||
<div style="display: none;" id="comment-form"> | |||||
<p>{{ _("Leave a Comment") }}</p> | |||||
<div class="alert" style="display:none;"></div> | |||||
<form> | |||||
<fieldset> | |||||
<div class="row" style="margin-bottom: 15px;"> | |||||
<div class="col-sm-6"> | |||||
<input class="form-control comment_by" name="comment_by" placeholder="{{ _("Your Name") }}" type="text"> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<input class="form-control comment_email" name="comment_email" placeholder="{{ _("Your Email Address") }}" type="email"> | |||||
</div> | |||||
</div> | |||||
<p><textarea class="form-control" name="comment" rows=10 | |||||
placeholder="{{ _("Comment") }}"></textarea></p> | |||||
<button class="btn btn-primary btn-sm" id="submit-comment" style="margin-top:10px">{{ _("Submit") }}</button> | |||||
</fieldset> | |||||
</form> | |||||
<div itemscope itemtype="http://schema.org/UserComments" id="comment-list"> | |||||
<div class="add-comment mb-5"> | |||||
<div class="timeline-dot"></div> | |||||
<button class="btn btn-sm small add-comment-button">{{ _("Add a comment") }}</button> | |||||
</div> | |||||
<div class="comment-list"> | |||||
{% for comment in comment_list %} | |||||
{% include "templates/includes/comments/comment.html" %} | |||||
{% endfor %} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
{% endif %} | |||||
<script> | <script> | ||||
frappe.ready(function() { | frappe.ready(function() { | ||||
let guest_allowed = "{{ guest_allowed or ''}}"; | |||||
let guest_allowed = parseInt("{{ guest_allowed or 0}}"); | |||||
let comment_count = "{{ comment_text }}"; | |||||
let full_name = "" | |||||
let user_id = ""; | |||||
let update_timeline_line_length = function(direction, size) { | |||||
if (direction == 'top') { | |||||
$('.blog-container')[0].style.setProperty('--comment-timeline-top', size); | |||||
} else { | |||||
let comment_timeline_bottom = $('.comment-list .comment-row:last-child').height() - 10; | |||||
$('.blog-container')[0].style.setProperty('--comment-timeline-bottom', comment_timeline_bottom +'px'); | |||||
} | |||||
} | |||||
let show_comment_box = function() { | |||||
$('.comment-form-wrapper').show(); | |||||
update_timeline_line_length('top', '-20px'); | |||||
$('.add-comment-hr').hide(); | |||||
$('.add-comment').hide(); | |||||
} | |||||
let hide_comment_box = function() { | |||||
$('.comment-form-wrapper').hide(); | |||||
update_timeline_line_length('top', '8px'); | |||||
update_timeline_line_length('bottom'); | |||||
$('.add-comment-hr').show(); | |||||
$('.add-comment').show(); | |||||
} | |||||
let $comment_count = $(` | |||||
<div class="feedback-item"> | |||||
<span class="comment-icon">${frappe.utils.icon('small-message', 'md')}</span> | |||||
<span class="comment-count"></span> | |||||
</div> | |||||
`); | |||||
$('form').keydown(function(event) { | |||||
if (event.ctrlKey && event.keyCode === 13) { | |||||
$(this).find('#submit-comment').trigger('click'); | |||||
} | |||||
}) | |||||
if (!frappe.is_user_logged_in()) { | if (!frappe.is_user_logged_in()) { | ||||
!guest_allowed && $(".login-required, .comment-form-wrapper").toggleClass("hidden"); | |||||
$(".user-details").toggle('hide'); | |||||
if (guest_allowed) { | |||||
$('.start-discussion').removeClass('hidden'); | |||||
} else { | |||||
$(".login-required, .comment-form-wrapper").toggleClass("hidden"); | |||||
$('.add-comment-button').text('{{ _("Login to comment") }}'); | |||||
$('.add-comment-button').click(() => { | |||||
window.location.href = '/login?redirect-to={{ pathname }}'; | |||||
}); | |||||
} | |||||
} else { | } else { | ||||
$('input.comment_by').prop("disabled", true); | $('input.comment_by').prop("disabled", true); | ||||
$('input.comment_email').prop("disabled", true); | $('input.comment_email').prop("disabled", true); | ||||
} | |||||
var n_comments = $(".comment-row").length; | |||||
full_name = frappe.get_cookie("full_name"); | |||||
user_id = frappe.get_cookie("user_id"); | |||||
if(user_id != "Guest") { | |||||
$("[name='comment_email']").val(user_id); | |||||
$("[name='comment_by']").val(full_name); | |||||
} | |||||
if(n_comments) { | |||||
$(".no_comment").toggle(false); | |||||
$('.start-discussion').removeClass('hidden'); | |||||
} | } | ||||
$('.blog-feedback').append($comment_count); | |||||
$('.comment-count').text(comment_count); | |||||
$("#comment-form textarea").val(""); | |||||
update_timeline_line_length('bottom'); | |||||
let n_comments = $(".comment-row").length; | |||||
n_comments ? $(".no_comment").toggle(false) : show_comment_box(); | |||||
if(n_comments > 50) { | if(n_comments > 50) { | ||||
$(".add-comment").toggle(false) | $(".add-comment").toggle(false) | ||||
.parent().append("<div class='text-muted'>Comments are closed.</div>") | .parent().append("<div class='text-muted'>Comments are closed.</div>") | ||||
} | } | ||||
$(".add-comment").click(function() { | |||||
$(this).toggle(false); | |||||
$("#comment-form").toggle(); | |||||
var full_name = "", user_id = ""; | |||||
if(frappe.is_user_logged_in()) { | |||||
full_name = frappe.get_cookie("full_name"); | |||||
user_id = frappe.get_cookie("user_id"); | |||||
if(user_id != "Guest") { | |||||
$("[name='comment_email']").val(user_id); | |||||
$("[name='comment_by']").val(full_name); | |||||
} | |||||
} | |||||
$("#comment-form textarea").val(""); | |||||
}) | |||||
$('.add-comment-button').click(() => { | |||||
show_comment_box(); | |||||
}); | |||||
$("#submit-comment").click(function() { | $("#submit-comment").click(function() { | ||||
var args = { | var args = { | ||||
@@ -94,17 +154,17 @@ | |||||
} | } | ||||
if(!args.comment_by || !args.comment_email || !args.comment) { | if(!args.comment_by || !args.comment_email || !args.comment) { | ||||
frappe.msgprint("{{ _("All fields are necessary to submit the comment.") }}"); | |||||
frappe.msgprint('{{ _("All fields are necessary to submit the comment.") }}'); | |||||
return false; | return false; | ||||
} | } | ||||
if (args.comment_email!=='Administrator' && !validate_email(args.comment_email)) { | if (args.comment_email!=='Administrator' && !validate_email(args.comment_email)) { | ||||
frappe.msgprint("{{ _("Please enter a valid email address.") }}"); | |||||
frappe.msgprint('{{ _("Please enter a valid email address.") }}'); | |||||
return false; | return false; | ||||
} | } | ||||
if(!args.comment || !args.comment.trim()) { | if(!args.comment || !args.comment.trim()) { | ||||
frappe.msgprint("{{ _("Please add a valid comment.") }}"); | |||||
frappe.msgprint('{{ _("Please add a valid comment.") }}'); | |||||
return false; | return false; | ||||
} | } | ||||
@@ -119,17 +179,18 @@ | |||||
frappe.msgprint(r._server_messages); | frappe.msgprint(r._server_messages); | ||||
} else { | } else { | ||||
if (r.message) { | if (r.message) { | ||||
$(r.message).appendTo("#comment-list"); | |||||
$(".add-comment").text(__("Add Another Comment")); | |||||
$(r.message).prependTo(".comment-list"); | |||||
comment_count = cint(comment_count) + 1; | |||||
$('.comment-count').text(comment_count); | |||||
} | } | ||||
$(".no-comment, .add-comment").toggle(false); | |||||
$("#comment-form").toggle(); | |||||
$(".add-comment").toggle(); | |||||
$(".no-comment").toggle(false); | |||||
$("#comment-form textarea").val(""); | |||||
hide_comment_box(); | |||||
} | } | ||||
} | } | ||||
}) | }) | ||||
return false; | return false; | ||||
}) | |||||
}); | |||||
}); | }); | ||||
</script> | </script> |
@@ -3,11 +3,14 @@ | |||||
import frappe | import frappe | ||||
import re | import re | ||||
from frappe.website.utils import clear_cache | from frappe.website.utils import clear_cache | ||||
from frappe.rate_limiter import rate_limit | |||||
from frappe.utils import add_to_date, now | from frappe.utils import add_to_date, now | ||||
from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit | |||||
from frappe import _ | from frappe import _ | ||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
@rate_limit(key='reference_name', limit=get_comment_limit, seconds=60*60) | |||||
def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): | def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): | ||||
doc = frappe.get_doc(reference_doctype, reference_name) | doc = frappe.get_doc(reference_doctype, reference_name) | ||||
@@ -1,160 +1,43 @@ | |||||
<div class="add-feedback-section"> | |||||
<div class="feedback-form-wrapper"> | |||||
<a class="give-feedback btn btn-light btn-sm">{{ _("How would you rate the blog?") }}</a> | |||||
<div style="display: none;" id="feedback-form"> | |||||
<p>{{ _("How would you rate the blog?") }}</p> | |||||
<div class="alert" style="display:none;"></div> | |||||
<form> | |||||
<fieldset> | |||||
<div class="row" style="margin-bottom: 15px;"> | |||||
<div class="col-sm-6"> | |||||
<div class="rating"> | |||||
{% for rating in [1, 2, 3, 4, 5 ,6, 7, 8, 9, 10] %} | |||||
<div class="icon rating-box" data-rating="{{ rating }}">{{ rating }}</div> | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<p> | |||||
<textarea class="form-control" name="feedback" rows=10 placeholder="{{ _("Feedback") }}"></textarea> | |||||
</p> | |||||
<button class="btn btn-sm" id="toggle-feedback" style="margin-top:10px; margin-right:2px;"> | |||||
{{ _("Back") }} | |||||
</button> | |||||
<button class="btn btn-primary btn-sm" id="submit-feedback" style="margin-top:10px"> | |||||
{{ _("Submit") }} | |||||
</button> | |||||
</fieldset> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
<div class="feedback-item mr-3"> | |||||
<span class="like-icon"></span> | |||||
<span class="like-count"></span> | |||||
</div> | </div> | ||||
<script type="text/javascript"> | <script type="text/javascript"> | ||||
frappe.ready(() => { | frappe.ready(() => { | ||||
let feedback = "{{ user_feedback.feedback or ''}}" | |||||
let user_rating = parseInt("{{ user_feedback.rating or 0 }}") | |||||
let rating = user_rating; | |||||
feedback && $("#submit-feedback").html(__("Update")); | |||||
if (frappe.is_user_logged_in()) { | |||||
if (feedback) { | |||||
$("[name='feedback']").val(feedback); | |||||
toggle_feedback(); | |||||
set_rating(rating); | |||||
} | |||||
let like = parseInt("{{ user_feedback.like or 0 }}"); | |||||
let like_count = parseInt("{{ like_count or 0 }}"); | |||||
let update_like = function() { | |||||
like = !like; | |||||
like ? like_count++ : like_count--; | |||||
toggle_like_icon(like); | |||||
$('.like-count').text(like_count); | |||||
} | } | ||||
$('.give-feedback').click(() => toggle_feedback()); | |||||
$('.rating').find('.rating-box').hover((ev) => { | |||||
const el = $(ev.currentTarget); | |||||
rating = el.data('rating'); | |||||
el.parent().children('.rating-box').each( function(e) { | |||||
if (e < rating) { | |||||
$(this).addClass('rating-hover'); | |||||
} else { | |||||
$(this).removeClass('rating-hover'); | |||||
} | |||||
}); | |||||
}, (ev) => { | |||||
const el = $(ev.currentTarget); | |||||
el.parent().children('.rating-box').each( function() { | |||||
$(this).removeClass('rating-hover'); | |||||
}); | |||||
}); | |||||
$('.rating').find('.rating-box').click((ev) => { | |||||
const el = $(ev.currentTarget); | |||||
rating = el.data('rating'); | |||||
el.parent().children('.rating-box').each( function(e) { | |||||
if (e < rating) { | |||||
$(this).addClass('rating-click'); | |||||
} else { | |||||
$(this).removeClass('rating-click'); | |||||
} | |||||
}); | |||||
}); | |||||
$('#submit-feedback').click((ev) => { | |||||
let update = ev.target.innerText !== __("Submit"); | |||||
let rating = $('.rating').find('.rating-click').length; | |||||
let args = { | |||||
reference_doctype: "{{ reference_doctype or doctype }}", | |||||
reference_name: "{{ reference_name or name }}", | |||||
rating: rating, | |||||
feedback: $("[name='feedback']").val() | |||||
} | |||||
if (args.rating == 0) { | |||||
frappe.msgprint("{{ _("Rating is required!") }}"); | |||||
return false; | |||||
} | |||||
let toggle_like_icon = function(active) { | |||||
active ? $('.like-icon').addClass('gray') : $('.like-icon').removeClass('gray'); | |||||
} | |||||
if (!args.feedback || !args.feedback.trim()) { | |||||
frappe.msgprint("{{ _("Please add a valid feedback.") }}"); | |||||
return false; | |||||
} | |||||
$('.like-icon').append(frappe.utils.icon('heart', 'md')) | |||||
toggle_like_icon(like); | |||||
if (!update) { | |||||
frappe.call({ | |||||
method: "frappe.templates.includes.feedback.feedback.add_feedback", | |||||
args: args, | |||||
callback: function(r) { | |||||
if (!r.message) { | |||||
return | |||||
} | |||||
toggle_feedback(); | |||||
if (!frappe.is_user_logged_in()) { | |||||
$("[name='feedback']").val(''); | |||||
set_rating(0); | |||||
} else { | |||||
feedback = $("[name='feedback']").val(); | |||||
user_rating = rating; | |||||
$("#submit-feedback").html(__("Update")); | |||||
} | |||||
frappe.msgprint({message:__("Thank you for your valuable feedback!"), indicator:'green'}); | |||||
} | |||||
}) | |||||
} else { | |||||
if (feedback == $("[name='feedback']").val() && rating == user_rating) { | |||||
frappe.msgprint({message:__("Please update rating or feedback before saving."), indicator:'red'}); | |||||
return false; | |||||
} | |||||
frappe.call({ | |||||
method: "frappe.templates.includes.feedback.feedback.update_feedback", | |||||
args: args, | |||||
callback: function(r) { | |||||
toggle_feedback(); | |||||
feedback = $("[name='feedback']").val(); | |||||
user_rating = rating; | |||||
frappe.msgprint({message:__("Feedback updated successfully!"), indicator:'green'}); | |||||
} | |||||
}) | |||||
} | |||||
return false; | |||||
}) | |||||
$('.like-count').text(like_count); | |||||
$('#toggle-feedback').click(() => { | |||||
toggle_feedback(); | |||||
return false; | |||||
$('.like-icon').click(() => { | |||||
update_like(); | |||||
update_feedback(); | |||||
}) | }) | ||||
function set_rating(rating) { | |||||
let el = $('.rating').find('.rating-box'); | |||||
el.children('.rating-box').prevObject.each( function(e) { | |||||
if (e < rating) { | |||||
$(this).addClass('rating-click'); | |||||
} else { | |||||
$(this).removeClass('rating-click'); | |||||
let update_feedback = function() { | |||||
return frappe.call({ | |||||
method: "frappe.templates.includes.feedback.feedback.give_feedback", | |||||
args: { | |||||
reference_doctype: "{{ reference_doctype or doctype }}", | |||||
reference_name: "{{ reference_name or name }}", | |||||
like | |||||
} | } | ||||
}); | }); | ||||
} | |||||
function toggle_feedback() { | |||||
$(".give-feedback").toggle(); | |||||
$("#feedback-form").toggle(); | |||||
} | |||||
} | |||||
}); | }); | ||||
</script> | </script> |
@@ -10,25 +10,8 @@ from frappe.website.doctype.blog_settings.blog_settings import get_feedback_limi | |||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
@rate_limit(key='reference_name', limit=get_feedback_limit, seconds=60*60) | @rate_limit(key='reference_name', limit=get_feedback_limit, seconds=60*60) | ||||
def add_feedback(reference_doctype, reference_name, rating, feedback): | |||||
doc = frappe.get_doc(reference_doctype, reference_name) | |||||
if doc.disable_feedback == 1: | |||||
return | |||||
doc = frappe.new_doc('Feedback') | |||||
doc.reference_doctype = reference_doctype | |||||
doc.reference_name = reference_name | |||||
doc.rating = rating | |||||
doc.feedback = feedback | |||||
doc.ip_address = frappe.local.request_ip | |||||
doc.save(ignore_permissions=True) | |||||
subject = _('New Feedback on {0}: {1}').format(reference_doctype, reference_name) | |||||
send_mail(doc, subject) | |||||
return doc | |||||
@frappe.whitelist() | |||||
def update_feedback(reference_doctype, reference_name, rating, feedback): | |||||
def give_feedback(reference_doctype, reference_name, like): | |||||
like = frappe.parse_json(like) | |||||
doc = frappe.get_doc(reference_doctype, reference_name) | doc = frappe.get_doc(reference_doctype, reference_name) | ||||
if doc.disable_feedback == 1: | if doc.disable_feedback == 1: | ||||
return | return | ||||
@@ -39,22 +22,26 @@ def update_feedback(reference_doctype, reference_name, rating, feedback): | |||||
"reference_name": reference_name | "reference_name": reference_name | ||||
} | } | ||||
d = frappe.get_all('Feedback', filters=filters, limit=1) | d = frappe.get_all('Feedback', filters=filters, limit=1) | ||||
doc = frappe.get_doc('Feedback', d[0].name) | |||||
doc.rating = rating | |||||
doc.feedback = feedback | |||||
if d: | |||||
doc = frappe.get_doc('Feedback', d[0].name) | |||||
else: | |||||
doc = doc = frappe.new_doc('Feedback') | |||||
doc.reference_doctype = reference_doctype | |||||
doc.reference_name = reference_name | |||||
doc.ip_address = frappe.local.request_ip | |||||
doc.like = like | |||||
doc.save(ignore_permissions=True) | doc.save(ignore_permissions=True) | ||||
subject = _('Feedback updated on {0}: {1}').format(reference_doctype, reference_name) | |||||
subject = _('Feedback on {0}: {1}').format(reference_doctype, reference_name) | |||||
send_mail(doc, subject) | send_mail(doc, subject) | ||||
return doc | return doc | ||||
def send_mail(feedback, subject): | def send_mail(feedback, subject): | ||||
doc = frappe.get_doc(feedback.reference_doctype, feedback.reference_name) | doc = frappe.get_doc(feedback.reference_doctype, feedback.reference_name) | ||||
message = ("<p>{0} ({1})</p>".format(feedback.feedback, feedback.rating) | |||||
+ "<p><a href='{0}/app/feedback/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(), | |||||
feedback.name, | |||||
_("View Feedback"))) | |||||
if feedback.like: | |||||
message = "<p>Hey, </p><p>You have received a ❤️ heart on your blog post <b>{0}</b></p>".format(feedback.reference_name) | |||||
else: | |||||
return | |||||
# notify creator | # notify creator | ||||
frappe.sendmail( | frappe.sendmail( | ||||
@@ -14,7 +14,7 @@ | |||||
{% block page_container %} | {% block page_container %} | ||||
<main class="{% if not full_width %}container my-4{% endif %}"> | <main class="{% if not full_width %}container my-4{% endif %}"> | ||||
<div class="d-flex justify-content-between align-items-center"> | |||||
<div class="page-header-wrapper"> | |||||
<div class="page-header"> | <div class="page-header"> | ||||
{% block header %}{% endblock %} | {% block header %}{% endblock %} | ||||
</div> | </div> | ||||
@@ -43,6 +43,13 @@ class TestDB(unittest.TestCase): | |||||
run=False, | run=False, | ||||
).lower(), | ).lower(), | ||||
) | ) | ||||
self.assertEqual( | |||||
frappe.db.sql("select email from tabUser where name='Administrator' order by modified DESC"), | |||||
frappe.db.get_values( | |||||
"User", filters=[["name", "=", "Administrator"]], fieldname="email" | |||||
), | |||||
) | |||||
def test_set_value(self): | def test_set_value(self): | ||||
todo1 = frappe.get_doc(dict(doctype='ToDo', description = 'test_set_value 1')).insert() | todo1 = frappe.get_doc(dict(doctype='ToDo', description = 'test_set_value 1')).insert() | ||||
todo2 = frappe.get_doc(dict(doctype='ToDo', description = 'test_set_value 2')).insert() | todo2 = frappe.get_doc(dict(doctype='ToDo', description = 'test_set_value 2')).insert() | ||||
@@ -0,0 +1,22 @@ | |||||
import unittest | |||||
import frappe | |||||
from frappe.www.printview import get_html_and_style | |||||
class PrintViewTest(unittest.TestCase): | |||||
def test_print_view_without_errors(self): | |||||
user = frappe.get_last_doc("User") | |||||
messages_before = frappe.get_message_log() | |||||
ret = get_html_and_style(doc=user.as_json(), print_format="Standard", no_letterhead=1) | |||||
messages_after = frappe.get_message_log() | |||||
if len(messages_after) > len(messages_before): | |||||
new_messages = messages_after[len(messages_before):] | |||||
self.fail("Print view showing error/warnings: \n" | |||||
+ "\n".join(str(msg) for msg in new_messages)) | |||||
# html should exist | |||||
self.assertTrue(bool(ret["html"])) |
@@ -10,6 +10,8 @@ | |||||
# 3. call update_nsm(doc_obj) in the on_upate method | # 3. call update_nsm(doc_obj) in the on_upate method | ||||
# ------------------------------------------ | # ------------------------------------------ | ||||
from typing import Iterator | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
@@ -271,6 +273,19 @@ class NestedSet(Document): | |||||
def get_ancestors(self): | def get_ancestors(self): | ||||
return get_ancestors_of(self.doctype, self.name) | return get_ancestors_of(self.doctype, self.name) | ||||
def get_parent(self) -> "NestedSet": | |||||
"""Return the parent Document.""" | |||||
parent_name = self.get(self.nsm_parent_field) | |||||
if parent_name: | |||||
return frappe.get_doc(self.doctype, parent_name) | |||||
def get_children(self) -> Iterator["NestedSet"]: | |||||
"""Return a generator that yields child Documents.""" | |||||
child_names = frappe.get_list(self.doctype, filters={self.nsm_parent_field: self.name}, pluck="name") | |||||
for name in child_names: | |||||
yield frappe.get_doc(self.doctype, name) | |||||
def get_root_of(doctype): | def get_root_of(doctype): | ||||
"""Get root element of a DocType with a tree structure""" | """Get root element of a DocType with a tree structure""" | ||||
result = frappe.db.sql("""select t1.name from `tab{0}` t1 where | result = frappe.db.sql("""select t1.name from `tab{0}` t1 where | ||||
@@ -5,18 +5,6 @@ import click | |||||
import frappe | import frappe | ||||
try: | |||||
from weasyprint import HTML, CSS | |||||
except OSError: | |||||
click.secho( | |||||
"\n".join(["WeasyPrint depdends on additional system dependencies.", | |||||
"Follow instructions specific to your operating system:", | |||||
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html"]), | |||||
fg="yellow" | |||||
) | |||||
raise | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def download_pdf(doctype, name, print_format, letterhead=None): | def download_pdf(doctype, name, print_format, letterhead=None): | ||||
doc = frappe.get_doc(doctype, name) | doc = frappe.get_doc(doctype, name) | ||||
@@ -121,6 +109,8 @@ class PrintFormatGenerator: | |||||
pdf: a bytes sequence | pdf: a bytes sequence | ||||
The rendered PDF. | The rendered PDF. | ||||
""" | """ | ||||
HTML, CSS = import_weasyprint() | |||||
self._make_header_footer() | self._make_header_footer() | ||||
self.context.update( | self.context.update( | ||||
@@ -151,6 +141,8 @@ class PrintFormatGenerator: | |||||
element_height: float | element_height: float | ||||
The height of this element, which will be then translated in a html height | The height of this element, which will be then translated in a html height | ||||
""" | """ | ||||
HTML, CSS = import_weasyprint() | |||||
html = HTML(string=getattr(self, f"{element}_html"), base_url=self.base_url,) | html = HTML(string=getattr(self, f"{element}_html"), base_url=self.base_url,) | ||||
element_doc = html.render( | element_doc = html.render( | ||||
stylesheets=[CSS(string="@page {size: A4 portrait; margin: 0;}")] | stylesheets=[CSS(string="@page {size: A4 portrait; margin: 0;}")] | ||||
@@ -254,3 +246,20 @@ class PrintFormatGenerator: | |||||
if box.element_tag == element: | if box.element_tag == element: | ||||
return box | return box | ||||
return PrintFormatGenerator.get_element(box.all_children(), element) | return PrintFormatGenerator.get_element(box.all_children(), element) | ||||
def import_weasyprint(): | |||||
try: | |||||
from weasyprint import HTML, CSS | |||||
return HTML, CSS | |||||
except OSError: | |||||
message = "\n".join([ | |||||
"WeasyPrint depdends on additional system dependencies.", | |||||
"Follow instructions specific to your operating system:", | |||||
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html" | |||||
]) | |||||
click.secho( | |||||
message, | |||||
fg="yellow" | |||||
) | |||||
frappe.throw(message) |
@@ -104,7 +104,7 @@ class BlogPost(WebsiteGenerator): | |||||
context.parents = [{"name": _("Home"), "route":"/"}, | context.parents = [{"name": _("Home"), "route":"/"}, | ||||
{"name": "Blog", "route": "/blog"}, | {"name": "Blog", "route": "/blog"}, | ||||
{"label": context.category.title, "route":context.category.route}] | {"label": context.category.title, "route":context.category.route}] | ||||
context.guest_allowed = True | |||||
context.guest_allowed = frappe.db.get_single_value("Blog Settings", "allow_guest_to_comment", cache=True) | |||||
def fetch_cta(self): | def fetch_cta(self): | ||||
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True): | if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True): | ||||
@@ -139,26 +139,36 @@ class BlogPost(WebsiteGenerator): | |||||
context.comment_list = get_comment_list(self.doctype, self.name) | context.comment_list = get_comment_list(self.doctype, self.name) | ||||
if not context.comment_list: | if not context.comment_list: | ||||
context.comment_text = _('No comments yet') | |||||
context.comment_text = 0 | |||||
else: | else: | ||||
if(len(context.comment_list)) == 1: | |||||
context.comment_text = _('1 comment') | |||||
else: | |||||
context.comment_text = _('{0} comments').format(len(context.comment_list)) | |||||
context.comment_text = len(context.comment_list) | |||||
def load_feedback(self, context): | def load_feedback(self, context): | ||||
user = frappe.session.user | user = frappe.session.user | ||||
if user == 'Guest': | |||||
user = '' | |||||
feedback = frappe.get_all('Feedback', | feedback = frappe.get_all('Feedback', | ||||
fields=['feedback', 'rating'], | |||||
fields=['like'], | |||||
filters=dict( | filters=dict( | ||||
reference_doctype=self.doctype, | reference_doctype=self.doctype, | ||||
reference_name=self.name, | reference_name=self.name, | ||||
ip_address=frappe.local.request_ip, | |||||
owner=user | owner=user | ||||
) | ) | ||||
) | ) | ||||
like_count = 0 | |||||
if frappe.db.count('Feedback'): | |||||
like_count = frappe.db.count('Feedback', | |||||
filters = dict( | |||||
reference_doctype = self.doctype, | |||||
reference_name = self.name, | |||||
like = True | |||||
) | |||||
) | |||||
context.user_feedback = feedback[0] if feedback else '' | context.user_feedback = feedback[0] if feedback else '' | ||||
context.like_count = like_count | |||||
def set_read_time(self): | def set_read_time(self): | ||||
content = self.content or self.content_html or '' | content = self.content or self.content_html or '' | ||||
@@ -43,21 +43,26 @@ | |||||
) }} | ) }} | ||||
{%- endif -%} | {%- endif -%} | ||||
<div class="blog-footer"> | <div class="blog-footer"> | ||||
<div> | |||||
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time> | |||||
<div class="blog-feedback"> | |||||
{% if not disable_feedback %} | |||||
{% include 'templates/includes/feedback/feedback.html' %} | |||||
{% endif %} | |||||
</div> | </div> | ||||
<div> | |||||
{% if social_links %} | |||||
{% if social_links %} | |||||
<div> | |||||
{% for link in social_links %} | {% for link in social_links %} | ||||
<a href="{{ link.link }}" class="text-muted ml-2 fa fa-{{ link.icon }}" target="_blank"></a> | <a href="{{ link.link }}" class="text-muted ml-2 fa fa-{{ link.icon }}" target="_blank"></a> | ||||
{% endfor %} | {% endfor %} | ||||
{% endif %} | |||||
</div> | |||||
{% endif %} | |||||
<div> | |||||
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{% if blogger_info %} | {% if blogger_info %} | ||||
<hr class="my-5"> | |||||
{% include "templates/includes/blog/blogger.html" %} | |||||
<hr class="mt-2 mb-5"> | |||||
{% include "templates/includes/blog/blogger.html" %} | |||||
{% endif %} | {% endif %} | ||||
{% if not disable_comments %} | {% if not disable_comments %} | ||||
@@ -65,11 +70,6 @@ | |||||
{% include 'templates/includes/comments/comments.html' %} | {% include 'templates/includes/comments/comments.html' %} | ||||
</div> | </div> | ||||
{% endif %} | {% endif %} | ||||
{% if not disable_feedback %} | |||||
<div class="blog-feedback"> | |||||
{% include 'templates/includes/feedback/feedback.html' %} | |||||
</div> | |||||
{% endif %} | |||||
</div> | </div> | ||||
<script> | <script> | ||||
@@ -10,6 +10,7 @@ | |||||
"column_break", | "column_break", | ||||
"enable_social_sharing", | "enable_social_sharing", | ||||
"show_cta_in_blog", | "show_cta_in_blog", | ||||
"allow_guest_to_comment", | |||||
"cta_section", | "cta_section", | ||||
"title", | "title", | ||||
"subtitle", | "subtitle", | ||||
@@ -17,7 +18,9 @@ | |||||
"cta_label", | "cta_label", | ||||
"cta_url", | "cta_url", | ||||
"section_break_12", | "section_break_12", | ||||
"feedback_limit" | |||||
"feedback_limit", | |||||
"column_break_14", | |||||
"comment_limit" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
@@ -86,18 +89,35 @@ | |||||
"fieldtype": "Section Break" | "fieldtype": "Section Break" | ||||
}, | }, | ||||
{ | { | ||||
"default": "1", | |||||
"default": "5", | |||||
"description": "Feedback limit per hour", | "description": "Feedback limit per hour", | ||||
"fieldname": "feedback_limit", | "fieldname": "feedback_limit", | ||||
"fieldtype": "Int", | "fieldtype": "Int", | ||||
"label": "Feedback limit" | "label": "Feedback limit" | ||||
}, | |||||
{ | |||||
"default": "5", | |||||
"description": "Comment limit per hour", | |||||
"fieldname": "comment_limit", | |||||
"fieldtype": "Int", | |||||
"label": "Comment limit" | |||||
}, | |||||
{ | |||||
"fieldname": "column_break_14", | |||||
"fieldtype": "Column Break" | |||||
}, | |||||
{ | |||||
"default": "1", | |||||
"fieldname": "allow_guest_to_comment", | |||||
"fieldtype": "Check", | |||||
"label": "Allow guest to comment" | |||||
} | } | ||||
], | ], | ||||
"icon": "fa fa-cog", | "icon": "fa fa-cog", | ||||
"idx": 1, | "idx": 1, | ||||
"issingle": 1, | "issingle": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-09-30 13:00:18.887103", | |||||
"modified": "2021-10-28 20:44:44.143193", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Blog Settings", | "name": "Blog Settings", | ||||
@@ -15,4 +15,7 @@ class BlogSettings(Document): | |||||
clear_cache("writers") | clear_cache("writers") | ||||
def get_feedback_limit(): | def get_feedback_limit(): | ||||
return frappe.db.get_single_value("Blog Settings", "feedback_limit") or 0 | |||||
return frappe.db.get_single_value("Blog Settings", "feedback_limit") or 5 | |||||
def get_comment_limit(): | |||||
return frappe.db.get_single_value("Blog Settings", "comment_limit") or 5 |
@@ -43,5 +43,12 @@ body { | |||||
--text-color: #{$body-text-color}; | --text-color: #{$body-text-color}; | ||||
--text-light: #{$body-text-color}; | --text-light: #{$body-text-color}; | ||||
{% endif -%} | {% endif -%} | ||||
{% if not button_rounded_corners %} | |||||
--border-radius-sm: 0px; | |||||
--border-radius: 0px; | |||||
--border-radius-md: 0px; | |||||
--border-radius-lg: 0px; | |||||
--border-radius-full: 0px; | |||||
{% endif -%} | |||||
} | } | ||||
@@ -1,5 +1,5 @@ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html data-theme="{{ desk_theme.lower() }}" dir={{ layout_direction }} lang="{{ lang }}"> | |||||
<html data-theme-mode="{{ desk_theme.lower() }}" data-theme="{{ desk_theme.lower() }}" dir={{ layout_direction }} lang="{{ lang }}"> | |||||
<head> | <head> | ||||
<!-- Chrome, Firefox OS and Opera --> | <!-- Chrome, Firefox OS and Opera --> | ||||
<meta name="theme-color" content="#0089FF"> | <meta name="theme-color" content="#0089FF"> | ||||