@@ -137,7 +137,7 @@ jobs: | |||||
- name: UI Tests | - name: UI Tests | ||||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | if: ${{ steps.check-build.outputs.build == 'strawberry' }} | ||||
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --with-coverage --headless --parallel --ci-build-id $GITHUB_RUN_ID | |||||
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --with-coverage --headless --parallel --ci-build-id $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT | |||||
env: | env: | ||||
CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb | CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb | ||||
@@ -21,7 +21,6 @@ context('Control Barcode', () => { | |||||
get_dialog_with_barcode().as('dialog'); | get_dialog_with_barcode().as('dialog'); | ||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | ||||
.focus() | |||||
.type('123456789') | .type('123456789') | ||||
.blur(); | .blur(); | ||||
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]') | cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]') | ||||
@@ -38,7 +37,6 @@ context('Control Barcode', () => { | |||||
get_dialog_with_barcode().as('dialog'); | get_dialog_with_barcode().as('dialog'); | ||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | ||||
.focus() | |||||
.type('123456789') | .type('123456789') | ||||
.blur(); | .blur(); | ||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') | ||||
@@ -19,18 +19,18 @@ context('Control Icon', () => { | |||||
get_dialog_with_icon().as('dialog'); | get_dialog_with_icon().as('dialog'); | ||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click(); | cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click(); | ||||
cy.get('.icon-picker .icon-wrapper[id=active]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'active'); | |||||
cy.get('.icon-picker .icon-wrapper[id=heart-active]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart-active'); | |||||
cy.get('@dialog').then(dialog => { | cy.get('@dialog').then(dialog => { | ||||
let value = dialog.get_value('icon'); | let value = dialog.get_value('icon'); | ||||
expect(value).to.equal('active'); | |||||
expect(value).to.equal('heart-active'); | |||||
}); | }); | ||||
cy.get('.icon-picker .icon-wrapper[id=resting]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'resting'); | |||||
cy.get('.icon-picker .icon-wrapper[id=heart]').first().click(); | |||||
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart'); | |||||
cy.get('@dialog').then(dialog => { | cy.get('@dialog').then(dialog => { | ||||
let value = dialog.get_value('icon'); | let value = dialog.get_value('icon'); | ||||
expect(value).to.equal('resting'); | |||||
expect(value).to.equal('heart'); | |||||
}); | }); | ||||
}); | }); | ||||
@@ -103,6 +103,7 @@ context('Control Date, Time and DateTime', () => { | |||||
input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata) | input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata) | ||||
} | } | ||||
]; | ]; | ||||
datetime_formats.forEach(d => { | datetime_formats.forEach(d => { | ||||
it(`test datetime format ${d.date_format} ${d.time_format}`, () => { | it(`test datetime format ${d.date_format} ${d.time_format}`, () => { | ||||
cy.set_value('System Settings', 'System Settings', { | cy.set_value('System Settings', 'System Settings', { | ||||
@@ -77,11 +77,11 @@ context('MultiSelectDialog', () => { | |||||
it('tests more button', () => { | it('tests more button', () => { | ||||
cy.get_open_dialog() | cy.get_open_dialog() | ||||
.get(`.frappe-control[data-fieldname="more_btn"]`) | |||||
.get(`.frappe-control[data-fieldname="more_child_btn"]`) | |||||
.should('exist') | .should('exist') | ||||
.as('more-btn'); | .as('more-btn'); | ||||
cy.get_open_dialog().get('.list-item-container').should(($rows) => { | |||||
cy.get_open_dialog().get('.datatable .dt-scrollable .dt-row').should(($rows) => { | |||||
expect($rows).to.have.length(20); | expect($rows).to.have.length(20); | ||||
}); | }); | ||||
@@ -89,7 +89,7 @@ context('MultiSelectDialog', () => { | |||||
cy.get('@more-btn').find('button').click({force: true}); | cy.get('@more-btn').find('button').click({force: true}); | ||||
cy.wait('@get-more-records'); | cy.wait('@get-more-records'); | ||||
cy.get_open_dialog().get('.list-item-container').should(($rows) => { | |||||
cy.get_open_dialog().get('.datatable .dt-scrollable .dt-row').should(($rows) => { | |||||
if ($rows.length <= 20) { | if ($rows.length <= 20) { | ||||
throw new Error("More button doesn't work"); | throw new Error("More button doesn't work"); | ||||
} | } | ||||
@@ -7,8 +7,6 @@ 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', | ||||
@@ -16,6 +14,8 @@ 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 | ||||
@@ -14,7 +14,7 @@ context('Timeline Email', () => { | |||||
cy.wait(700); | cy.wait(700); | ||||
}); | }); | ||||
it('Adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { | |||||
it('Adding email and verifying timeline content for email attachment', () => { | |||||
cy.visit('/app/todo'); | cy.visit('/app/todo'); | ||||
cy.get('.list-row > .level-left > .list-subject').eq(0).click(); | cy.get('.list-row > .level-left > .list-subject').eq(0).click(); | ||||
@@ -43,7 +43,9 @@ context('Timeline Email', () => { | |||||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click(); | cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click(); | ||||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click(); | cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click(); | ||||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click(); | cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click(); | ||||
}); | |||||
it('Deleting attachment and ToDo', () => { | |||||
cy.visit('/app/todo'); | cy.visit('/app/todo'); | ||||
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); | cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); | ||||
@@ -33,44 +33,39 @@ context('Workspace 2.0', () => { | |||||
}); | }); | ||||
it('Add New Block', () => { | it('Add New Block', () => { | ||||
cy.get('.codex-editor__redactor .ce-block'); | |||||
cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); | |||||
cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Heading').click(); | |||||
cy.get('.ce-block').click().type('{enter}'); | |||||
cy.get('.block-list-container .block-list-item').contains('Heading').click(); | |||||
cy.get(":focus").type('Header'); | cy.get(":focus").type('Header'); | ||||
cy.get(".ce-block:last").find('.ce-header').should('exist'); | cy.get(".ce-block:last").find('.ce-header').should('exist'); | ||||
cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); | |||||
cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Text').click(); | |||||
cy.get('.ce-block:last').click().type('{enter}'); | |||||
cy.get('.block-list-container .block-list-item').contains('Text').click(); | |||||
cy.get(":focus").type('Paragraph text'); | cy.get(":focus").type('Paragraph text'); | ||||
cy.get(".ce-block:last").find('.ce-paragraph').should('exist'); | cy.get(".ce-block:last").find('.ce-paragraph').should('exist'); | ||||
}); | }); | ||||
it('Delete A Block', () => { | it('Delete A Block', () => { | ||||
cy.get(".ce-block:last").find('.delete-paragraph').click(); | |||||
cy.get(":focus").click(); | |||||
cy.get('.paragraph-control .setting-btn').click(); | |||||
cy.get('.paragraph-control .dropdown-item').contains('Delete').click(); | |||||
cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist'); | cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist'); | ||||
}); | }); | ||||
it('Shrink and Expand A Block', () => { | it('Shrink and Expand A Block', () => { | ||||
cy.get(".ce-block:last").find('.tune-btn').click(); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-11'); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-10'); | |||||
cy.get('.ce-settings--opened .ce-shrink-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-9'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-10'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-11'); | |||||
cy.get('.ce-settings--opened .ce-expand-button').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-12'); | |||||
}); | |||||
it('Change Header Text Size', () => { | |||||
cy.get('.ce-settings--opened .cdx-settings-button[data-level="3"]').click(); | |||||
cy.get(".ce-block:last").find('.widget-head h3').should('exist'); | |||||
cy.get('.ce-settings--opened .cdx-settings-button[data-level="4"]').click(); | |||||
cy.get(".ce-block:last").find('.widget-head h4').should('exist'); | |||||
cy.get(":focus").click(); | |||||
cy.get('.ce-block:last .setting-btn').click(); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Shrink').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-11'); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Shrink').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-10'); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Shrink').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-9'); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Expand').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-10'); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Expand').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-11'); | |||||
cy.get('.ce-block:last .dropdown-item').contains('Expand').click(); | |||||
cy.get(".ce-block:last").should('have.class', 'col-xs-12'); | |||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | ||||
}); | }); | ||||
@@ -79,7 +74,10 @@ context('Workspace 2.0', () => { | |||||
cy.get('.codex-editor__redactor .ce-block'); | cy.get('.codex-editor__redactor .ce-block'); | ||||
cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); | cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); | ||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').find('.sidebar-item-control .delete-page').click(); | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]') | |||||
.find('.sidebar-item-control .setting-btn').click(); | |||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]') | |||||
.find('.dropdown-item[title="Delete Workspace"]').click({force: true}); | |||||
cy.wait(300); | cy.wait(300); | ||||
cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click(); | cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click(); | ||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); | ||||
@@ -446,7 +446,7 @@ def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None, | |||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide, as_list=as_list) | msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide, as_list=as_list) | ||||
def emit_js(js, user=False, **kwargs): | def emit_js(js, user=False, **kwargs): | ||||
if user == False: | |||||
if user is False: | |||||
user = session.user | user = session.user | ||||
publish_realtime('eval_js', js, user=user, **kwargs) | publish_realtime('eval_js', js, user=user, **kwargs) | ||||
@@ -111,7 +111,8 @@ class LoginManager: | |||||
self.user_type = None | self.user_type = None | ||||
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | ||||
if self.login()==False: return | |||||
if self.login() is False: | |||||
return | |||||
self.resume = False | self.resume = False | ||||
# run login triggers | # run login triggers | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"ToDo\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"File\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Assignment Rule\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Auto Repeat\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Email\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Automation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Event Streaming\", \"col\": 4}}]", | |||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"ToDo\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"File\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Assignment Rule\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Auto Repeat\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Automation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Event Streaming\",\"col\":4}}]", | |||||
"creation": "2020-03-02 14:53:24.980279", | "creation": "2020-03-02 14:53:24.980279", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -208,7 +208,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-08-05 12:16:02.839181", | |||||
"modified": "2022-01-13 17:48:48.456763", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Automation", | "module": "Automation", | ||||
"name": "Tools", | "name": "Tools", | ||||
@@ -217,7 +217,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 26, | |||||
"sequence_id": 26.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"label": "ToDo", | "label": "ToDo", | ||||
@@ -107,8 +107,8 @@ def load_conf_settings(bootinfo): | |||||
if key in conf: bootinfo[key] = conf.get(key) | if key in conf: bootinfo[key] = conf.get(key) | ||||
def load_desktop_data(bootinfo): | def load_desktop_data(bootinfo): | ||||
from frappe.desk.desktop import get_wspace_sidebar_items | |||||
bootinfo.allowed_workspaces = get_wspace_sidebar_items().get('pages') | |||||
from frappe.desk.desktop import get_workspace_sidebar_items | |||||
bootinfo.allowed_workspaces = get_workspace_sidebar_items().get('pages') | |||||
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() | bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() | ||||
bootinfo.dashboards = frappe.get_all("Dashboard") | bootinfo.dashboards = frappe.get_all("Dashboard") | ||||
@@ -148,7 +148,7 @@ def build_table_count_cache(): | |||||
data = ( | data = ( | ||||
frappe.qb.from_(information_schema.tables).select(table_name, table_rows) | frappe.qb.from_(information_schema.tables).select(table_name, table_rows) | ||||
).run(as_dict=True) | ).run(as_dict=True) | ||||
counts = {d.get('name').lstrip('tab'): d.get('count', None) for d in data} | |||||
counts = {d.get('name').replace('tab', '', 1): d.get('count', None) for d in data} | |||||
_cache.set_value("information_schema:counts", counts) | _cache.set_value("information_schema:counts", counts) | ||||
return counts | return counts | ||||
@@ -952,7 +952,7 @@ def trim_database(context, dry_run, format, no_backup): | |||||
doctype_tables = frappe.get_all("DocType", pluck="name") | doctype_tables = frappe.get_all("DocType", pluck="name") | ||||
for x in database_tables: | for x in database_tables: | ||||
doctype = x.lstrip("tab") | |||||
doctype = x.replace("tab", "", 1) | |||||
if not (doctype in doctype_tables or x.startswith("__") or x in STANDARD_TABLES): | if not (doctype in doctype_tables or x.startswith("__") or x in STANDARD_TABLES): | ||||
TABLES_TO_DROP.append(x) | TABLES_TO_DROP.append(x) | ||||
@@ -966,7 +966,7 @@ def trim_database(context, dry_run, format, no_backup): | |||||
odb = scheduled_backup( | odb = scheduled_backup( | ||||
ignore_conf=False, | ignore_conf=False, | ||||
include_doctypes=",".join(x.lstrip("tab") for x in TABLES_TO_DROP), | |||||
include_doctypes=",".join(x.replace("tab", "", 1) for x in TABLES_TO_DROP), | |||||
ignore_files=True, | ignore_files=True, | ||||
force=True, | force=True, | ||||
) | ) | ||||
@@ -1,154 +1,55 @@ | |||||
{ | { | ||||
"allow_copy": 0, | |||||
"allow_guest_to_view": 0, | |||||
"allow_import": 0, | |||||
"allow_rename": 0, | |||||
"autoname": "field:gateway", | |||||
"beta": 0, | |||||
"creation": "2015-12-15 22:26:45.221162", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"document_type": "", | |||||
"editable_grid": 1, | |||||
"actions": [], | |||||
"autoname": "field:gateway", | |||||
"creation": "2022-01-24 21:09:47.229371", | |||||
"doctype": "DocType", | |||||
"editable_grid": 1, | |||||
"engine": "InnoDB", | |||||
"field_order": [ | |||||
"gateway", | |||||
"gateway_settings", | |||||
"gateway_controller" | |||||
], | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "gateway", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_global_search": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | |||||
"label": "Gateway", | |||||
"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": 1, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
"fieldname": "gateway", | |||||
"fieldtype": "Data", | |||||
"in_list_view": 1, | |||||
"label": "Gateway", | |||||
"reqd": 1, | |||||
"unique": 1 | |||||
}, | |||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "gateway_settings", | |||||
"fieldtype": "Link", | |||||
"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": "Gateway Settings", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "DocType", | |||||
"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, | |||||
"unique": 0 | |||||
}, | |||||
"fieldname": "gateway_settings", | |||||
"fieldtype": "Link", | |||||
"label": "Gateway Settings", | |||||
"options": "DocType" | |||||
}, | |||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "gateway_controller", | |||||
"fieldtype": "Dynamic Link", | |||||
"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": "Gateway Controller", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "gateway_settings", | |||||
"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, | |||||
"unique": 0 | |||||
"fieldname": "gateway_controller", | |||||
"fieldtype": "Dynamic Link", | |||||
"label": "Gateway Controller", | |||||
"options": "gateway_settings" | |||||
} | } | ||||
], | |||||
"has_web_view": 0, | |||||
"hide_heading": 0, | |||||
"hide_toolbar": 0, | |||||
"idx": 0, | |||||
"image_view": 0, | |||||
"in_create": 1, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"modified": "2018-02-05 14:24:33.526645", | |||||
"modified_by": "Administrator", | |||||
"module": "Core", | |||||
"name": "Payment Gateway", | |||||
"name_case": "", | |||||
"owner": "Administrator", | |||||
], | |||||
"links": [], | |||||
"modified": "2022-01-24 21:17:03.864719", | |||||
"modified_by": "Administrator", | |||||
"module": "Core", | |||||
"name": "Payment Gateway", | |||||
"naming_rule": "By fieldname", | |||||
"owner": "Administrator", | |||||
"permissions": [ | "permissions": [ | ||||
{ | { | ||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 0, | |||||
"delete": 0, | |||||
"email": 0, | |||||
"export": 0, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 0, | |||||
"read": 1, | |||||
"report": 0, | |||||
"role": "System Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 0, | |||||
"submit": 0, | |||||
"write": 0 | |||||
"create": 1, | |||||
"delete": 1, | |||||
"read": 1, | |||||
"role": "System Manager", | |||||
"write": 1 | |||||
} | } | ||||
], | |||||
"quick_entry": 1, | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"show_name_in_global_search": 0, | |||||
"sort_field": "modified", | |||||
"sort_order": "DESC", | |||||
"track_changes": 0, | |||||
"track_seen": 0 | |||||
], | |||||
"quick_entry": 1, | |||||
"sort_field": "modified", | |||||
"sort_order": "DESC", | |||||
"states": [] | |||||
} | } |
@@ -121,7 +121,7 @@ class UserType(Document): | |||||
for child_table in doc.get_table_fields(): | for child_table in doc.get_table_fields(): | ||||
child_doc = frappe.get_meta(child_table.options) | child_doc = frappe.get_meta(child_table.options) | ||||
if not child_doc.istable: | |||||
if child_doc: | |||||
self.prepare_select_perm_doctypes(child_doc, user_doctypes, select_doctypes) | self.prepare_select_perm_doctypes(child_doc, user_doctypes, select_doctypes) | ||||
if select_doctypes: | if select_doctypes: | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]", | |||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Elements</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]", | |||||
"creation": "2021-01-02 10:51:16.579957", | "creation": "2021-01-02 10:51:16.579957", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -222,7 +222,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-09-05 21:14:52.384816", | |||||
"modified": "2022-01-13 17:26:02.736366", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Build", | "name": "Build", | ||||
@@ -231,7 +231,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 5, | |||||
"sequence_id": 5.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"doc_view": "", | "doc_view": "", | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\":\"header\",\"data\": {\"text\":\"Settings\",\"level\": 4,\"col\": 12}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"System Settings\",\"col\": 4}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"Print Settings\",\"col\": 4}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"Website Settings\",\"col\": 4}}, {\"type\":\"spacer\",\"data\": {\"col\": 12}}, {\"type\":\"header\",\"data\": {\"text\":\"Reports & Masters\",\"level\": 4,\"col\": 12}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Data\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Email / Notifications\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Website\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Core\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Printing\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Workflow\",\"col\": 4}}]", | |||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", | |||||
"creation": "2020-03-02 15:09:40.527211", | "creation": "2020-03-02 15:09:40.527211", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -367,7 +367,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-08-05 12:16:03.456174", | |||||
"modified": "2022-01-13 17:49:59.586909", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Settings", | "name": "Settings", | ||||
@@ -376,7 +376,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 29, | |||||
"sequence_id": 29.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"icon": "setting", | "icon": "setting", | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Role\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Permission Manager\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Profile\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Type\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Users\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Logs\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Permissions\", \"col\": 4}}]", | |||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Permission Manager\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Profile\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Type\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Users\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Logs\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Permissions\",\"col\":4}}]", | |||||
"creation": "2020-03-02 15:12:16.754449", | "creation": "2020-03-02 15:12:16.754449", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -145,7 +145,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-08-05 12:16:03.010205", | |||||
"modified": "2022-01-13 17:49:08.912772", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Users", | "name": "Users", | ||||
@@ -154,7 +154,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 27, | |||||
"sequence_id": 27.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"label": "User", | "label": "User", | ||||
@@ -107,20 +107,26 @@ class CustomizeForm(Document): | |||||
def set_name_translation(self): | def set_name_translation(self): | ||||
'''Create, update custom translation for this doctype''' | '''Create, update custom translation for this doctype''' | ||||
current = self.get_name_translation() | current = self.get_name_translation() | ||||
if current: | |||||
if self.label and current.translated_text != self.label: | |||||
frappe.db.set_value('Translation', current.name, 'translated_text', self.label) | |||||
frappe.translate.clear_cache() | |||||
else: | |||||
if not self.label: | |||||
if current: | |||||
# clear translation | # clear translation | ||||
frappe.delete_doc('Translation', current.name) | frappe.delete_doc('Translation', current.name) | ||||
return | |||||
else: | |||||
if self.label: | |||||
frappe.get_doc(dict(doctype='Translation', | |||||
source_text=self.doc_type, | |||||
translated_text=self.label, | |||||
language_code=frappe.local.lang or 'en')).insert() | |||||
if not current: | |||||
frappe.get_doc( | |||||
{ | |||||
"doctype": 'Translation', | |||||
"source_text": self.doc_type, | |||||
"translated_text": self.label, | |||||
"language_code": frappe.local.lang or 'en' | |||||
} | |||||
).insert() | |||||
return | |||||
if self.label != current.translated_text: | |||||
frappe.db.set_value('Translation', current.name, 'translated_text', self.label) | |||||
frappe.translate.clear_cache() | |||||
def clear_existing_doc(self): | def clear_existing_doc(self): | ||||
doc_type = self.doc_type | doc_type = self.doc_type | ||||
@@ -304,3 +304,25 @@ class TestCustomizeForm(unittest.TestCase): | |||||
action = [d for d in event.actions if d.label=='Test Action'] | action = [d for d in event.actions if d.label=='Test Action'] | ||||
self.assertEqual(len(action), 0) | self.assertEqual(len(action), 0) | ||||
def test_custom_label(self): | |||||
d = self.get_customize_form("Event") | |||||
# add label | |||||
d.label = "Test Rename" | |||||
d.run_method("save_customization") | |||||
self.assertEqual(d.label, "Test Rename") | |||||
# change label | |||||
d.label = "Test Rename 2" | |||||
d.run_method("save_customization") | |||||
self.assertEqual(d.label, "Test Rename 2") | |||||
# saving again to make sure existing label persists | |||||
d.run_method("save_customization") | |||||
self.assertEqual(d.label, "Test Rename 2") | |||||
# clear label | |||||
d.label = "" | |||||
d.run_method("save_customization") | |||||
self.assertEqual(d.label, "") |
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", | |||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]", | |||||
"creation": "2020-03-02 15:15:03.839594", | "creation": "2020-03-02 15:15:03.839594", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -123,7 +123,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-11-24 16:20:03.500885", | |||||
"modified": "2022-01-13 17:28:08.345794", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Custom", | "module": "Custom", | ||||
"name": "Customization", | "name": "Customization", | ||||
@@ -132,7 +132,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 8, | |||||
"sequence_id": 8.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"label": "Customize Form", | "label": "Customize Form", | ||||
@@ -245,9 +245,16 @@ class MariaDBDatabase(Database): | |||||
column_name as 'name', | column_name as 'name', | ||||
column_type as 'type', | column_type as 'type', | ||||
column_default as 'default', | column_default as 'default', | ||||
column_key = 'MUL' as 'index', | |||||
COALESCE( | |||||
(select 1 | |||||
from information_schema.statistics | |||||
where table_name="{table_name}" | |||||
and column_name=columns.column_name | |||||
and NON_UNIQUE=1 | |||||
limit 1 | |||||
), 0) as 'index', | |||||
column_key = 'UNI' as 'unique' | column_key = 'UNI' as 'unique' | ||||
from information_schema.columns | |||||
from information_schema.columns as columns | |||||
where table_name = '{table_name}' '''.format(table_name=table_name), as_dict=1) | where table_name = '{table_name}' '''.format(table_name=table_name), as_dict=1) | ||||
def has_index(self, table_name, index_name): | def has_index(self, table_name, index_name): | ||||
@@ -58,18 +58,34 @@ class MariaDBTable(DBTable): | |||||
modify_column_query.append("MODIFY `{}` {}".format(col.fieldname, col.get_definition())) | modify_column_query.append("MODIFY `{}` {}".format(col.fieldname, col.get_definition())) | ||||
for col in self.add_index: | for col in self.add_index: | ||||
# if index key not exists | |||||
if not frappe.db.sql("SHOW INDEX FROM `%s` WHERE key_name = %s" % | |||||
(self.table_name, '%s'), col.fieldname): | |||||
add_index_query.append("ADD INDEX `{}`(`{}`)".format(col.fieldname, col.fieldname)) | |||||
# if index key does not exists | |||||
if not frappe.db.has_index(self.table_name, col.fieldname + '_index'): | |||||
add_index_query.append("ADD INDEX `{}_index`(`{}`)".format(col.fieldname, col.fieldname)) | |||||
for col in self.drop_index: | |||||
for col in self.drop_index + self.drop_unique: | |||||
if col.fieldname != 'name': # primary key | if col.fieldname != 'name': # primary key | ||||
current_column = self.current_columns.get(col.fieldname.lower()) | |||||
unique_constraint_changed = current_column.unique != col.unique | |||||
if unique_constraint_changed and not col.unique: | |||||
# nosemgrep | |||||
unique_index_record = frappe.db.sql(""" | |||||
SHOW INDEX FROM `{0}` | |||||
WHERE Key_name=%s | |||||
AND Non_unique=0 | |||||
""".format(self.table_name), (col.fieldname), as_dict=1) | |||||
if unique_index_record: | |||||
drop_index_query.append("DROP INDEX `{}`".format(unique_index_record[0].Key_name)) | |||||
index_constraint_changed = current_column.index != col.set_index | |||||
# if index key exists | # if index key exists | ||||
if frappe.db.sql("""SHOW INDEX FROM `{0}` | |||||
WHERE key_name=%s | |||||
AND Non_unique=%s""".format(self.table_name), (col.fieldname, col.unique)): | |||||
drop_index_query.append("drop index `{}`".format(col.fieldname)) | |||||
if index_constraint_changed and not col.set_index: | |||||
# nosemgrep | |||||
index_record = frappe.db.sql(""" | |||||
SHOW INDEX FROM `{0}` | |||||
WHERE Key_name=%s | |||||
AND Non_unique=1 | |||||
""".format(self.table_name), (col.fieldname + '_index'), as_dict=1) | |||||
if index_record: | |||||
drop_index_query.append("DROP INDEX `{}`".format(index_record[0].Key_name)) | |||||
try: | try: | ||||
for query_parts in [add_column_query, modify_column_query, add_index_query, drop_index_query]: | for query_parts in [add_column_query, modify_column_query, add_index_query, drop_index_query]: | ||||
@@ -77,11 +77,11 @@ class PostgresDatabase(Database): | |||||
"""Escape quotes and percent in given string.""" | """Escape quotes and percent in given string.""" | ||||
if isinstance(s, bytes): | if isinstance(s, bytes): | ||||
s = s.decode('utf-8') | s = s.decode('utf-8') | ||||
# MariaDB's driver treats None as an empty string | # MariaDB's driver treats None as an empty string | ||||
# So Postgres should do the same | # So Postgres should do the same | ||||
if s is None: | |||||
if s is None: | |||||
s = '' | s = '' | ||||
if percent: | if percent: | ||||
@@ -308,18 +308,20 @@ class PostgresDatabase(Database): | |||||
WHEN 'timestamp without time zone' THEN 'timestamp' | WHEN 'timestamp without time zone' THEN 'timestamp' | ||||
ELSE a.data_type | ELSE a.data_type | ||||
END AS type, | END AS type, | ||||
COUNT(b.indexdef) AS Index, | |||||
BOOL_OR(b.index) AS index, | |||||
SPLIT_PART(COALESCE(a.column_default, NULL), '::', 1) AS default, | SPLIT_PART(COALESCE(a.column_default, NULL), '::', 1) AS default, | ||||
BOOL_OR(b.unique) AS unique | BOOL_OR(b.unique) AS unique | ||||
FROM information_schema.columns a | FROM information_schema.columns a | ||||
LEFT JOIN | LEFT JOIN | ||||
(SELECT indexdef, tablename, indexdef LIKE '%UNIQUE INDEX%' AS unique | |||||
(SELECT indexdef, tablename, | |||||
indexdef LIKE '%UNIQUE INDEX%' AS unique, | |||||
indexdef NOT LIKE '%UNIQUE INDEX%' AS index | |||||
FROM pg_indexes | FROM pg_indexes | ||||
WHERE tablename='{table_name}') b | WHERE tablename='{table_name}') b | ||||
ON SUBSTRING(b.indexdef, '\(.*\)') LIKE CONCAT('%', a.column_name, '%') | |||||
ON SUBSTRING(b.indexdef, '(.*)') LIKE CONCAT('%', a.column_name, '%') | |||||
WHERE a.table_name = '{table_name}' | WHERE a.table_name = '{table_name}' | ||||
GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length;''' | |||||
.format(table_name=table_name), as_dict=1) | |||||
GROUP BY a.column_name, a.data_type, a.column_default, a.character_maximum_length; | |||||
'''.format(table_name=table_name), as_dict=1) | |||||
def get_database_list(self, target): | def get_database_list(self, target): | ||||
return [d[0] for d in self.sql("SELECT datname FROM pg_database;")] | return [d[0] for d in self.sql("SELECT datname FROM pg_database;")] | ||||
@@ -11,8 +11,6 @@ class PostgresTable(DBTable): | |||||
column_defs = self.get_column_definitions() | column_defs = self.get_column_definitions() | ||||
if column_defs: add_text += ',\n'.join(column_defs) | if column_defs: add_text += ',\n'.join(column_defs) | ||||
# index | |||||
# index_defs = self.get_index_definitions() | |||||
# TODO: set docstatus length | # TODO: set docstatus length | ||||
# create table | # create table | ||||
frappe.db.sql("""create table `%s` ( | frappe.db.sql("""create table `%s` ( | ||||
@@ -28,8 +26,25 @@ class PostgresTable(DBTable): | |||||
idx bigint not null default '0', | idx bigint not null default '0', | ||||
%s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text)) | %s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text)) | ||||
self.create_indexes() | |||||
frappe.db.commit() | frappe.db.commit() | ||||
def create_indexes(self): | |||||
create_index_query = "" | |||||
for key, col in self.columns.items(): | |||||
if (col.set_index | |||||
and col.fieldtype in frappe.db.type_map | |||||
and frappe.db.type_map.get(col.fieldtype)[0] | |||||
not in ('text', 'longtext')): | |||||
create_index_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( | |||||
index_name=col.fieldname, | |||||
table_name=self.table_name, | |||||
field=col.fieldname | |||||
) | |||||
if create_index_query: | |||||
# nosemgrep | |||||
frappe.db.sql(create_index_query) | |||||
def alter(self): | def alter(self): | ||||
for col in self.columns.values(): | for col in self.columns.values(): | ||||
col.build_for_alter_table(self.current_columns.get(col.fieldname.lower())) | col.build_for_alter_table(self.current_columns.get(col.fieldname.lower())) | ||||
@@ -52,8 +67,8 @@ class PostgresTable(DBTable): | |||||
query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format( | query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format( | ||||
col.fieldname, | col.fieldname, | ||||
get_definition(col.fieldtype, precision=col.precision, length=col.length), | get_definition(col.fieldtype, precision=col.precision, length=col.length), | ||||
using_clause) | |||||
) | |||||
using_clause | |||||
)) | |||||
for col in self.set_default: | for col in self.set_default: | ||||
if col.fieldname=="name": | if col.fieldname=="name": | ||||
@@ -73,37 +88,54 @@ class PostgresTable(DBTable): | |||||
query.append("ALTER COLUMN `{}` SET DEFAULT {}".format(col.fieldname, col_default)) | query.append("ALTER COLUMN `{}` SET DEFAULT {}".format(col.fieldname, col_default)) | ||||
create_index_query = "" | |||||
create_contraint_query = "" | |||||
for col in self.add_index: | for col in self.add_index: | ||||
# if index key not exists | # if index key not exists | ||||
create_index_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( | |||||
create_contraint_query += 'CREATE INDEX IF NOT EXISTS "{index_name}" ON `{table_name}`(`{field}`);'.format( | |||||
index_name=col.fieldname, | index_name=col.fieldname, | ||||
table_name=self.table_name, | table_name=self.table_name, | ||||
field=col.fieldname) | field=col.fieldname) | ||||
drop_index_query = "" | |||||
for col in self.add_unique: | |||||
# if index key not exists | |||||
create_contraint_query += 'CREATE UNIQUE INDEX IF NOT EXISTS "unique_{index_name}" ON `{table_name}`(`{field}`);'.format( | |||||
index_name=col.fieldname, | |||||
table_name=self.table_name, | |||||
field=col.fieldname | |||||
) | |||||
drop_contraint_query = "" | |||||
for col in self.drop_index: | for col in self.drop_index: | ||||
# primary key | # primary key | ||||
if col.fieldname != 'name': | if col.fieldname != 'name': | ||||
# if index key exists | # if index key exists | ||||
if not frappe.db.has_index(self.table_name, col.fieldname): | |||||
drop_index_query += 'DROP INDEX IF EXISTS "{}" ;'.format(col.fieldname) | |||||
drop_contraint_query += 'DROP INDEX IF EXISTS "{}" ;'.format(col.fieldname) | |||||
if query: | |||||
try: | |||||
for col in self.drop_unique: | |||||
# primary key | |||||
if col.fieldname != 'name': | |||||
# if index key exists | |||||
drop_contraint_query += 'DROP INDEX IF EXISTS "unique_{}" ;'.format(col.fieldname) | |||||
try: | |||||
if query: | |||||
final_alter_query = "ALTER TABLE `{}` {}".format(self.table_name, ", ".join(query)) | final_alter_query = "ALTER TABLE `{}` {}".format(self.table_name, ", ".join(query)) | ||||
if final_alter_query: frappe.db.sql(final_alter_query) | |||||
if create_index_query: frappe.db.sql(create_index_query) | |||||
if drop_index_query: frappe.db.sql(drop_index_query) | |||||
except Exception as e: | |||||
# sanitize | |||||
if frappe.db.is_duplicate_fieldname(e): | |||||
frappe.throw(str(e)) | |||||
elif frappe.db.is_duplicate_entry(e): | |||||
fieldname = str(e).split("'")[-2] | |||||
frappe.throw(_("""{0} field cannot be set as unique in {1}, | |||||
as there are non-unique existing values""".format( | |||||
fieldname, self.table_name))) | |||||
raise e | |||||
else: | |||||
raise e | |||||
# nosemgrep | |||||
frappe.db.sql(final_alter_query) | |||||
if create_contraint_query: | |||||
# nosemgrep | |||||
frappe.db.sql(create_contraint_query) | |||||
if drop_contraint_query: | |||||
# nosemgrep | |||||
frappe.db.sql(drop_contraint_query) | |||||
except Exception as e: | |||||
# sanitize | |||||
if frappe.db.is_duplicate_fieldname(e): | |||||
frappe.throw(str(e)) | |||||
elif frappe.db.is_duplicate_entry(e): | |||||
fieldname = str(e).split("'")[-2] | |||||
frappe.throw( | |||||
_("{0} field cannot be set as unique in {1}, as there are non-unique existing values") | |||||
.format(fieldname, self.table_name) | |||||
) | |||||
else: | |||||
raise e |
@@ -308,7 +308,7 @@ class Permission: | |||||
doctype = [doctype] | doctype = [doctype] | ||||
for dt in doctype: | for dt in doctype: | ||||
dt = re.sub("tab", "", dt) | |||||
dt = re.sub("^tab", "", dt) | |||||
if not frappe.has_permission( | if not frappe.has_permission( | ||||
dt, | dt, | ||||
"select", | "select", | ||||
@@ -21,6 +21,7 @@ class DBTable: | |||||
self.change_name = [] | self.change_name = [] | ||||
self.add_unique = [] | self.add_unique = [] | ||||
self.add_index = [] | self.add_index = [] | ||||
self.drop_unique = [] | |||||
self.drop_index = [] | self.drop_index = [] | ||||
self.set_default = [] | self.set_default = [] | ||||
@@ -219,8 +220,10 @@ class DbColumn: | |||||
self.table.change_type.append(self) | self.table.change_type.append(self) | ||||
# unique | # unique | ||||
if((self.unique and not current_def['unique']) and column_type not in ('text', 'longtext')): | |||||
if ((self.unique and not current_def['unique']) and column_type not in ('text', 'longtext')): | |||||
self.table.add_unique.append(self) | self.table.add_unique.append(self) | ||||
elif (current_def['unique'] and not self.unique) and column_type not in ('text', 'longtext'): | |||||
self.table.drop_unique.append(self) | |||||
# default | # default | ||||
if (self.default_changed(current_def) | if (self.default_changed(current_def) | ||||
@@ -230,9 +233,7 @@ class DbColumn: | |||||
self.table.set_default.append(self) | self.table.set_default.append(self) | ||||
# index should be applied or dropped irrespective of type change | # index should be applied or dropped irrespective of type change | ||||
if ((current_def['index'] and not self.set_index and not self.unique) | |||||
or (current_def['unique'] and not self.unique)): | |||||
# to drop unique you have to drop index | |||||
if (current_def['index'] and not self.set_index) and column_type not in ('text', 'longtext'): | |||||
self.table.drop_index.append(self) | self.table.drop_index.append(self) | ||||
elif (not current_def['index'] and self.set_index) and not (column_type in ('text', 'longtext')): | elif (not current_def['index'] and self.set_index) and not (column_type in ('text', 'longtext')): | ||||
@@ -56,31 +56,6 @@ class Workspace: | |||||
self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() | self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() | ||||
self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() | self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() | ||||
def is_page_allowed(self): | |||||
cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module) | |||||
shortcuts = self.doc.shortcuts | |||||
for section in cards: | |||||
links = loads(section.get('links')) if isinstance(section.get('links'), str) else section.get('links') | |||||
for item in links: | |||||
if self.is_item_allowed(item.get('link_to'), item.get('link_type')): | |||||
return True | |||||
def _in_active_domains(item): | |||||
if not item.restrict_to_domain: | |||||
return True | |||||
else: | |||||
return item.restrict_to_domain in frappe.get_active_domains() | |||||
for item in shortcuts: | |||||
if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item): | |||||
return True | |||||
if not shortcuts and not self.doc.links: | |||||
return True | |||||
return False | |||||
def is_permitted(self): | def is_permitted(self): | ||||
"""Returns true if Has Role is not set or the user is allowed.""" | """Returns true if Has Role is not set or the user is allowed.""" | ||||
from frappe.utils import has_common | from frappe.utils import has_common | ||||
@@ -346,20 +321,20 @@ def get_desktop_page(page): | |||||
dict: dictionary of cards, charts and shortcuts to be displayed on website | dict: dictionary of cards, charts and shortcuts to be displayed on website | ||||
""" | """ | ||||
try: | try: | ||||
wspace = Workspace(loads(page)) | |||||
wspace.build_workspace() | |||||
workspace = Workspace(loads(page)) | |||||
workspace.build_workspace() | |||||
return { | return { | ||||
'charts': wspace.charts, | |||||
'shortcuts': wspace.shortcuts, | |||||
'cards': wspace.cards, | |||||
'onboardings': wspace.onboardings | |||||
'charts': workspace.charts, | |||||
'shortcuts': workspace.shortcuts, | |||||
'cards': workspace.cards, | |||||
'onboardings': workspace.onboardings | |||||
} | } | ||||
except DoesNotExistError: | except DoesNotExistError: | ||||
frappe.log_error(frappe.get_traceback()) | frappe.log_error(frappe.get_traceback()) | ||||
return {} | return {} | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def get_wspace_sidebar_items(): | |||||
def get_workspace_sidebar_items(): | |||||
"""Get list of sidebar items for desk""" | """Get list of sidebar items for desk""" | ||||
has_access = "Workspace Manager" in frappe.get_roles() | has_access = "Workspace Manager" in frappe.get_roles() | ||||
@@ -385,8 +360,8 @@ def get_wspace_sidebar_items(): | |||||
# Filter Page based on Permission | # Filter Page based on Permission | ||||
for page in all_pages: | for page in all_pages: | ||||
try: | try: | ||||
wspace = Workspace(page, True) | |||||
if wspace.is_permitted() and wspace.is_page_allowed() or has_access: | |||||
workspace = Workspace(page, True) | |||||
if has_access or workspace.is_permitted(): | |||||
if page.public: | if page.public: | ||||
pages.append(page) | pages.append(page) | ||||
elif page.for_user == frappe.session.user: | elif page.for_user == frappe.session.user: | ||||
@@ -453,25 +428,24 @@ def get_custom_report_list(module): | |||||
return out | return out | ||||
def save_new_widget(doc, page, blocks, new_widgets): | def save_new_widget(doc, page, blocks, new_widgets): | ||||
if loads(new_widgets): | |||||
widgets = _dict(loads(new_widgets)) | |||||
widgets = _dict(loads(new_widgets)) | |||||
if widgets.chart: | |||||
doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts")) | |||||
if widgets.shortcut: | |||||
doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts")) | |||||
if widgets.card: | |||||
doc.build_links_table_from_card(widgets.card) | |||||
if widgets.chart: | |||||
doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts")) | |||||
if widgets.shortcut: | |||||
doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts")) | |||||
if widgets.card: | |||||
doc.build_links_table_from_card(widgets.card) | |||||
# remove duplicate and unwanted widgets | # remove duplicate and unwanted widgets | ||||
if widgets: | |||||
clean_up(doc, blocks) | |||||
clean_up(doc, blocks) | |||||
try: | try: | ||||
doc.save(ignore_permissions=True) | doc.save(ignore_permissions=True) | ||||
except (ValidationError, TypeError) as e: | except (ValidationError, TypeError) as e: | ||||
# Create a json string to log | # Create a json string to log | ||||
json_config = dumps(widgets, sort_keys=True, indent=4) | |||||
json_config = widgets and dumps(widgets, sort_keys=True, indent=4) | |||||
# Error log body | # Error log body | ||||
log = \ | log = \ | ||||
@@ -1,5 +1,6 @@ | |||||
{ | { | ||||
"actions": [], | "actions": [], | ||||
"allow_rename": 1, | |||||
"autoname": "field:label", | "autoname": "field:label", | ||||
"beta": 1, | "beta": 1, | ||||
"creation": "2020-01-23 13:45:59.470592", | "creation": "2020-01-23 13:45:59.470592", | ||||
@@ -141,7 +142,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "sequence_id", | "fieldname": "sequence_id", | ||||
"fieldtype": "Int", | |||||
"fieldtype": "Float", | |||||
"label": "Sequence Id" | "label": "Sequence Id" | ||||
}, | }, | ||||
{ | { | ||||
@@ -158,7 +159,7 @@ | |||||
], | ], | ||||
"in_create": 1, | "in_create": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-09-16 12:01:06.450622", | |||||
"modified": "2021-12-15 19:33:00.805265", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "Workspace", | "name": "Workspace", | ||||
@@ -6,6 +6,7 @@ import frappe | |||||
from frappe import _ | from frappe import _ | ||||
from frappe.modules.export_file import export_to_files | from frappe.modules.export_file import export_to_files | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.model.rename_doc import rename_doc | |||||
from frappe.desk.desktop import save_new_widget | from frappe.desk.desktop import save_new_widget | ||||
from frappe.desk.utils import validate_route_conflict | from frappe.desk.utils import validate_route_conflict | ||||
@@ -121,77 +122,157 @@ def get_report_type(report): | |||||
report_type = frappe.get_value("Report", report, "report_type") | report_type = frappe.get_value("Report", report, "report_type") | ||||
return report_type in ["Query Report", "Script Report", "Custom Report"] | return report_type in ["Query Report", "Script Report", "Custom Report"] | ||||
@frappe.whitelist() | |||||
def new_page(new_page): | |||||
if not loads(new_page): | |||||
return | |||||
page = loads(new_page) | |||||
if page.get("public") and not is_workspace_manager(): | |||||
return | |||||
doc = frappe.new_doc('Workspace') | |||||
doc.title = page.get('title') | |||||
doc.icon = page.get('icon') | |||||
doc.content = page.get('content') | |||||
doc.parent_page = page.get('parent_page') | |||||
doc.label = page.get('label') | |||||
doc.for_user = page.get('for_user') | |||||
doc.public = page.get('public') | |||||
doc.sequence_id = last_sequence_id(doc) + 1 | |||||
doc.save(ignore_permissions=True) | |||||
return doc | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def save_page(title, icon, parent, public, sb_public_items, sb_private_items, deleted_pages, new_widgets, blocks, save): | |||||
save = frappe.parse_json(save) | |||||
def save_page(title, public, new_widgets, blocks): | |||||
public = frappe.parse_json(public) | public = frappe.parse_json(public) | ||||
if save: | |||||
doc = frappe.new_doc('Workspace') | |||||
filters = { | |||||
'public': public, | |||||
'label': title | |||||
} | |||||
if not public: | |||||
filters = { | |||||
'for_user': frappe.session.user, | |||||
'label': title + "-" + frappe.session.user | |||||
} | |||||
pages = frappe.get_list("Workspace", filters=filters) | |||||
if pages: | |||||
doc = frappe.get_doc("Workspace", pages[0]) | |||||
doc.content = blocks | |||||
doc.save(ignore_permissions=True) | |||||
save_new_widget(doc, title, blocks, new_widgets) | |||||
return {"name": title, "public": public, "label": doc.label} | |||||
@frappe.whitelist() | |||||
def update_page(name, title, icon, parent, public): | |||||
public = frappe.parse_json(public) | |||||
doc = frappe.get_doc("Workspace", name) | |||||
filters = { | |||||
'parent_page': doc.title, | |||||
'public': doc.public | |||||
} | |||||
child_docs = frappe.get_list("Workspace", filters=filters) | |||||
if doc: | |||||
doc.title = title | doc.title = title | ||||
doc.icon = icon | doc.icon = icon | ||||
doc.content = blocks | |||||
doc.parent_page = parent | doc.parent_page = parent | ||||
if public: | |||||
doc.label = title | |||||
doc.public = 1 | |||||
else: | |||||
doc.label = title + "-" + frappe.session.user | |||||
doc.for_user = frappe.session.user | |||||
doc.save(ignore_permissions=True) | |||||
else: | |||||
if public: | |||||
filters = { | |||||
'public': public, | |||||
'label': title | |||||
} | |||||
else: | |||||
filters = { | |||||
'for_user': frappe.session.user, | |||||
'label': title + "-" + frappe.session.user | |||||
} | |||||
pages = frappe.get_list("Workspace", filters=filters) | |||||
if pages: | |||||
doc = frappe.get_doc("Workspace", pages[0]) | |||||
doc.content = blocks | |||||
if doc.public != public: | |||||
doc.sequence_id = frappe.db.count('Workspace', {'public':public}, cache=True) | |||||
doc.public = public | |||||
doc.for_user = '' if public else doc.for_user or frappe.session.user | |||||
doc.label = '{0}-{1}'.format(title, doc.for_user) if doc.for_user else title | |||||
doc.save(ignore_permissions=True) | doc.save(ignore_permissions=True) | ||||
if loads(new_widgets): | |||||
save_new_widget(doc, title, blocks, new_widgets) | |||||
if name != doc.label: | |||||
rename_doc("Workspace", name, doc.label, force=True, ignore_permissions=True) | |||||
if loads(sb_public_items) or loads(sb_private_items): | |||||
sort_pages(loads(sb_public_items), loads(sb_private_items)) | |||||
# update new name and public in child pages | |||||
if child_docs: | |||||
for child in child_docs: | |||||
child_doc = frappe.get_doc("Workspace", child.name) | |||||
child_doc.parent_page = doc.title | |||||
child_doc.public = doc.public | |||||
child_doc.save(ignore_permissions=True) | |||||
if loads(deleted_pages): | |||||
return delete_pages(loads(deleted_pages)) | |||||
return {"name": doc.title, "public": doc.public, "label": doc.label} | |||||
return {"name": title, "public": public, "label": doc.label} | |||||
@frappe.whitelist() | |||||
def duplicate_page(page_name, new_page): | |||||
if not loads(new_page): | |||||
return | |||||
new_page = loads(new_page) | |||||
if new_page.get("is_public") and not is_workspace_manager(): | |||||
return | |||||
old_doc = frappe.get_doc("Workspace", page_name) | |||||
doc = frappe.copy_doc(old_doc) | |||||
doc.title = new_page.get('title') | |||||
doc.icon = new_page.get('icon') | |||||
doc.parent_page = new_page.get('parent') or '' | |||||
doc.public = new_page.get('is_public') | |||||
doc.for_user = '' | |||||
doc.label = doc.title | |||||
if not doc.public: | |||||
doc.for_user = doc.for_user or frappe.session.user | |||||
doc.label = '{0}-{1}'.format(doc.title, doc.for_user) | |||||
doc.name = doc.label | |||||
if old_doc.public == doc.public: | |||||
doc.sequence_id += 0.1 | |||||
else: | |||||
doc.sequence_id = last_sequence_id(doc) + 1 | |||||
doc.insert(ignore_permissions=True) | |||||
def delete_pages(deleted_pages): | |||||
for page in deleted_pages: | |||||
if page.get("public") and not is_workspace_manager(): | |||||
return {"name": page.get("title"), "public": 1, "label": page.get("label")} | |||||
return doc | |||||
if frappe.db.exists("Workspace", page.get("name")): | |||||
frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True) | |||||
@frappe.whitelist() | |||||
def delete_page(page): | |||||
if not loads(page): | |||||
return | |||||
page = loads(page) | |||||
if page.get("public") and not is_workspace_manager(): | |||||
return | |||||
if frappe.db.exists("Workspace", page.get("name")): | |||||
frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True) | |||||
return {"name": "Home", "public": 1, "label": "Home"} | |||||
return {"name": page.get("name"), "public": page.get("public"), "title": page.get("title")} | |||||
@frappe.whitelist() | |||||
def sort_pages(sb_public_items, sb_private_items): | def sort_pages(sb_public_items, sb_private_items): | ||||
wspace_public_pages = get_page_list(['name', 'title'], {'public': 1}) | |||||
wspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user}) | |||||
if not loads(sb_public_items) and not loads(sb_private_items): | |||||
return | |||||
sb_public_items = loads(sb_public_items) | |||||
sb_private_items = loads(sb_private_items) | |||||
workspace_public_pages = get_page_list(['name', 'title'], {'public': 1}) | |||||
workspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user}) | |||||
if sb_private_items: | if sb_private_items: | ||||
sort_page(wspace_private_pages, sb_private_items) | |||||
return sort_page(workspace_private_pages, sb_private_items) | |||||
if sb_public_items and is_workspace_manager(): | if sb_public_items and is_workspace_manager(): | ||||
sort_page(wspace_public_pages, sb_public_items) | |||||
return sort_page(workspace_public_pages, sb_public_items) | |||||
def sort_page(wspace_pages, pages): | |||||
return False | |||||
def sort_page(workspace_pages, pages): | |||||
for seq, d in enumerate(pages): | for seq, d in enumerate(pages): | ||||
for page in wspace_pages: | |||||
for page in workspace_pages: | |||||
if page.title == d.get('title'): | if page.title == d.get('title'): | ||||
doc = frappe.get_doc('Workspace', page.name) | doc = frappe.get_doc('Workspace', page.name) | ||||
doc.sequence_id = seq + 1 | doc.sequence_id = seq + 1 | ||||
@@ -199,6 +280,27 @@ def sort_page(wspace_pages, pages): | |||||
doc.save(ignore_permissions=True) | doc.save(ignore_permissions=True) | ||||
break | break | ||||
return True | |||||
def last_sequence_id(doc): | |||||
doc_exists = frappe.db.exists({ | |||||
'doctype': 'Workspace', | |||||
'public': doc.public, | |||||
'for_user': doc.for_user | |||||
}) | |||||
if not doc_exists: | |||||
return 0 | |||||
return frappe.db.get_list('Workspace', | |||||
fields=['sequence_id'], | |||||
filters={ | |||||
'public': doc.public, | |||||
'for_user': doc.for_user | |||||
}, | |||||
order_by="sequence_id desc" | |||||
)[0].sequence_id | |||||
def get_page_list(fields, filters): | def get_page_list(fields, filters): | ||||
return frappe.get_list("Workspace", fields=fields, filters=filters, order_by='sequence_id asc') | return frappe.get_list("Workspace", fields=fields, filters=filters, order_by='sequence_id asc') | ||||
@@ -154,7 +154,7 @@ def install_app(name, verbose=False, set_as_patched=True): | |||||
for before_install in app_hooks.before_install or []: | for before_install in app_hooks.before_install or []: | ||||
out = frappe.get_attr(before_install)() | out = frappe.get_attr(before_install)() | ||||
if out==False: | |||||
if out is False: | |||||
return | return | ||||
if name != "frappe": | if name != "frappe": | ||||
@@ -346,14 +346,15 @@ def post_install(rebuild_website=False): | |||||
def set_all_patches_as_completed(app): | def set_all_patches_as_completed(app): | ||||
patch_path = os.path.join(frappe.get_pymodule_path(app), "patches.txt") | |||||
if os.path.exists(patch_path): | |||||
for patch in frappe.get_file_items(patch_path): | |||||
frappe.get_doc({ | |||||
"doctype": "Patch Log", | |||||
"patch": patch | |||||
}).insert(ignore_permissions=True) | |||||
frappe.db.commit() | |||||
from frappe.modules.patch_handler import get_patches_from_app | |||||
patches = get_patches_from_app(app) | |||||
for patch in patches: | |||||
frappe.get_doc({ | |||||
"doctype": "Patch Log", | |||||
"patch": patch | |||||
}).insert(ignore_permissions=True) | |||||
frappe.db.commit() | |||||
def init_singles(): | def init_singles(): | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Backup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Google Services\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Authentication\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", | |||||
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", | |||||
"creation": "2020-03-02 15:16:18.714190", | "creation": "2020-03-02 15:16:18.714190", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -260,7 +260,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-08-05 12:16:00.355268", | |||||
"modified": "2022-01-13 17:39:01.292154", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Integrations", | "module": "Integrations", | ||||
"name": "Integrations", | "name": "Integrations", | ||||
@@ -269,7 +269,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 15, | |||||
"sequence_id": 15.0, | |||||
"shortcuts": [], | "shortcuts": [], | ||||
"title": "Integrations" | "title": "Integrations" | ||||
} | } |
@@ -19,6 +19,8 @@ from frappe.modules.utils import sync_customizations | |||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs | from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs | ||||
from frappe.search.website_search import build_index_for_all_routes | from frappe.search.website_search import build_index_for_all_routes | ||||
from frappe.database.schema import add_column | from frappe.database.schema import add_column | ||||
from frappe.modules.patch_handler import PatchType | |||||
def migrate(verbose=True, skip_failing=False, skip_search_index=False): | def migrate(verbose=True, skip_failing=False, skip_search_index=False): | ||||
@@ -59,16 +61,13 @@ Otherwise, check the server logs and ensure that all the required services are r | |||||
clear_global_cache() | clear_global_cache() | ||||
#run before_migrate hooks | |||||
for app in frappe.get_installed_apps(): | for app in frappe.get_installed_apps(): | ||||
for fn in frappe.get_hooks('before_migrate', app_name=app): | for fn in frappe.get_hooks('before_migrate', app_name=app): | ||||
frappe.get_attr(fn)() | frappe.get_attr(fn)() | ||||
# run patches | |||||
frappe.modules.patch_handler.run_all(skip_failing) | |||||
# sync | |||||
frappe.modules.patch_handler.run_all(skip_failing=skip_failing, patch_type=PatchType.pre_model_sync) | |||||
frappe.model.sync.sync_all() | frappe.model.sync.sync_all() | ||||
frappe.modules.patch_handler.run_all(skip_failing=skip_failing, patch_type=PatchType.post_model_sync) | |||||
frappe.translate.clear_cache() | frappe.translate.clear_cache() | ||||
sync_jobs() | sync_jobs() | ||||
sync_fixtures() | sync_fixtures() | ||||
@@ -78,18 +77,16 @@ Otherwise, check the server logs and ensure that all the required services are r | |||||
frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() | frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() | ||||
# syncs statics | |||||
# syncs static files | |||||
clear_website_cache() | clear_website_cache() | ||||
# updating installed applications data | # updating installed applications data | ||||
frappe.get_single('Installed Applications').update_versions() | frappe.get_single('Installed Applications').update_versions() | ||||
#run after_migrate hooks | |||||
for app in frappe.get_installed_apps(): | for app in frappe.get_installed_apps(): | ||||
for fn in frappe.get_hooks('after_migrate', app_name=app): | for fn in frappe.get_hooks('after_migrate', app_name=app): | ||||
frappe.get_attr(fn)() | frappe.get_attr(fn)() | ||||
# build web_routes index | |||||
if not skip_search_index: | if not skip_search_index: | ||||
# Run this last as it updates the current session | # Run this last as it updates the current session | ||||
print('Building search index for {}'.format(frappe.local.site)) | print('Building search index for {}'.format(frappe.local.site)) | ||||
@@ -80,6 +80,7 @@ def rename_doc( | |||||
if doctype=='DocType': | if doctype=='DocType': | ||||
rename_doctype(doctype, old, new, force) | rename_doctype(doctype, old, new, force) | ||||
update_customizations(old, new) | |||||
update_attachments(doctype, old, new) | update_attachments(doctype, old, new) | ||||
@@ -174,6 +175,8 @@ def update_user_settings(old, new, link_fields): | |||||
else: | else: | ||||
continue | continue | ||||
def update_customizations(old: str, new: str) -> None: | |||||
frappe.db.set_value("Custom DocPerm", {"parent": old}, "parent", new, update_modified=False) | |||||
def update_attachments(doctype, old, new): | def update_attachments(doctype, old, new): | ||||
try: | try: | ||||
@@ -1,37 +1,76 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: MIT. See LICENSE | # License: MIT. See LICENSE | ||||
""" | |||||
Execute Patch Files | |||||
""" Patch Handler. | |||||
This file manages execution of manaully written patches. Patches are script | |||||
that apply changes in database schema or data to accomodate for changes in the | |||||
code. | |||||
Ways to specify patches: | |||||
1. patches.txt file specifies patches that run before doctype schema | |||||
migration. Each line represents one patch (old format). | |||||
2. patches.txt can alternatively also separate pre and post model sync | |||||
patches by using INI like file format: | |||||
```patches.txt | |||||
[pre_model_sync] | |||||
app.module.patch1 | |||||
app.module.patch2 | |||||
To run directly | |||||
python lib/wnf.py patch patch1, patch2 etc | |||||
python lib/wnf.py patch -f patch1, patch2 etc | |||||
[post_model_sync] | |||||
app.module.patch3 | |||||
``` | |||||
where patch1, patch2 is module name | |||||
When different sections are specified patches are executed in this order: | |||||
1. Run pre_model_sync patches | |||||
2. Reload/resync all doctype schema | |||||
3. Run post_model_sync patches | |||||
Hence any patch that just needs to modify data but doesn't depend on | |||||
old schema should be added to post_model_sync section of file. | |||||
3. simple python commands can be added by starting line with `execute:` | |||||
`execute:` example: `execute:print("hello world")` | |||||
""" | """ | ||||
import frappe, frappe.permissions, time | |||||
class PatchError(Exception): pass | |||||
import configparser | |||||
import time | |||||
from enum import Enum | |||||
from typing import List, Optional | |||||
import frappe | |||||
def run_all(skip_failing=False): | |||||
class PatchError(Exception): | |||||
pass | |||||
class PatchType(Enum): | |||||
pre_model_sync = "pre_model_sync" | |||||
post_model_sync = "post_model_sync" | |||||
def run_all(skip_failing: bool = False, patch_type: Optional[PatchType] = None) -> None: | |||||
"""run all pending patches""" | """run all pending patches""" | ||||
executed = [p[0] for p in frappe.db.sql("""select patch from `tabPatch Log`""")] | |||||
executed = set(frappe.get_all("Patch Log", fields="patch", pluck="patch")) | |||||
frappe.flags.final_patches = [] | frappe.flags.final_patches = [] | ||||
def run_patch(patch): | def run_patch(patch): | ||||
try: | try: | ||||
if not run_single(patchmodule = patch): | if not run_single(patchmodule = patch): | ||||
log(patch + ': failed: STOPPED') | |||||
print(patch + ': failed: STOPPED') | |||||
raise PatchError(patch) | raise PatchError(patch) | ||||
except Exception: | except Exception: | ||||
if not skip_failing: | if not skip_failing: | ||||
raise | raise | ||||
else: | else: | ||||
log('Failed to execute patch') | |||||
print('Failed to execute patch') | |||||
patches = get_all_patches(patch_type=patch_type) | |||||
for patch in get_all_patches(): | |||||
for patch in patches: | |||||
if patch and (patch not in executed): | if patch and (patch not in executed): | ||||
run_patch(patch) | run_patch(patch) | ||||
@@ -40,18 +79,54 @@ def run_all(skip_failing=False): | |||||
patch = patch.replace('finally:', '') | patch = patch.replace('finally:', '') | ||||
run_patch(patch) | run_patch(patch) | ||||
def get_all_patches(): | |||||
def get_all_patches(patch_type: Optional[PatchType] = None) -> List[str]: | |||||
if patch_type and not isinstance(patch_type, PatchType): | |||||
frappe.throw(f"Unsupported patch type specified: {patch_type}") | |||||
patches = [] | patches = [] | ||||
for app in frappe.get_installed_apps(): | for app in frappe.get_installed_apps(): | ||||
if app == "shopping_cart": | |||||
continue | |||||
# 3-to-4 fix | |||||
if app=="webnotes": | |||||
app="frappe" | |||||
patches.extend(frappe.get_file_items(frappe.get_pymodule_path(app, "patches.txt"))) | |||||
patches.extend(get_patches_from_app(app, patch_type=patch_type)) | |||||
return patches | return patches | ||||
def get_patches_from_app(app: str, patch_type: Optional[PatchType] = None) -> List[str]: | |||||
""" Get patches from an app's patches.txt | |||||
patches.txt can be: | |||||
1. ini like file with section for different patch_type | |||||
2. plain text file with each line representing a patch. | |||||
""" | |||||
patches_txt = frappe.get_pymodule_path(app, "patches.txt") | |||||
try: | |||||
# Attempt to parse as ini file with pre/post patches | |||||
# allow_no_value: patches are not key value pairs | |||||
# delimiters = '\n' to avoid treating default `:` and `=` in execute as k:v delimiter | |||||
parser = configparser.ConfigParser(allow_no_value=True, delimiters="\n") | |||||
# preserve case | |||||
parser.optionxform = str | |||||
parser.read(patches_txt) | |||||
if not patch_type: | |||||
return [patch for patch in parser[PatchType.pre_model_sync.value]] + \ | |||||
[patch for patch in parser[PatchType.post_model_sync.value]] | |||||
if patch_type.value in parser.sections(): | |||||
return [patch for patch in parser[patch_type.value]] | |||||
else: | |||||
frappe.throw(frappe._("Patch type {} not found in patches.txt").format(patch_type)) | |||||
except configparser.MissingSectionHeaderError: | |||||
# treat as old format with each line representing a single patch | |||||
# backward compatbility with old patches.txt format | |||||
if not patch_type or patch_type == PatchType.pre_model_sync: | |||||
return frappe.get_file_items(patches_txt) | |||||
return [] | |||||
def reload_doc(args): | def reload_doc(args): | ||||
import frappe.modules | import frappe.modules | ||||
run_single(method = frappe.modules.reload_doc, methodargs = args) | run_single(method = frappe.modules.reload_doc, methodargs = args) | ||||
@@ -73,7 +148,7 @@ def execute_patch(patchmodule, method=None, methodargs=None): | |||||
frappe.db.begin() | frappe.db.begin() | ||||
start_time = time.time() | start_time = time.time() | ||||
try: | try: | ||||
log('Executing {patch} in {site} ({db})'.format(patch=patchmodule or str(methodargs), | |||||
print('Executing {patch} in {site} ({db})'.format(patch=patchmodule or str(methodargs), | |||||
site=frappe.local.site, db=frappe.db.cur_db_name)) | site=frappe.local.site, db=frappe.db.cur_db_name)) | ||||
if patchmodule: | if patchmodule: | ||||
if patchmodule.startswith("finally:"): | if patchmodule.startswith("finally:"): | ||||
@@ -96,7 +171,7 @@ def execute_patch(patchmodule, method=None, methodargs=None): | |||||
frappe.db.commit() | frappe.db.commit() | ||||
end_time = time.time() | end_time = time.time() | ||||
block_user(False) | block_user(False) | ||||
log('Success: Done in {time}s'.format(time = round(end_time - start_time, 3))) | |||||
print('Success: Done in {time}s'.format(time = round(end_time - start_time, 3))) | |||||
return True | return True | ||||
@@ -109,10 +184,7 @@ def executed(patchmodule): | |||||
if patchmodule.startswith('finally:'): | if patchmodule.startswith('finally:'): | ||||
# patches are saved without the finally: tag | # patches are saved without the finally: tag | ||||
patchmodule = patchmodule.replace('finally:', '') | patchmodule = patchmodule.replace('finally:', '') | ||||
done = frappe.db.get_value("Patch Log", {"patch": patchmodule}) | |||||
# if done: | |||||
# print "Patch %s already executed in %s" % (patchmodule, frappe.db.cur_db_name) | |||||
return done | |||||
return frappe.db.get_value("Patch Log", {"patch": patchmodule}) | |||||
def block_user(block, msg=None): | def block_user(block, msg=None): | ||||
"""stop/start execution till patch is run""" | """stop/start execution till patch is run""" | ||||
@@ -128,6 +200,3 @@ def check_session_stopped(): | |||||
if frappe.db.get_global("__session_status")=='stop': | if frappe.db.get_global("__session_status")=='stop': | ||||
frappe.msgprint(frappe.db.get_global("__session_status_message")) | frappe.msgprint(frappe.db.get_global("__session_status_message")) | ||||
raise frappe.SessionStopped('Session Stopped') | raise frappe.SessionStopped('Session Stopped') | ||||
def log(msg): | |||||
print (msg) |
@@ -1,3 +1,4 @@ | |||||
[pre_model_sync] | |||||
frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3 | frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3 | ||||
execute:frappe.utils.global_search.setup_global_search_table() | execute:frappe.utils.global_search.setup_global_search_table() | ||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23 | execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23 | ||||
@@ -87,7 +88,6 @@ frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permission | |||||
frappe.patches.v11_0.set_default_letter_head_source | frappe.patches.v11_0.set_default_letter_head_source | ||||
frappe.patches.v12_0.set_primary_key_in_series | frappe.patches.v12_0.set_primary_key_in_series | ||||
execute:frappe.delete_doc("Page", "modules", ignore_missing=True) | execute:frappe.delete_doc("Page", "modules", ignore_missing=True) | ||||
frappe.patches.v11_0.set_default_letter_head_source | |||||
frappe.patches.v12_0.setup_comments_from_communications | frappe.patches.v12_0.setup_comments_from_communications | ||||
frappe.patches.v12_0.replace_null_values_in_tables | frappe.patches.v12_0.replace_null_values_in_tables | ||||
frappe.patches.v12_0.reset_home_settings | frappe.patches.v12_0.reset_home_settings | ||||
@@ -123,7 +123,7 @@ frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats | |||||
frappe.patches.v12_0.remove_example_email_thread_notify | frappe.patches.v12_0.remove_example_email_thread_notify | ||||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() | execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() | ||||
frappe.patches.v12_0.set_correct_url_in_files | frappe.patches.v12_0.set_correct_url_in_files | ||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) | |||||
execute:frappe.reload_doc('core', 'doctype', 'doctype') | |||||
execute:frappe.reload_doc('custom', 'doctype', 'property_setter') | execute:frappe.reload_doc('custom', 'doctype', 'property_setter') | ||||
frappe.patches.v13_0.remove_invalid_options_for_data_fields | frappe.patches.v13_0.remove_invalid_options_for_data_fields | ||||
frappe.patches.v13_0.website_theme_custom_scss | frappe.patches.v13_0.website_theme_custom_scss | ||||
@@ -184,12 +184,14 @@ frappe.patches.v13_0.queryreport_columns | |||||
frappe.patches.v13_0.jinja_hook | frappe.patches.v13_0.jinja_hook | ||||
frappe.patches.v13_0.update_notification_channel_if_empty | frappe.patches.v13_0.update_notification_channel_if_empty | ||||
frappe.patches.v13_0.set_first_day_of_the_week | frappe.patches.v13_0.set_first_day_of_the_week | ||||
frappe.patches.v14_0.drop_data_import_legacy | |||||
frappe.patches.v14_0.rename_cancelled_documents | frappe.patches.v14_0.rename_cancelled_documents | ||||
frappe.patches.v14_0.copy_mail_data #08.03.21 | |||||
frappe.patches.v14_0.update_workspace2 # 20.09.2021 | frappe.patches.v14_0.update_workspace2 # 20.09.2021 | ||||
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 | |||||
frappe.patches.v14_0.transform_todo_schema | |||||
[post_model_sync] | |||||
frappe.patches.v14_0.drop_data_import_legacy | |||||
frappe.patches.v14_0.copy_mail_data #08.03.21 | |||||
frappe.patches.v14_0.update_github_endpoints #08-11-2021 | frappe.patches.v14_0.update_github_endpoints #08-11-2021 | ||||
frappe.patches.v14_0.remove_db_aggregation | frappe.patches.v14_0.remove_db_aggregation | ||||
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 | |||||
frappe.patches.v14_0.update_color_names_in_kanban_board_column | frappe.patches.v14_0.update_color_names_in_kanban_board_column | ||||
frappe.patches.v14_0.transform_todo_schema |
@@ -3,9 +3,6 @@ import frappe | |||||
def execute(): | def execute(): | ||||
frappe.reload_doc("email", "doctype", "imap_folder") | |||||
frappe.reload_doc("email", "doctype", "email_account") | |||||
# patch for all Email Account with the flag use_imap | # patch for all Email Account with the flag use_imap | ||||
for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1, "use_imap": 1}): | for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1, "use_imap": 1}): | ||||
# get all data from Email Account | # get all data from Email Account | ||||
@@ -5,7 +5,6 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
def execute(): | def execute(): | ||||
frappe.reload_doc("desk", "doctype", "kanban_board_column") | |||||
indicator_map = { | indicator_map = { | ||||
'blue': 'Blue', | 'blue': 'Blue', | ||||
'orange': 'Orange', | 'orange': 'Orange', | ||||
@@ -5,10 +5,10 @@ from frappe import _ | |||||
def execute(): | def execute(): | ||||
frappe.reload_doc('desk', 'doctype', 'workspace', force=True) | frappe.reload_doc('desk', 'doctype', 'workspace', force=True) | ||||
for seq, wspace in enumerate(frappe.get_all('Workspace', order_by='name asc')): | |||||
doc = frappe.get_doc('Workspace', wspace.name) | |||||
for seq, workspace in enumerate(frappe.get_all('Workspace', order_by='name asc')): | |||||
doc = frappe.get_doc('Workspace', workspace.name) | |||||
content = create_content(doc) | content = create_content(doc) | ||||
update_wspace(doc, seq, content) | |||||
update_workspace(doc, seq, content) | |||||
frappe.db.commit() | frappe.db.commit() | ||||
def create_content(doc): | def create_content(doc): | ||||
@@ -49,7 +49,7 @@ def create_content(doc): | |||||
del doc.links[doc.links.index(l)] | del doc.links[doc.links.index(l)] | ||||
return content | return content | ||||
def update_wspace(doc, seq, content): | |||||
def update_workspace(doc, seq, content): | |||||
if not doc.title and not doc.content and not doc.is_standard and not doc.public: | if not doc.title and not doc.content and not doc.is_standard and not doc.public: | ||||
doc.sequence_id = seq + 1 | doc.sequence_id = seq + 1 | ||||
doc.content = json.dumps(content) | doc.content = json.dumps(content) | ||||
@@ -23,7 +23,7 @@ def print_has_permission_check_logs(func): | |||||
frappe.flags['has_permission_check_logs'] = [] | frappe.flags['has_permission_check_logs'] = [] | ||||
result = func(*args, **kwargs) | result = func(*args, **kwargs) | ||||
self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user | self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user | ||||
raise_exception = False if kwargs.get('raise_exception') == False else True | |||||
raise_exception = False if kwargs.get('raise_exception') is False else True | |||||
# print only if access denied | # print only if access denied | ||||
# and if user is checking his own permission | # and if user is checking his own permission | ||||
@@ -96,6 +96,7 @@ import "./frappe/ui/sort_selector.js"; | |||||
import "./frappe/change_log.html"; | import "./frappe/change_log.html"; | ||||
import "./frappe/ui/workspace_loading_skeleton.html"; | import "./frappe/ui/workspace_loading_skeleton.html"; | ||||
import "./frappe/ui/workspace_sidebar_loading_skeleton.html"; | |||||
import "./frappe/desk.js"; | import "./frappe/desk.js"; | ||||
import "./frappe/query_string.js"; | import "./frappe/query_string.js"; | ||||
@@ -214,19 +214,20 @@ frappe.Application = class Application { | |||||
email_password_prompt(email_account,user,i) { | email_password_prompt(email_account,user,i) { | ||||
var me = this; | var me = this; | ||||
const email_id = email_account[i]["email_id"]; | |||||
let d = new frappe.ui.Dialog({ | let d = new frappe.ui.Dialog({ | ||||
title: __('Password missing in Email Account'), | title: __('Password missing in Email Account'), | ||||
fields: [ | fields: [ | ||||
{ | { | ||||
'fieldname': 'password', | 'fieldname': 'password', | ||||
'fieldtype': 'Password', | 'fieldtype': 'Password', | ||||
'label': __('Please enter the password for: <b>{0}</b>', [email_account[i]["email_id"]]), | |||||
'label': __('Please enter the password for: <b>{0}</b>', [email_id], "Email Account"), | |||||
'reqd': 1 | 'reqd': 1 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "submit", | "fieldname": "submit", | ||||
"fieldtype": "Button", | "fieldtype": "Button", | ||||
"label": __("Submit") | |||||
"label": __("Submit", null, "Submit password for Email Account") | |||||
} | } | ||||
] | ] | ||||
}); | }); | ||||
@@ -11,7 +11,8 @@ frappe.ui.form.ControlDateRange = class ControlDateRange extends frappe.ui.form. | |||||
language: "en", | language: "en", | ||||
range: true, | range: true, | ||||
autoClose: true, | autoClose: true, | ||||
toggleSelected: false | |||||
toggleSelected: false, | |||||
firstDay: frappe.datetime.get_first_day_of_the_week_index() | |||||
}; | }; | ||||
this.datepicker_options.dateFormat = | this.datepicker_options.dateFormat = | ||||
(frappe.boot.sysdefaults.date_format || 'yyyy-mm-dd'); | (frappe.boot.sysdefaults.date_format || 'yyyy-mm-dd'); | ||||
@@ -2,7 +2,7 @@ frappe.ui.form.ControlDynamicLink = class ControlDynamicLink extends frappe.ui.f | |||||
get_options() { | get_options() { | ||||
let options = ''; | let options = ''; | ||||
if (this.df.get_options) { | if (this.df.get_options) { | ||||
options = this.df.get_options(); | |||||
options = this.df.get_options(this); | |||||
} else if (this.docname==null && cur_dialog) { | } else if (this.docname==null && cur_dialog) { | ||||
//for dialog box | //for dialog box | ||||
options = cur_dialog.get_value(this.df.options); | options = cur_dialog.get_value(this.df.options); | ||||
@@ -943,7 +943,10 @@ frappe.ui.form.Form = class FrappeForm { | |||||
// re-enable buttons | // re-enable buttons | ||||
resolve(); | resolve(); | ||||
} | } | ||||
frappe.throw (__("No permission to '{0}' {1}", [__(action), __(this.doc.doctype)])); | |||||
frappe.throw( | |||||
__("No permission to '{0}' {1}", [__(action), __(this.doc.doctype)], "{0} = verb, {1} = object") | |||||
); | |||||
} | } | ||||
} | } | ||||
@@ -7,12 +7,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) { | |||||
$(btn).prop("disabled", true); | $(btn).prop("disabled", true); | ||||
// specified here because there are keyboard shortcuts to save | // specified here because there are keyboard shortcuts to save | ||||
var working_label = { | |||||
"Save": __("Saving"), | |||||
"Submit": __("Submitting"), | |||||
"Update": __("Updating"), | |||||
"Amend": __("Amending"), | |||||
"Cancel": __("Cancelling") | |||||
const working_label = { | |||||
"Save": __("Saving", null, "Freeze message while saving a document"), | |||||
"Submit": __("Submitting", null, "Freeze message while submitting a document"), | |||||
"Update": __("Updating", null, "Freeze message while updating a document"), | |||||
"Amend": __("Amending", null, "Freeze message while amending a document"), | |||||
"Cancel": __("Cancelling", null, "Freeze message while cancelling a document"), | |||||
}[toTitle(action)]; | }[toTitle(action)]; | ||||
var freeze_message = working_label ? __(working_label) : ""; | var freeze_message = working_label ? __(working_label) : ""; | ||||
@@ -154,8 +154,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) { | |||||
if (error_fields.length) { | if (error_fields.length) { | ||||
let meta = frappe.get_meta(doc.doctype); | let meta = frappe.get_meta(doc.doctype); | ||||
if (meta.istable) { | if (meta.istable) { | ||||
var message = __('Mandatory fields required in table {0}, Row {1}', | |||||
[__(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold(), doc.idx]); | |||||
const table_label = __(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold(); | |||||
var message = __('Mandatory fields required in table {0}, Row {1}', [table_label, doc.idx]); | |||||
} else { | } else { | ||||
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]); | var message = __('Mandatory fields required in {0}', [__(doc.doctype)]); | ||||
} | } | ||||
@@ -276,4 +276,3 @@ frappe.ui.form.update_calling_link = (newdoc) => { | |||||
frappe._from_link = null; | frappe._from_link = null; | ||||
} | } | ||||
} | } | ||||
@@ -200,7 +200,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
frappe.render_template("list_view_permission_restrictions", { | frappe.render_template("list_view_permission_restrictions", { | ||||
condition_list: match_rules_list, | condition_list: match_rules_list, | ||||
}), | }), | ||||
__("Restrictions") | |||||
__("Restrictions", null, "Title of message showing restrictions in list view") | |||||
); | ); | ||||
} | } | ||||
@@ -255,8 +255,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
set_primary_action() { | set_primary_action() { | ||||
if (this.can_create) { | if (this.can_create) { | ||||
const doctype_name = __(frappe.router.doctype_layout) || __(this.doctype); | |||||
// Better style would be __("Add {0}", [doctype_name], "Primary action in list view") | |||||
// Keeping it like this to not disrupt existing translations | |||||
const label = `${__("Add", null, "Primary action in list view")} ${doctype_name}`; | |||||
this.page.set_primary_action( | this.page.set_primary_action( | ||||
`${__("Add")} ${frappe.router.doctype_layout || __(this.doctype)}`, | |||||
label, | |||||
() => { | () => { | ||||
if (this.settings.primary_action) { | if (this.settings.primary_action) { | ||||
this.settings.primary_action(); | this.settings.primary_action(); | ||||
@@ -320,9 +325,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
setup_freeze_area() { | setup_freeze_area() { | ||||
this.$freeze = $( | this.$freeze = $( | ||||
`<div class="freeze flex justify-center align-center text-muted">${__( | |||||
"Loading" | |||||
)}...</div>` | |||||
`<div class="freeze flex justify-center align-center text-muted"> | |||||
${__("Loading")}... | |||||
</div>` | |||||
).hide(); | ).hide(); | ||||
this.$result.append(this.$freeze); | this.$result.append(this.$freeze); | ||||
} | } | ||||
@@ -460,8 +465,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
? __("No {0} found", [__(this.doctype)]) | ? __("No {0} found", [__(this.doctype)]) | ||||
: __("You haven't created a {0} yet", [__(this.doctype)]); | : __("You haven't created a {0} yet", [__(this.doctype)]); | ||||
let new_button_label = filters && filters.length | let new_button_label = filters && filters.length | ||||
? __("Create a new {0}", [__(this.doctype)]) | |||||
: __("Create your first {0}", [__(this.doctype)]); | |||||
? __("Create a new {0}", [__(this.doctype)], "Create a new document from list view") | |||||
: __("Create your first {0}", [__(this.doctype)], "Create a new document from list view"); | |||||
let empty_state_image = | let empty_state_image = | ||||
this.settings.empty_state_image || | this.settings.empty_state_image || | ||||
"/assets/frappe/images/ui-states/list-empty-state.svg"; | "/assets/frappe/images/ui-states/list-empty-state.svg"; | ||||
@@ -469,7 +474,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const new_button = this.can_create | const new_button = this.can_create | ||||
? `<p><button class="btn btn-primary btn-sm btn-new-doc hidden-xs"> | ? `<p><button class="btn btn-primary btn-sm btn-new-doc hidden-xs"> | ||||
${new_button_label} | ${new_button_label} | ||||
</button> <button class="btn btn-primary btn-new-doc visible-xs">${__('Create New')}</button></p>` | |||||
</button> <button class="btn btn-primary btn-new-doc visible-xs"> | |||||
${__("Create New", null, "Create a new document from list view")} | |||||
</button></p>` | |||||
: ""; | : ""; | ||||
return `<div class="msg-box no-border"> | return `<div class="msg-box no-border"> | ||||
@@ -486,7 +493,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
if (this.list_view_settings && !this.list_view_settings.disable_count) { | if (this.list_view_settings && !this.list_view_settings.disable_count) { | ||||
this.$result | this.$result | ||||
.find(".list-count") | .find(".list-count") | ||||
.html(`<span>${__("Refreshing")}...</span>`); | |||||
.html(`<span>${__("Refreshing", null, "Document count in list view")}...</span>`); | |||||
} | } | ||||
} | } | ||||
@@ -1081,14 +1088,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
frappe.ui.keys.add_shortcut({ | frappe.ui.keys.add_shortcut({ | ||||
shortcut: "down", | shortcut: "down", | ||||
action: () => handle_navigation("down"), | action: () => handle_navigation("down"), | ||||
description: __("Navigate list down"), | |||||
description: __("Navigate list down", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
frappe.ui.keys.add_shortcut({ | frappe.ui.keys.add_shortcut({ | ||||
shortcut: "up", | shortcut: "up", | ||||
action: () => handle_navigation("up"), | action: () => handle_navigation("up"), | ||||
description: __("Navigate list up"), | |||||
description: __("Navigate list up", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
@@ -1100,7 +1107,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
check_row($list_row); | check_row($list_row); | ||||
focus_next(); | focus_next(); | ||||
}, | }, | ||||
description: __("Select multiple list items"), | |||||
description: __("Select multiple list items", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
@@ -1112,7 +1119,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
check_row($list_row); | check_row($list_row); | ||||
focus_prev(); | focus_prev(); | ||||
}, | }, | ||||
description: __("Select multiple list items"), | |||||
description: __("Select multiple list items", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
@@ -1126,7 +1133,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
} | } | ||||
return false; | return false; | ||||
}, | }, | ||||
description: __("Open list item"), | |||||
description: __("Open list item", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
@@ -1140,7 +1147,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
} | } | ||||
return false; | return false; | ||||
}, | }, | ||||
description: __("Select list item"), | |||||
description: __("Select list item", null, "Description of a list view shortcut"), | |||||
page: this.page, | page: this.page, | ||||
}); | }); | ||||
} | } | ||||
@@ -1515,7 +1522,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
if (frappe.model.can_import(doctype, null, this.meta)) { | if (frappe.model.can_import(doctype, null, this.meta)) { | ||||
items.push({ | items.push({ | ||||
label: __("Import"), | |||||
label: __("Import", null, "Button in list view menu"), | |||||
action: () => | action: () => | ||||
frappe.set_route("list", "data-import", { | frappe.set_route("list", "data-import", { | ||||
reference_doctype: doctype, | reference_doctype: doctype, | ||||
@@ -1526,7 +1533,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
if (frappe.model.can_set_user_permissions(doctype)) { | if (frappe.model.can_set_user_permissions(doctype)) { | ||||
items.push({ | items.push({ | ||||
label: __("User Permissions"), | |||||
label: __("User Permissions", null, "Button in list view menu"), | |||||
action: () => | action: () => | ||||
frappe.set_route("list", "user-permission", { | frappe.set_route("list", "user-permission", { | ||||
allow: doctype, | allow: doctype, | ||||
@@ -1537,7 +1544,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
if (frappe.user_roles.includes("System Manager")) { | if (frappe.user_roles.includes("System Manager")) { | ||||
items.push({ | items.push({ | ||||
label: __("Role Permissions Manager"), | |||||
label: __("Role Permissions Manager", null, "Button in list view menu"), | |||||
action: () => | action: () => | ||||
frappe.set_route("permission-manager", { | frappe.set_route("permission-manager", { | ||||
doctype, | doctype, | ||||
@@ -1546,7 +1553,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
}); | }); | ||||
items.push({ | items.push({ | ||||
label: __("Customize"), | |||||
label: __("Customize", null, "Button in list view menu"), | |||||
action: () => { | action: () => { | ||||
if (!this.meta) return; | if (!this.meta) return; | ||||
if (this.meta.custom) { | if (this.meta.custom) { | ||||
@@ -1563,7 +1570,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
} | } | ||||
items.push({ | items.push({ | ||||
label: __("Toggle Sidebar"), | |||||
label: __("Toggle Sidebar", null, "Button in list view menu"), | |||||
action: () => this.toggle_side_bar(), | action: () => this.toggle_side_bar(), | ||||
condition: () => !this.hide_sidebar, | condition: () => !this.hide_sidebar, | ||||
standard: true, | standard: true, | ||||
@@ -1571,7 +1578,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
}); | }); | ||||
items.push({ | items.push({ | ||||
label: __("Share URL"), | |||||
label: __("Share URL", null, "Button in list view menu"), | |||||
action: () => this.share_url(), | action: () => this.share_url(), | ||||
standard: true, | standard: true, | ||||
shortcut: "Ctrl+L", | shortcut: "Ctrl+L", | ||||
@@ -1583,7 +1590,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
) { | ) { | ||||
// edit doctype | // edit doctype | ||||
items.push({ | items.push({ | ||||
label: __("Edit DocType"), | |||||
label: __("Edit DocType", null, "Button in list view menu"), | |||||
action: () => frappe.set_route("form", "doctype", doctype), | action: () => frappe.set_route("form", "doctype", doctype), | ||||
standard: true, | standard: true, | ||||
}); | }); | ||||
@@ -1591,7 +1598,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
if (frappe.user.has_role("System Manager")) { | if (frappe.user.has_role("System Manager")) { | ||||
items.push({ | items.push({ | ||||
label: __("List Settings"), | |||||
label: __("List Settings", null, "Button in list view menu"), | |||||
action: () => this.show_list_settings(), | action: () => this.show_list_settings(), | ||||
standard: true, | standard: true, | ||||
}); | }); | ||||
@@ -1682,7 +1689,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
// utility | // utility | ||||
const bulk_assignment = () => { | const bulk_assignment = () => { | ||||
return { | return { | ||||
label: __("Assign To"), | |||||
label: __("Assign To", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.assign( | bulk_operations.assign( | ||||
@@ -1700,7 +1707,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_assignment_rule = () => { | const bulk_assignment_rule = () => { | ||||
return { | return { | ||||
label: __("Apply Assignment Rule"), | |||||
label: __("Apply Assignment Rule", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.apply_assignment_rule( | bulk_operations.apply_assignment_rule( | ||||
@@ -1718,7 +1725,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_add_tags = () => { | const bulk_add_tags = () => { | ||||
return { | return { | ||||
label: __("Add Tags"), | |||||
label: __("Add Tags", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.add_tags( | bulk_operations.add_tags( | ||||
@@ -1736,7 +1743,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_printing = () => { | const bulk_printing = () => { | ||||
return { | return { | ||||
label: __("Print"), | |||||
label: __("Print", null, "Button in list view actions menu"), | |||||
action: () => bulk_operations.print(this.get_checked_items()), | action: () => bulk_operations.print(this.get_checked_items()), | ||||
standard: true, | standard: true, | ||||
}; | }; | ||||
@@ -1744,13 +1751,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_delete = () => { | const bulk_delete = () => { | ||||
return { | return { | ||||
label: __("Delete"), | |||||
label: __("Delete", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
const docnames = this.get_checked_items(true).map( | const docnames = this.get_checked_items(true).map( | ||||
(docname) => docname.toString() | (docname) => docname.toString() | ||||
); | ); | ||||
frappe.confirm( | frappe.confirm( | ||||
__("Delete {0} items permanently?", [docnames.length]), | |||||
__("Delete {0} items permanently?", [docnames.length], "Title of confirmation dialog"), | |||||
() => { | () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.delete(docnames, () => { | bulk_operations.delete(docnames, () => { | ||||
@@ -1767,12 +1774,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_cancel = () => { | const bulk_cancel = () => { | ||||
return { | return { | ||||
label: __("Cancel"), | |||||
label: __("Cancel", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
const docnames = this.get_checked_items(true); | const docnames = this.get_checked_items(true); | ||||
if (docnames.length > 0) { | if (docnames.length > 0) { | ||||
frappe.confirm( | frappe.confirm( | ||||
__("Cancel {0} documents?", [docnames.length]), | |||||
__("Cancel {0} documents?", [docnames.length], "Title of confirmation dialog"), | |||||
() => { | () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.submit_or_cancel( | bulk_operations.submit_or_cancel( | ||||
@@ -1793,12 +1800,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_submit = () => { | const bulk_submit = () => { | ||||
return { | return { | ||||
label: __("Submit"), | |||||
label: __("Submit", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
const docnames = this.get_checked_items(true); | const docnames = this.get_checked_items(true); | ||||
if (docnames.length > 0) { | if (docnames.length > 0) { | ||||
frappe.confirm( | frappe.confirm( | ||||
__("Submit {0} documents?", [docnames.length]), | |||||
__("Submit {0} documents?", [docnames.length], "Title of confirmation dialog"), | |||||
() => { | () => { | ||||
this.disable_list_update = true; | this.disable_list_update = true; | ||||
bulk_operations.submit_or_cancel( | bulk_operations.submit_or_cancel( | ||||
@@ -1820,7 +1827,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_edit = () => { | const bulk_edit = () => { | ||||
return { | return { | ||||
label: __("Edit"), | |||||
label: __("Edit", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
let field_mappings = {}; | let field_mappings = {}; | ||||
@@ -1850,7 +1857,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
const bulk_export = () => { | const bulk_export = () => { | ||||
return { | return { | ||||
label: __("Export"), | |||||
label: __("Export", null, "Button in list view actions menu"), | |||||
action: () => { | action: () => { | ||||
const docnames = this.get_checked_items(true); | const docnames = this.get_checked_items(true); | ||||
@@ -39,7 +39,7 @@ Object.assign(frappe.model, { | |||||
} | } | ||||
frappe.model.sync_docinfo(r); | frappe.model.sync_docinfo(r); | ||||
return r.docs; | |||||
}, | }, | ||||
rename_after_save: (d, i) => { | rename_after_save: (d, i) => { | ||||
@@ -133,14 +133,14 @@ frappe.router = { | |||||
// /app/user/user-001 = ["Form", "User", "user-001"] | // /app/user/user-001 = ["Form", "User", "user-001"] | ||||
// /app/event/view/calendar/default = ["List", "Event", "Calendar", "Default"] | // /app/event/view/calendar/default = ["List", "Event", "Calendar", "Default"] | ||||
let private_wspace = route[1] && `${route[1]}-${frappe.user.name.toLowerCase()}`; | |||||
let private_workspace = route[1] && `${route[1]}-${frappe.user.name.toLowerCase()}`; | |||||
if (frappe.workspaces[route[0]]) { | if (frappe.workspaces[route[0]]) { | ||||
// public workspace | // public workspace | ||||
route = ['Workspaces', frappe.workspaces[route[0]].title]; | route = ['Workspaces', frappe.workspaces[route[0]].title]; | ||||
} else if (route[0] == 'private' && frappe.workspaces[private_wspace]) { | |||||
} else if (route[0] == 'private' && frappe.workspaces[private_workspace]) { | |||||
// private workspace | // private workspace | ||||
route = ['Workspaces', 'private', frappe.workspaces[private_wspace].title]; | |||||
route = ['Workspaces', 'private', frappe.workspaces[private_workspace].title]; | |||||
} else if (this.routes[route[0]]) { | } else if (this.routes[route[0]]) { | ||||
// route | // route | ||||
route = this.set_doctype_route(route); | route = this.set_doctype_route(route); | ||||
@@ -57,8 +57,10 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { | |||||
// show footer | // show footer | ||||
this.action = this.action || { primary: { }, secondary: { } }; | this.action = this.action || { primary: { }, secondary: { } }; | ||||
if (this.primary_action || (this.action.primary && this.action.primary.onsubmit)) { | if (this.primary_action || (this.action.primary && this.action.primary.onsubmit)) { | ||||
this.set_primary_action(this.primary_action_label || this.action.primary.label || __("Submit"), | |||||
this.primary_action || this.action.primary.onsubmit); | |||||
this.set_primary_action( | |||||
this.primary_action_label || this.action.primary.label || __("Submit", null, "Primary action in dialog"), | |||||
this.primary_action || this.action.primary.onsubmit | |||||
); | |||||
} | } | ||||
if (this.secondary_action) { | if (this.secondary_action) { | ||||
@@ -63,7 +63,7 @@ frappe.warn = function(title, message_html, proceed_action, primary_label, is_mi | |||||
if (proceed_action) proceed_action(); | if (proceed_action) proceed_action(); | ||||
d.hide(); | d.hide(); | ||||
}, | }, | ||||
secondary_action_label: __("Cancel"), | |||||
secondary_action_label: __("Cancel", null, "Secondary button in warning dialog"), | |||||
secondary_action: () => d.hide(), | secondary_action: () => d.hide(), | ||||
minimizable: is_minimizable | minimizable: is_minimizable | ||||
}); | }); | ||||
@@ -113,42 +113,44 @@ frappe.ui.SortSelector = class SortSelector { | |||||
if(!this.args.options) { | if(!this.args.options) { | ||||
// default options | // default options | ||||
var _options = [ | var _options = [ | ||||
{'fieldname': 'modified'} | |||||
{'fieldname': 'modified'}, | |||||
{'fieldname': 'name'}, | |||||
{'fieldname': 'creation'}, | |||||
{'fieldname': 'idx'}, | |||||
] | ] | ||||
// title field | // title field | ||||
if(meta.title_field) { | |||||
_options.push({'fieldname': meta.title_field}); | |||||
if (meta.title_field) { | |||||
_options.splice(1, 0, {'fieldname': meta.title_field}); | |||||
} | |||||
// sort field - set via DocType schema or Customize Form | |||||
if (meta_sort_field) { | |||||
_options.splice(1, 0, { 'fieldname': meta_sort_field }); | |||||
} | } | ||||
// bold or mandatory | |||||
// bold, mandatory and fields that are available in list view | |||||
meta.fields.forEach(function(df) { | meta.fields.forEach(function(df) { | ||||
if(df.mandatory || df.bold) { | |||||
if ( | |||||
(df.mandatory || df.bold || df.in_list_view) | |||||
&& frappe.model.is_value_type(df.fieldtype) | |||||
&& frappe.perm.has_perm(me.doctype, df.permlevel, "read") | |||||
) { | |||||
_options.push({fieldname: df.fieldname, label: df.label}); | _options.push({fieldname: df.fieldname, label: df.label}); | ||||
} | } | ||||
}); | }); | ||||
// meta sort field | |||||
if(meta_sort_field) _options.push({ 'fieldname': meta_sort_field }); | |||||
// more default options | |||||
_options.push( | |||||
{'fieldname': 'name'}, | |||||
{'fieldname': 'creation'}, | |||||
{'fieldname': 'idx'} | |||||
) | |||||
// add missing labels | |||||
_options.forEach(option => { | |||||
if (!option.label) { | |||||
option.label = me.get_label(option.fieldname); | |||||
} | |||||
}); | |||||
// de-duplicate | // de-duplicate | ||||
this.args.options = _options.uniqBy(function(obj) { | |||||
this.args.options = _options.uniqBy(obj => { | |||||
return obj.fieldname; | return obj.fieldname; | ||||
}); | }); | ||||
// add missing labels | |||||
this.args.options.forEach(function(o) { | |||||
if(!o.label) { | |||||
o.label = me.get_label(o.fieldname); | |||||
} | |||||
}); | |||||
} | } | ||||
// set default | // set default | ||||
@@ -0,0 +1,22 @@ | |||||
<div class="workspace-sidebar-skeleton"> | |||||
<div class="widget-group-body"> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
</div> | |||||
<div class="widget-group-body"> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box child skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
<div class="widget sidebar-box skeleton-card"></div> | |||||
</div> | |||||
</div> |
@@ -54,7 +54,7 @@ frappe.ui.DiffView = class DiffView { | |||||
fieldname: "diff", | fieldname: "diff", | ||||
}, | }, | ||||
], | ], | ||||
size: "large", | |||||
size: "extra-large", | |||||
}); | }); | ||||
return dialog; | return dialog; | ||||
} | } | ||||
@@ -243,9 +243,28 @@ Object.assign(frappe.utils, { | |||||
'=': '=' | '=': '=' | ||||
}; | }; | ||||
return String(txt).replace(/[&<>"'`=/]/g, function(char) { | |||||
return escape_html_mapping[char]; | |||||
}); | |||||
return String(txt).replace( | |||||
/[&<>"'`=/]/g, | |||||
char => escape_html_mapping[char] || char | |||||
); | |||||
}, | |||||
unescape_html: function(txt) { | |||||
let unescape_html_mapping = { | |||||
'&': '&', | |||||
'<': '<', | |||||
'>': '>', | |||||
'"': '"', | |||||
''': "'", | |||||
'/': '/', | |||||
'`': '`', | |||||
'=': '=' | |||||
}; | |||||
return String(txt).replace( | |||||
/&|<|>|"|'|/|`|=/g, | |||||
char => unescape_html_mapping[char] || char | |||||
); | |||||
}, | }, | ||||
html2text: function(html) { | html2text: function(html) { | ||||
@@ -340,7 +340,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { | |||||
options: columns_in_picker | options: columns_in_picker | ||||
}, | }, | ||||
{ | { | ||||
label: __('Insert Column Before {0}', [datatabe_col.docfield.label.bold()]), | |||||
label: __('Insert Column Before {0}', [__(datatabe_col.docfield.label).bold()]), | |||||
fieldname: 'insert_before', | fieldname: 'insert_before', | ||||
fieldtype: 'Check' | fieldtype: 'Check' | ||||
} | } | ||||
@@ -789,7 +789,10 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { | |||||
} else { | } else { | ||||
this.fields.splice(col_index, 0, field); | this.fields.splice(col_index, 0, field); | ||||
} | } | ||||
frappe.show_alert(__('Also adding the dependent currency field {0}', [field[0].bold()])); | |||||
const field_label = frappe.meta.get_label(doctype, field[0]); | |||||
frappe.show_alert( | |||||
__('Also adding the dependent currency field {0}', [__(field_label).bold()]) | |||||
); | |||||
} | } | ||||
} | } | ||||
@@ -799,7 +802,10 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { | |||||
const field = [col, doctype]; | const field = [col, doctype]; | ||||
this.fields.push(field); | this.fields.push(field); | ||||
this.refresh(); | this.refresh(); | ||||
frappe.show_alert(__('Also adding the status dependency field {0}', [field[0].bold()])); | |||||
const field_label = frappe.meta.get_label(doctype, field[0]); | |||||
frappe.show_alert( | |||||
__('Also adding the status dependency field {0}', [__(field_label).bold()]) | |||||
); | |||||
} | } | ||||
} | } | ||||
@@ -7,7 +7,7 @@ export default class Block { | |||||
make(block, block_name, widget_type = block) { | make(block, block_name, widget_type = block) { | ||||
let block_data = this.config.page_data[block+'s'].items.find(obj => { | let block_data = this.config.page_data[block+'s'].items.find(obj => { | ||||
return obj.label == block_name; | |||||
return frappe.utils.unescape_html(obj.label) == frappe.utils.unescape_html(block_name); | |||||
}); | }); | ||||
if (!block_data) return false; | if (!block_data) return false; | ||||
this.wrapper.innerHTML = ''; | this.wrapper.innerHTML = ''; | ||||
@@ -28,12 +28,64 @@ export default class Block { | |||||
return true; | return true; | ||||
} | } | ||||
rendered() { | |||||
var e = this.wrapper.closest('.ce-block'); | |||||
e.classList.add("col-" + this.get_col()); | |||||
rendered(wrapper) { | |||||
if (wrapper) this.wrapper = wrapper; | |||||
!this.readOnly && this.resizer(); | |||||
let block = this.wrapper.closest('.ce-block'); | |||||
this.set_col_class(block, this.get_col()); | |||||
} | |||||
resizer() { | |||||
this.wrapper.className = this.wrapper.className + ' resizable'; | |||||
var resizer = document.createElement('div'); | |||||
resizer.className = 'resizer'; | |||||
this.wrapper.parentElement.appendChild(resizer); | |||||
resizer.addEventListener('mousedown', init_drag, false); | |||||
let me = this; | |||||
var startX, startWidth; | |||||
function init_drag(e) { | |||||
startX = e.clientX; | |||||
startWidth = this.parentElement.offsetWidth; | |||||
document.documentElement.addEventListener('mousemove', do_drag, false); | |||||
document.documentElement.addEventListener('mouseup', stop_drag, false); | |||||
} | |||||
function do_drag(e) { | |||||
$(this).css("cursor", "col-resize"); | |||||
$('.widget').css("pointer-events", "none"); | |||||
$(me.wrapper.parentElement).find('.resizer').css("border-right", "3px solid var(--gray-400)"); | |||||
un_focus(); | |||||
if ((startWidth + e.clientX - startX) - startWidth > 60) { | |||||
startX = e.clientX; | |||||
me.increase_width(); | |||||
} else if ((startWidth + e.clientX - startX) - startWidth < -60) { | |||||
startX = e.clientX; | |||||
me.decrease_width(); | |||||
} | |||||
} | |||||
// disable text selection on mousedown (on drag) | |||||
function un_focus() { | |||||
if (document.selection) { | |||||
document.selection.empty(); | |||||
} else { | |||||
window.getSelection().removeAllRanges(); | |||||
} | |||||
} | |||||
function stop_drag() { | |||||
$(this).css("cursor", "default"); | |||||
$('.widget').css("pointer-events", "auto"); | |||||
$(me.wrapper.parentElement).find('.resizer').css("border-right", "0px solid transparent"); | |||||
document.documentElement.removeEventListener('mousemove', do_drag, false); | |||||
document.documentElement.removeEventListener('mouseup', stop_drag, false); | |||||
} | |||||
} | } | ||||
new(block, widget_type = block) { | new(block, widget_type = block) { | ||||
let me = this; | |||||
const dialog_class = get_dialog_constructor(widget_type); | const dialog_class = get_dialog_constructor(widget_type); | ||||
let block_name = block+'_name'; | let block_name = block+'_name'; | ||||
this.dialog = new dialog_class({ | this.dialog = new dialog_class({ | ||||
@@ -53,13 +105,18 @@ export default class Block { | |||||
}); | }); | ||||
this.block_widget.customize(this.options); | this.block_widget.customize(this.options); | ||||
this.wrapper.setAttribute(block_name, this.block_widget.label); | this.wrapper.setAttribute(block_name, this.block_widget.label); | ||||
$(this.wrapper).find('.widget').addClass(`${widget_type} edit-mode`); | |||||
this.new_block_widget = this.block_widget.get_config(); | this.new_block_widget = this.block_widget.get_config(); | ||||
this.add_tune_button(); | |||||
this.add_settings_button(); | |||||
}, | }, | ||||
}); | }); | ||||
if (!this.readOnly && this.data && !this.data[block_name]) { | if (!this.readOnly && this.data && !this.data[block_name]) { | ||||
this.dialog.make(); | this.dialog.make(); | ||||
this.dialog.dialog.get_close_btn().click(() => { | |||||
me.wrapper.closest('.ce-block').remove(); | |||||
}); | |||||
} | } | ||||
} | } | ||||
@@ -74,42 +131,203 @@ export default class Block { | |||||
this.new_block_widget = block_obj.get_config(); | this.new_block_widget = block_obj.get_config(); | ||||
} | } | ||||
add_tune_button() { | |||||
let $widget_control = $(this.wrapper).find('.widget-control'); | |||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('dot-horizontal', 'xs'), | |||||
(event) => { | |||||
let evn = event; | |||||
!$('.ce-settings.ce-settings--opened').length && | |||||
setTimeout(() => { | |||||
this.api.toolbar.toggleBlockSettings(); | |||||
var position = $(evn.target).offset(); | |||||
$('.ce-settings.ce-settings--opened').offset({ | |||||
top: position.top + 25, | |||||
left: position.left - 77 | |||||
}); | |||||
}, 50); | |||||
add_new_block_button() { | |||||
let $new_button = $(` | |||||
<div class="new-block-button">${frappe.utils.icon('add-round', 'lg')}</div> | |||||
`); | |||||
$new_button.appendTo(this.wrapper); | |||||
$new_button.click(event => { | |||||
event.stopPropagation(); | |||||
let index = this.api.blocks.getCurrentBlockIndex() + 1; | |||||
this.api.blocks.insert('paragraph', {}, {}, index); | |||||
this.api.caret.setToBlock(index); | |||||
}); | |||||
} | |||||
add_settings_button() { | |||||
let me = this; | |||||
this.dropdown_list = [ | |||||
{ | |||||
label: 'Delete', | |||||
title: 'Delete Block', | |||||
icon: frappe.utils.icon('delete-active', 'sm'), | |||||
action: () => this.api.blocks.delete() | |||||
}, | |||||
{ | |||||
label: 'Expand', | |||||
title: 'Expand Block', | |||||
icon: frappe.utils.icon('expand-alt', 'sm'), | |||||
action: () => this.increase_width() | |||||
}, | |||||
{ | |||||
label: 'Shrink', | |||||
title: 'Shrink Block', | |||||
icon: frappe.utils.icon('shrink', 'sm'), | |||||
action: () => this.decrease_width() | |||||
}, | |||||
{ | |||||
label: 'Move Up', | |||||
title: 'Move Up', | |||||
icon: frappe.utils.icon('up-arrow', 'sm'), | |||||
action: () => this.move_block('up') | |||||
}, | }, | ||||
"tune-btn", | |||||
`${__('Tune')}`, | |||||
null, | |||||
$widget_control, | |||||
true | |||||
); | |||||
{ | |||||
label: 'Move Down', | |||||
title: 'Move Down', | |||||
icon: frappe.utils.icon('down-arrow', 'sm'), | |||||
action: () => this.move_block('down') | |||||
} | |||||
]; | |||||
let $widget_control = $(this.wrapper).find('.widget-control'); | |||||
let $button = $(` | |||||
<div class="dropdown-btn"> | |||||
<button class="btn btn-secondary btn-xs setting-btn" title="${__('Setting')}"> | |||||
${frappe.utils.icon('dot-horizontal', 'xs')} | |||||
</button> | |||||
<div class="dropdown-list hidden"></div> | |||||
</div> | |||||
`); | |||||
let dropdown_item = function(label, title, icon, action) { | |||||
let html = $(` | |||||
<div class="dropdown-item" title="${title}"> | |||||
<span class="dropdown-item-icon">${icon}</span> | |||||
<span class="dropdown-item-label">${label}</span> | |||||
</div> | |||||
`); | |||||
html.click(event => { | |||||
event.stopPropagation(); | |||||
action && action(); | |||||
}); | |||||
return html; | |||||
}; | |||||
$button.click(event => { | |||||
event.stopPropagation(); | |||||
$button.find('.dropdown-list').toggleClass('hidden'); | |||||
}); | |||||
$(document).click(() => { | |||||
$button.find('.dropdown-list').addClass('hidden'); | |||||
}); | |||||
$widget_control.prepend($button); | |||||
this.dropdown_list.forEach((item) => { | |||||
if ((item.label == 'Expand' || item.label == 'Shrink') && | |||||
me.options && !me.options.allow_resize) { | |||||
return; | |||||
} | |||||
$button.find('.dropdown-list').append(dropdown_item(item.label, item.title, item.icon, item.action)); | |||||
}); | |||||
} | } | ||||
get_col() { | get_col() { | ||||
let col = this.col || 12; | let col = this.col || 12; | ||||
let class_name = "col-12"; | |||||
let class_name = "col-xs-12"; | |||||
let wrapper = this.wrapper.closest('.ce-block'); | let wrapper = this.wrapper.closest('.ce-block'); | ||||
const col_class = new RegExp(/\bcol-.+?\b/, "g"); | const col_class = new RegExp(/\bcol-.+?\b/, "g"); | ||||
if (wrapper && wrapper.className.match(col_class)) { | if (wrapper && wrapper.className.match(col_class)) { | ||||
wrapper.classList.forEach(function (cn) { | wrapper.classList.forEach(function (cn) { | ||||
cn.match(col_class) && (class_name = cn); | |||||
if (cn.match(col_class)) { | |||||
class_name = cn; | |||||
} | |||||
}); | }); | ||||
let parts = class_name.split("-"); | let parts = class_name.split("-"); | ||||
col = parseInt(parts[1]); | |||||
col = parseInt(parts[2]); | |||||
} | } | ||||
return col; | return col; | ||||
} | } | ||||
decrease_width() { | |||||
this.update_width('decrease'); | |||||
} | |||||
increase_width() { | |||||
this.update_width('increase'); | |||||
} | |||||
update_width(action) { | |||||
let min_width = this.options && this.options.min_width || 3; | |||||
const current_block_index = this.api.blocks.getCurrentBlockIndex(); | |||||
if (current_block_index < 0) { | |||||
return; | |||||
} | |||||
let current_block = this.api.blocks.getBlockByIndex(current_block_index); | |||||
if (!current_block) { | |||||
return; | |||||
} | |||||
const current_block_element = current_block.holder; | |||||
let className = 'col-xs-12'; | |||||
const colClass = new RegExp(/\bcol-.+?\b/, 'g'); | |||||
if (current_block_element.className.match(colClass)) { | |||||
current_block_element.classList.forEach( cn => { | |||||
if (cn.match(colClass)) { | |||||
className = cn; | |||||
} | |||||
}); | |||||
let parts = className.split('-'); | |||||
let width = parseInt(parts[2]); | |||||
let condition = true; | |||||
if (action == 'increase') { | |||||
condition = width <= 11; | |||||
width = width + 1; | |||||
} else if (action == 'decrease') { | |||||
condition = width > min_width; | |||||
width = width - 1; | |||||
} | |||||
if (condition) { | |||||
this.set_col_class(current_block_element, width); | |||||
} | |||||
} | |||||
} | |||||
set_col_class(node, width) { | |||||
let classes = $.grep(node.classList, function (item) { | |||||
return item.indexOf("col-") !== 0; | |||||
}); | |||||
node.classList = ''; | |||||
classes.forEach(cl => { | |||||
node.classList.add(cl); | |||||
}); | |||||
let col = 'col-xs-12'; | |||||
if (width <= 12 && width >= 7) { | |||||
col = 'col-xs-' + width; | |||||
} else if (width == 6 || width == 5) { | |||||
node.classList.add('col-xs-12'); | |||||
col = 'col-sm-' + width; | |||||
} else if (width == 4) { | |||||
node.classList.add('col-xs-12'); | |||||
node.classList.add('col-sm-6'); | |||||
col = 'col-md-' + width; | |||||
} else if (width == 3) { | |||||
node.classList.add('col-xs-12'); | |||||
node.classList.add('col-sm-6'); | |||||
node.classList.add('col-md-4'); | |||||
col = 'col-lg-' + width; | |||||
} | |||||
node.classList.add(col); | |||||
} | |||||
move_block(direction) { | |||||
let current_index = this.api.blocks.getCurrentBlockIndex(); | |||||
let new_index = current_index + (direction == 'down' ? 1 : -1); | |||||
this.api.blocks.move(new_index, current_index); | |||||
} | |||||
} | } |
@@ -3,7 +3,7 @@ export default class Card extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
title: 'Card', | title: 'Card', | ||||
icon: '<svg height="20" width="20" viewBox="2 2 20 20"><path d="M7 15h3a1 1 0 000-2H7a1 1 0 000 2zM19 5H5a3 3 0 00-3 3v9a3 3 0 003 3h14a3 3 0 003-3V8a3 3 0 00-3-3zm1 12a1 1 0 01-1 1H5a1 1 0 01-1-1v-6h16zm0-8H4V8a1 1 0 011-1h14a1 1 0 011 1z"/></svg>' | |||||
icon: frappe.utils.icon('card', 'sm') | |||||
}; | }; | ||||
} | } | ||||
@@ -22,6 +22,7 @@ export default class Card extends Block { | |||||
allow_delete: this.allow_customization, | allow_delete: this.allow_customization, | ||||
allow_hiding: false, | allow_hiding: false, | ||||
allow_edit: true, | allow_edit: true, | ||||
allow_resize: true | |||||
}; | }; | ||||
} | } | ||||
@@ -35,7 +36,9 @@ export default class Card extends Block { | |||||
} | } | ||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
this.add_tune_button(); | |||||
$(this.wrapper).find('.widget').addClass('links edit-mode'); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
} | } | ||||
return this.wrapper; | return this.wrapper; | ||||
@@ -49,9 +52,9 @@ export default class Card extends Block { | |||||
return true; | return true; | ||||
} | } | ||||
save(blockContent) { | |||||
save() { | |||||
return { | return { | ||||
card_name: blockContent.getAttribute('card_name'), | |||||
card_name: this.wrapper.getAttribute('card_name'), | |||||
col: this.get_col(), | col: this.get_col(), | ||||
new: this.new_block_widget | new: this.new_block_widget | ||||
}; | }; | ||||
@@ -3,7 +3,7 @@ export default class Chart extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
title: 'Chart', | title: 'Chart', | ||||
icon: '<svg height="18" width="18" viewBox="0 0 512 512"><path d="M117.547 234.667H10.88c-5.888 0-10.667 4.779-10.667 10.667v256C.213 507.221 4.992 512 10.88 512h106.667c5.888 0 10.667-4.779 10.667-10.667v-256a10.657 10.657 0 00-10.667-10.666zM309.12 0H202.453c-5.888 0-10.667 4.779-10.667 10.667v490.667c0 5.888 4.779 10.667 10.667 10.667H309.12c5.888 0 10.667-4.779 10.667-10.667V10.667C319.787 4.779 315.008 0 309.12 0zM501.12 106.667H394.453c-5.888 0-10.667 4.779-10.667 10.667v384c0 5.888 4.779 10.667 10.667 10.667H501.12c5.888 0 10.667-4.779 10.667-10.667v-384c0-5.889-4.779-10.667-10.667-10.667z"/></svg>' | |||||
icon: frappe.utils.icon('chart', 'sm') | |||||
}; | }; | ||||
} | } | ||||
@@ -21,7 +21,9 @@ export default class Chart extends Block { | |||||
allow_delete: this.allow_customization, | allow_delete: this.allow_customization, | ||||
allow_hiding: false, | allow_hiding: false, | ||||
allow_edit: true, | allow_edit: true, | ||||
max_widget_count: 2, | |||||
allow_resize: true, | |||||
min_width: 6, | |||||
max_widget_count: 2 | |||||
}; | }; | ||||
} | } | ||||
@@ -35,7 +37,9 @@ export default class Chart extends Block { | |||||
} | } | ||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
this.add_tune_button(); | |||||
$(this.wrapper).find('.widget').addClass('chart edit-mode'); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
} | } | ||||
return this.wrapper; | return this.wrapper; | ||||
@@ -49,9 +53,9 @@ export default class Chart extends Block { | |||||
return true; | return true; | ||||
} | } | ||||
save(blockContent) { | |||||
save() { | |||||
return { | return { | ||||
chart_name: blockContent.getAttribute('chart_name'), | |||||
chart_name: this.wrapper.getAttribute('chart_name'), | |||||
col: this.get_col(), | col: this.get_col(), | ||||
new: this.new_block_widget | new: this.new_block_widget | ||||
}; | }; | ||||
@@ -4,16 +4,8 @@ export default class Header extends Block { | |||||
constructor({ data, config, api, readOnly }) { | constructor({ data, config, api, readOnly }) { | ||||
super({ config, api, readOnly }); | super({ config, api, readOnly }); | ||||
this._CSS = { | |||||
block: this.api.styles.block, | |||||
settingsButton: this.api.styles.settingsButton, | |||||
settingsButtonActive: this.api.styles.settingsButtonActive, | |||||
wrapper: 'ce-header', | |||||
}; | |||||
this._settings = this.config; | this._settings = this.config; | ||||
this._data = this.normalizeData(data); | this._data = this.normalizeData(data); | ||||
this.settingsButtons = []; | |||||
this._element = this.getTag(); | this._element = this.getTag(); | ||||
this.data = data; | this.data = data; | ||||
@@ -27,8 +19,7 @@ export default class Header extends Block { | |||||
data = {}; | data = {}; | ||||
} | } | ||||
newData.text = (data.text && __(data.text.replace(/(\n|\t)/gm, ""))) || ''; | |||||
newData.level = parseInt(data.level) || this.defaultLevel.number; | |||||
newData.text = data.text || ''; | |||||
newData.col = parseInt(data.col) || 12; | newData.col = parseInt(data.col) || 12; | ||||
return newData; | return newData; | ||||
@@ -36,7 +27,6 @@ export default class Header extends Block { | |||||
render() { | render() { | ||||
this.wrapper = document.createElement('div'); | this.wrapper = document.createElement('div'); | ||||
this.wrapper.contentEditable = this.readOnly ? 'false' : 'true'; | |||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
let $widget_head = $(`<div class="widget-head"></div>`); | let $widget_head = $(`<div class="widget-head"></div>`); | ||||
let $widget_control = $(`<div class="widget-control"></div>`); | let $widget_control = $(`<div class="widget-control"></div>`); | ||||
@@ -45,27 +35,10 @@ export default class Header extends Block { | |||||
$widget_control.appendTo($widget_head); | $widget_control.appendTo($widget_head); | ||||
$widget_head.appendTo(this.wrapper); | $widget_head.appendTo(this.wrapper); | ||||
this.wrapper.classList.add('widget', 'header'); | |||||
this.wrapper.classList.add('widget', 'header', 'edit-mode'); | |||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('dot-horizontal', 'xs'), | |||||
(event) => { | |||||
let evn = event; | |||||
!$('.ce-settings.ce-settings--opened').length && | |||||
setTimeout(() => { | |||||
this.api.toolbar.toggleBlockSettings(); | |||||
var position = $(evn.target).offset(); | |||||
$('.ce-settings.ce-settings--opened').offset({ | |||||
top: position.top + 25, | |||||
left: position.left - 77 | |||||
}); | |||||
}, 50); | |||||
}, | |||||
"tune-btn", | |||||
`${__('Tune')}`, | |||||
null, | |||||
$widget_control | |||||
); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
frappe.utils.add_custom_button( | frappe.utils.add_custom_button( | ||||
frappe.utils.icon('drag', 'xs'), | frappe.utils.icon('drag', 'xs'), | ||||
@@ -76,67 +49,14 @@ export default class Header extends Block { | |||||
$widget_control | $widget_control | ||||
); | ); | ||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('delete', 'xs'), | |||||
() => this.api.blocks.delete(), | |||||
"delete-header", | |||||
`${__('Delete')}`, | |||||
null, | |||||
$widget_control | |||||
); | |||||
return this.wrapper; | return this.wrapper; | ||||
} | } | ||||
return this._element; | return this._element; | ||||
} | } | ||||
renderSettings() { | |||||
const holder = document.createElement('DIV'); | |||||
if (this.levels.length <= 1) { | |||||
return holder; | |||||
} | |||||
this.levels.forEach(level => { | |||||
const selectTypeButton = document.createElement('SPAN'); | |||||
selectTypeButton.classList.add(this._CSS.settingsButton); | |||||
if (this.currentLevel.number === level.number) { | |||||
selectTypeButton.classList.add(this._CSS.settingsButtonActive); | |||||
} | |||||
selectTypeButton.innerHTML = level.svg; | |||||
selectTypeButton.dataset.level = level.number; | |||||
selectTypeButton.addEventListener('click', () => { | |||||
this.setLevel(level.number); | |||||
}); | |||||
holder.appendChild(selectTypeButton); | |||||
this.settingsButtons.push(selectTypeButton); | |||||
}); | |||||
return holder; | |||||
} | |||||
setLevel(level) { | |||||
this.data = { | |||||
level: level, | |||||
text: this.data.text, | |||||
}; | |||||
this.settingsButtons.forEach(button => { | |||||
button.classList.toggle(this._CSS.settingsButtonActive, parseInt(button.dataset.level) === level); | |||||
}); | |||||
} | |||||
merge(data) { | merge(data) { | ||||
const newData = { | const newData = { | ||||
text: this.data.text + data.text, | |||||
level: this.data.level, | |||||
text: this.data.text + data.text | |||||
}; | }; | ||||
this.data = newData; | this.data = newData; | ||||
@@ -146,31 +66,28 @@ export default class Header extends Block { | |||||
return blockData.text.trim() !== ''; | return blockData.text.trim() !== ''; | ||||
} | } | ||||
save(toolsContent) { | |||||
save() { | |||||
this.wrapper = this._element; | this.wrapper = this._element; | ||||
return { | return { | ||||
text: toolsContent.innerText, | |||||
level: this.currentLevel.number, | |||||
text: this.wrapper.innerHTML.replace(/ /gi, ''), | |||||
col: this.get_col() | col: this.get_col() | ||||
}; | }; | ||||
} | } | ||||
rendered() { | rendered() { | ||||
var e = this._element.closest('.ce-block'); | |||||
e.classList.add("col-" + this.get_col()); | |||||
} | |||||
static get conversionConfig() { | |||||
return { | |||||
export: 'text', // use 'text' property for other blocks | |||||
import: 'text', // fill 'text' property from other block's export string | |||||
}; | |||||
super.rendered(this._element); | |||||
} | } | ||||
static get sanitize() { | static get sanitize() { | ||||
return { | return { | ||||
level: false, | level: false, | ||||
text: {}, | |||||
text: { | |||||
br: true, | |||||
b: true, | |||||
i: true, | |||||
a: true, | |||||
span: true | |||||
}, | |||||
}; | }; | ||||
} | } | ||||
@@ -180,7 +97,6 @@ export default class Header extends Block { | |||||
get data() { | get data() { | ||||
this._data.text = this._element.innerHTML; | this._data.text = this._element.innerHTML; | ||||
this._data.level = this.currentLevel.number; | |||||
return this._data; | return this._data; | ||||
} | } | ||||
@@ -188,15 +104,11 @@ export default class Header extends Block { | |||||
set data(data) { | set data(data) { | ||||
this._data = this.normalizeData(data); | this._data = this.normalizeData(data); | ||||
if (data.level !== undefined && this._element.parentNode) { | |||||
const newHeader = this.getTag(); | |||||
newHeader.innerHTML = this._element.innerHTML; | |||||
this._element.parentNode.replaceChild(newHeader, this._element); | |||||
this._element = newHeader; | |||||
} | |||||
if (data.text !== undefined) { | if (data.text !== undefined) { | ||||
this._element.innerHTML = this._data.text || ''; | |||||
let text = this._data.text || ''; | |||||
const contains_html_tag = /<[a-z][\s\S]*>/i.test(text); | |||||
this._element.innerHTML = contains_html_tag ? | |||||
text : `<span class="h${this._settings.default_size}">${text}</span>`; | |||||
} | } | ||||
if (!this.readOnly && this.wrapper) { | if (!this.readOnly && this.wrapper) { | ||||
@@ -205,11 +117,12 @@ export default class Header extends Block { | |||||
} | } | ||||
getTag() { | getTag() { | ||||
const tag = document.createElement(this.currentLevel.tag); | |||||
const tag = document.createElement('DIV'); | |||||
tag.innerHTML = this._data.text || ''; | |||||
let text = this._data.text || ' '; | |||||
tag.innerHTML = `<span class="h${this._settings.default_size}"><b>${text}</b></span>`; | |||||
tag.classList.add(this._CSS.wrapper); | |||||
tag.classList.add('ce-header'); | |||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
tag.contentEditable = true; | tag.contentEditable = true; | ||||
@@ -220,120 +133,10 @@ export default class Header extends Block { | |||||
return tag; | return tag; | ||||
} | } | ||||
get currentLevel() { | |||||
let level = this.levels.find(levelItem => levelItem.number === this._data.level); | |||||
if (!level) { | |||||
level = this.defaultLevel; | |||||
} | |||||
return level; | |||||
} | |||||
get defaultLevel() { | |||||
if (this._settings.defaultLevel) { | |||||
const userSpecified = this.levels.find(levelItem => { | |||||
return levelItem.number === this._settings.defaultLevel; | |||||
}); | |||||
if (userSpecified) { | |||||
return userSpecified; | |||||
} else { | |||||
// console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels'); | |||||
} | |||||
} | |||||
return this.levels[1]; | |||||
} | |||||
get levels() { | |||||
const availableLevels = [ | |||||
{ | |||||
number: 1, | |||||
tag: 'H1', | |||||
svg: '<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.14 1.494V4.98h4.62V1.494c0-.498.098-.871.293-1.12A.927.927 0 0 1 7.82 0c.322 0 .583.123.782.37.2.246.3.62.3 1.124v9.588c0 .503-.101.88-.303 1.128a.957.957 0 0 1-.779.374.921.921 0 0 1-.77-.378c-.193-.251-.29-.626-.29-1.124V6.989H2.14v4.093c0 .503-.1.88-.302 1.128a.957.957 0 0 1-.778.374.921.921 0 0 1-.772-.378C.096 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.285.374A.922.922 0 0 1 1.06 0c.321 0 .582.123.782.37.199.246.299.62.299 1.124zm11.653 9.985V5.27c-1.279.887-2.14 1.33-2.583 1.33a.802.802 0 0 1-.563-.228.703.703 0 0 1-.245-.529c0-.232.08-.402.241-.511.161-.11.446-.25.854-.424.61-.259 1.096-.532 1.462-.818a5.84 5.84 0 0 0 .97-.962c.282-.355.466-.573.552-.655.085-.082.246-.123.483-.123.267 0 .481.093.642.28.161.186.242.443.242.77v7.813c0 .914-.345 1.371-1.035 1.371-.307 0-.554-.093-.74-.28-.187-.186-.28-.461-.28-.825z"/></svg>', | |||||
}, | |||||
{ | |||||
number: 2, | |||||
tag: 'H2', | |||||
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm10.99 9.288h3.527c.351 0 .62.072.804.216.185.144.277.34.277.588 0 .22-.073.408-.22.56-.146.154-.368.23-.665.23h-4.972c-.338 0-.601-.093-.79-.28a.896.896 0 0 1-.284-.659c0-.162.06-.377.182-.645s.255-.478.399-.631a38.617 38.617 0 0 1 1.621-1.598c.482-.444.827-.735 1.034-.875.369-.261.676-.523.922-.787.245-.263.432-.534.56-.81.129-.278.193-.549.193-.815 0-.288-.069-.546-.206-.773a1.428 1.428 0 0 0-.56-.53 1.618 1.618 0 0 0-.774-.19c-.59 0-1.054.26-1.392.777-.045.068-.12.252-.226.554-.106.302-.225.534-.358.696-.133.162-.328.243-.585.243a.76.76 0 0 1-.56-.223c-.149-.148-.223-.351-.223-.608 0-.31.07-.635.21-.972.139-.338.347-.645.624-.92a3.093 3.093 0 0 1 1.054-.665c.426-.169.924-.253 1.496-.253.69 0 1.277.108 1.764.324.315.144.592.343.83.595.24.252.425.544.558.875.133.33.2.674.2 1.03 0 .558-.14 1.066-.416 1.523-.277.457-.56.815-.848 1.074-.288.26-.771.666-1.45 1.22-.677.554-1.142.984-1.394 1.29a3.836 3.836 0 0 0-.331.44z"/></svg>', | |||||
}, | |||||
{ | |||||
number: 3, | |||||
tag: 'H3', | |||||
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm11.61 4.919c.418 0 .778-.123 1.08-.368.301-.245.452-.597.452-1.055 0-.35-.12-.65-.36-.902-.241-.252-.566-.378-.974-.378-.277 0-.505.038-.684.116a1.1 1.1 0 0 0-.426.306 2.31 2.31 0 0 0-.296.49c-.093.2-.178.388-.255.565a.479.479 0 0 1-.245.225.965.965 0 0 1-.409.081.706.706 0 0 1-.5-.22c-.152-.148-.228-.345-.228-.59 0-.236.071-.484.214-.745a2.72 2.72 0 0 1 .627-.746 3.149 3.149 0 0 1 1.024-.568 4.122 4.122 0 0 1 1.368-.214c.44 0 .842.06 1.205.18.364.12.679.294.947.52.267.228.47.49.606.79.136.3.204.622.204.967 0 .454-.099.843-.296 1.168-.198.324-.48.64-.848.95.354.19.653.408.895.653.243.245.426.516.548.813.123.298.184.619.184.964 0 .413-.083.812-.248 1.198-.166.386-.41.73-.732 1.031a3.49 3.49 0 0 1-1.147.708c-.443.17-.932.256-1.467.256a3.512 3.512 0 0 1-1.464-.293 3.332 3.332 0 0 1-1.699-1.64c-.142-.314-.214-.573-.214-.777 0-.263.085-.475.255-.636a.89.89 0 0 1 .637-.242c.127 0 .25.037.367.112a.53.53 0 0 1 .232.27c.236.63.489 1.099.759 1.405.27.306.65.46 1.14.46a1.714 1.714 0 0 0 1.46-.824c.17-.273.256-.588.256-.947 0-.53-.145-.947-.436-1.249-.29-.302-.694-.453-1.212-.453-.09 0-.231.01-.422.028-.19.018-.313.027-.367.027-.25 0-.443-.062-.579-.187-.136-.125-.204-.299-.204-.521 0-.218.081-.394.245-.528.163-.134.406-.2.728-.2h.28z"/></svg>', | |||||
}, | |||||
{ | |||||
number: 4, | |||||
tag: 'H4', | |||||
svg: '<svg width="20" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm13.003 10.09v-1.252h-3.38c-.427 0-.746-.097-.96-.29-.213-.193-.32-.456-.32-.788 0-.085.016-.171.048-.259.031-.088.078-.18.141-.276.063-.097.128-.19.195-.28.068-.09.15-.2.25-.33l3.568-4.774a5.44 5.44 0 0 1 .576-.683.763.763 0 0 1 .542-.212c.682 0 1.023.39 1.023 1.171v5.212h.29c.346 0 .623.047.832.142.208.094.313.3.313.62 0 .26-.086.45-.256.568-.17.12-.427.179-.768.179h-.41v1.252c0 .346-.077.603-.23.771-.152.168-.356.253-.612.253a.78.78 0 0 1-.61-.26c-.154-.173-.232-.427-.232-.764zm-2.895-2.76h2.895V4.91L12.26 8.823z"/></svg>', | |||||
}, | |||||
{ | |||||
number: 5, | |||||
tag: 'H5', | |||||
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm14.16 2.645h-3.234l-.388 2.205c.644-.344 1.239-.517 1.783-.517.436 0 .843.082 1.222.245.38.164.712.39.998.677.286.289.51.63.674 1.025.163.395.245.82.245 1.273 0 .658-.148 1.257-.443 1.797-.295.54-.72.97-1.276 1.287-.556.318-1.197.477-1.923.477-.813 0-1.472-.15-1.978-.45-.506-.3-.865-.643-1.076-1.031-.21-.388-.316-.727-.316-1.018 0-.177.073-.345.22-.504a.725.725 0 0 1 .556-.238c.381 0 .665.22.85.66.182.404.427.719.736.943.309.225.654.337 1.035.337.35 0 .656-.09.919-.272.263-.182.466-.431.61-.749.142-.318.214-.678.214-1.082 0-.436-.078-.808-.232-1.117a1.607 1.607 0 0 0-.62-.69 1.674 1.674 0 0 0-.864-.229c-.39 0-.67.048-.837.143-.168.095-.41.262-.725.5-.316.239-.576.358-.78.358a.843.843 0 0 1-.592-.242c-.173-.16-.259-.344-.259-.548 0-.022.025-.177.075-.463l.572-3.26c.063-.39.181-.675.354-.852.172-.177.454-.265.844-.265h3.595c.708 0 1.062.27 1.062.81a.711.711 0 0 1-.26.572c-.172.145-.426.218-.762.218z"/></svg>', | |||||
}, | |||||
{ | |||||
number: 6, | |||||
tag: 'H6', | |||||
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zM12.53 7.058a3.093 3.093 0 0 1 1.004-.814 2.734 2.734 0 0 1 1.214-.264c.43 0 .827.08 1.19.24.365.161.684.39.957.686.274.296.485.645.635 1.048a3.6 3.6 0 0 1 .223 1.262c0 .637-.145 1.216-.437 1.736-.292.52-.699.926-1.221 1.218-.522.292-1.114.438-1.774.438-.76 0-1.416-.186-1.967-.557-.552-.37-.974-.919-1.265-1.645-.292-.726-.438-1.613-.438-2.662 0-.855.088-1.62.265-2.293.176-.674.43-1.233.76-1.676.33-.443.73-.778 1.2-1.004.47-.226 1.006-.339 1.608-.339.579 0 1.089.113 1.53.34.44.225.773.506.997.84.224.335.335.656.335.964 0 .185-.07.354-.21.505a.698.698 0 0 1-.536.227.874.874 0 0 1-.529-.18 1.039 1.039 0 0 1-.36-.498 1.42 1.42 0 0 0-.495-.655 1.3 1.3 0 0 0-.786-.247c-.24 0-.479.069-.716.207a1.863 1.863 0 0 0-.6.56c-.33.479-.525 1.333-.584 2.563zm1.832 4.213c.456 0 .834-.186 1.133-.56.298-.373.447-.862.447-1.468 0-.412-.07-.766-.21-1.062a1.584 1.584 0 0 0-.577-.678 1.47 1.47 0 0 0-.807-.234c-.28 0-.548.074-.804.224-.255.149-.461.365-.617.647a2.024 2.024 0 0 0-.234.994c0 .61.158 1.12.475 1.527.316.407.714.61 1.194.61z"/></svg>', | |||||
}, | |||||
]; | |||||
return this._settings.levels ? availableLevels.filter( | |||||
l => this._settings.levels.includes(l.number) | |||||
) : availableLevels; | |||||
} | |||||
onPaste(event) { | |||||
const content = event.detail.data; | |||||
let level = this.defaultLevel.number; | |||||
switch (content.tagName) { | |||||
case 'H1': | |||||
level = 1; | |||||
break; | |||||
case 'H2': | |||||
level = 2; | |||||
break; | |||||
case 'H3': | |||||
level = 3; | |||||
break; | |||||
case 'H4': | |||||
level = 4; | |||||
break; | |||||
case 'H5': | |||||
level = 5; | |||||
break; | |||||
case 'H6': | |||||
level = 6; | |||||
break; | |||||
} | |||||
if (this._settings.levels) { | |||||
// Fallback to nearest level when specified not available | |||||
level = this._settings.levels.reduce((prevLevel, currLevel) => { | |||||
return Math.abs(currLevel - level) < Math.abs(prevLevel - level) ? currLevel : prevLevel; | |||||
}); | |||||
} | |||||
this.data = { | |||||
level, | |||||
text: content.innerHTML, | |||||
}; | |||||
} | |||||
static get pasteConfig() { | |||||
return { | |||||
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'], | |||||
}; | |||||
} | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
icon: '<svg width="10" height="14" viewBox="0 0 10 14"><path d="M7.6 8.15H2.25v4.525a1.125 1.125 0 0 1-2.25 0V1.125a1.125 1.125 0 1 1 2.25 0V5.9H7.6V1.125a1.125 1.125 0 0 1 2.25 0v11.55a1.125 1.125 0 0 1-2.25 0V8.15z"></path></svg>', | |||||
title: 'Heading', | title: 'Heading', | ||||
icon: frappe.utils.icon('header', 'sm') | |||||
}; | }; | ||||
} | } | ||||
} | } |
@@ -0,0 +1,117 @@ | |||||
export default class HeaderSize { | |||||
static get isInline() { | |||||
return true; | |||||
} | |||||
get state() { | |||||
return this._state; | |||||
} | |||||
set state(state) { | |||||
this._state = state; | |||||
} | |||||
get title() { | |||||
return 'Header Size'; | |||||
} | |||||
constructor({api}) { | |||||
this.api = api; | |||||
this.button = null; | |||||
this._state = true; | |||||
this.selectedText = null; | |||||
this.range = null; | |||||
this.headerLevels = []; | |||||
} | |||||
render() { | |||||
this.button = document.createElement('button'); | |||||
this.button.type = 'button'; | |||||
this.button.innerHTML = `${frappe.utils.icon('header', 'sm')}${frappe.utils.icon('small-down', 'xs')}`; | |||||
this.button.classList = 'header-inline-tool'; | |||||
return this.button; | |||||
} | |||||
checkState(selection) { | |||||
let termWrapper = this.api.selection.findParentTag('SPAN'); | |||||
for (const h of ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) { | |||||
if (termWrapper && termWrapper.classList.contains(h)) { | |||||
let num = h.match(/\d+/)[0]; | |||||
$('.header-inline-tool svg:first-child').replaceWith(frappe.utils.icon(`header-${num}`, 'md')); | |||||
} | |||||
} | |||||
const text = selection.anchorNode; | |||||
if (!text) return; | |||||
} | |||||
change_size(range, size) { | |||||
if (!range) return; | |||||
let span = document.createElement('SPAN'); | |||||
span.classList.add(`h${size}`); | |||||
span.innerText = range.toString(); | |||||
this.remove_parent_tag(range, range.commonAncestorContainer, span); | |||||
range.extractContents(); | |||||
range.insertNode(span); | |||||
this.api.inlineToolbar.close(); | |||||
} | |||||
remove_parent_tag(range, parent_node, span) { | |||||
let diff = range.startContainer.data; | |||||
let selected_text = span.innerText; | |||||
let parent_tag = parent_node.parentElement; | |||||
if (diff !== selected_text) { | |||||
parent_tag = parent_node; | |||||
} | |||||
if (parent_tag.innerText == selected_text) { | |||||
if (!parent_tag.classList.contains('ce-header') && !parent_tag.classList.contains('ce-paragraph')) { | |||||
this.remove_parent_tag(range, parent_node.parentElement, span); | |||||
parent_tag.remove(); | |||||
} | |||||
} | |||||
} | |||||
surround(range) { | |||||
this.selectedText = range.cloneContents(); | |||||
this.actions.hidden = !this.actions.hidden; | |||||
this.range = !this.actions.hidden ? range : null; | |||||
this.state = !this.actions.hidden; | |||||
} | |||||
renderActions() { | |||||
this.actions = document.createElement('div'); | |||||
this.actions.classList = 'header-level-select'; | |||||
this.headerLevels = new Array(6).fill().map((_, idx) => { | |||||
const $header_level = document.createElement('div'); | |||||
$header_level.classList.add(`h${idx+1}`, 'header-level'); | |||||
$header_level.innerText = `Header ${idx+1}`; | |||||
return $header_level; | |||||
}); | |||||
for (const [i, headerLevel] of this.headerLevels.entries()) { | |||||
this.actions.appendChild(headerLevel); | |||||
this.api.listeners.on(headerLevel, 'click', () => { | |||||
this.change_size(this.range, i+1); | |||||
}); | |||||
} | |||||
this.actions.hidden = true; | |||||
return this.actions; | |||||
} | |||||
destroy() { | |||||
for (const headerLevel of this.headerLevels) { | |||||
this.api.listeners.off(headerLevel, 'click'); | |||||
} | |||||
} | |||||
} |
@@ -8,11 +8,11 @@ import Spacer from "./spacer"; | |||||
import Onboarding from "./onboarding"; | import Onboarding from "./onboarding"; | ||||
// import tunes | // import tunes | ||||
import SpacingTune from "./spacing_tune"; | |||||
import HeaderSize from "./header_size"; | |||||
frappe.provide("frappe.wspace_block"); | |||||
frappe.provide("frappe.workspace_block"); | |||||
frappe.wspace_block.blocks = { | |||||
frappe.workspace_block.blocks = { | |||||
header: Header, | header: Header, | ||||
paragraph: Paragraph, | paragraph: Paragraph, | ||||
card: Card, | card: Card, | ||||
@@ -22,6 +22,6 @@ frappe.wspace_block.blocks = { | |||||
onboarding: Onboarding, | onboarding: Onboarding, | ||||
}; | }; | ||||
frappe.wspace_block.tunes = { | |||||
spacing_tune: SpacingTune | |||||
frappe.workspace_block.tunes = { | |||||
header_size: HeaderSize, | |||||
}; | }; |
@@ -4,7 +4,7 @@ export default class Onboarding extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
title: 'Onboarding', | title: 'Onboarding', | ||||
icon: '<svg width="24" height="24" viewBox="2 0 20 24" fill="none"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 11.09v5.455" stroke="#1F272E" fill="none"/><path d="M12.41 7.455a.41.41 0 11-.82 0 .41.41 0 01.82 0z" stroke="#1F272E"/></svg>' | |||||
icon: frappe.utils.icon('onboarding', 'sm') | |||||
}; | }; | ||||
} | } | ||||
@@ -21,19 +21,21 @@ export default class Onboarding extends Block { | |||||
allow_create: this.allow_customization, | allow_create: this.allow_customization, | ||||
allow_delete: this.allow_customization, | allow_delete: this.allow_customization, | ||||
allow_hiding: false, | allow_hiding: false, | ||||
allow_edit: true | |||||
allow_edit: true, | |||||
allow_resize: false | |||||
}; | }; | ||||
} | } | ||||
rendered() { | rendered() { | ||||
var e = this.wrapper.closest('.ce-block'); | |||||
let block = this.wrapper.closest('.ce-block'); | |||||
if (this.readOnly && !$(this.wrapper).find('.onboarding-widget-box').is(':visible')) { | if (this.readOnly && !$(this.wrapper).find('.onboarding-widget-box').is(':visible')) { | ||||
$(e).hide(); | |||||
$(block).hide(); | |||||
} | } | ||||
e.classList.add("col-" + this.get_col()); | |||||
this.set_col_class(block, this.get_col()); | |||||
} | } | ||||
new(block, widget_type = block) { | new(block, widget_type = block) { | ||||
let me = this; | |||||
const dialog_class = get_dialog_constructor(widget_type); | const dialog_class = get_dialog_constructor(widget_type); | ||||
let block_name = block+'_name'; | let block_name = block+'_name'; | ||||
this.dialog = new dialog_class({ | this.dialog = new dialog_class({ | ||||
@@ -54,13 +56,18 @@ export default class Onboarding extends Block { | |||||
}); | }); | ||||
this.block_widget.customize(this.options); | this.block_widget.customize(this.options); | ||||
this.wrapper.setAttribute(block_name, this.block_widget.label || this.block_widget.onboarding_name); | this.wrapper.setAttribute(block_name, this.block_widget.label || this.block_widget.onboarding_name); | ||||
$(this.wrapper).find('.widget').addClass(`${widget_type} edit-mode`); | |||||
this.new_block_widget = this.block_widget.get_config(); | this.new_block_widget = this.block_widget.get_config(); | ||||
this.add_tune_button(); | |||||
this.add_settings_button(); | |||||
}, | }, | ||||
}); | }); | ||||
if (!this.readOnly && this.data && !this.data[block_name]) { | if (!this.readOnly && this.data && !this.data[block_name]) { | ||||
this.dialog.make(); | this.dialog.make(); | ||||
this.dialog.dialog.get_close_btn().click(() => { | |||||
me.wrapper.closest('.ce-block').remove(); | |||||
}); | |||||
} | } | ||||
} | } | ||||
@@ -105,7 +112,9 @@ export default class Onboarding extends Block { | |||||
} | } | ||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
this.add_tune_button(); | |||||
$(this.wrapper).find('.widget').addClass('onboarding edit-mode'); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
} | } | ||||
$(this.wrapper).css("padding-bottom", "20px"); | $(this.wrapper).css("padding-bottom", "20px"); | ||||
return this.wrapper; | return this.wrapper; | ||||
@@ -119,9 +128,9 @@ export default class Onboarding extends Block { | |||||
return true; | return true; | ||||
} | } | ||||
save(blockContent) { | |||||
save() { | |||||
return { | return { | ||||
onboarding_name: blockContent.getAttribute('onboarding_name'), | |||||
onboarding_name: this.wrapper.getAttribute('onboarding_name'), | |||||
col: this.get_col(), | col: this.get_col(), | ||||
new: this.new_block_widget | new: this.new_block_widget | ||||
}; | }; | ||||
@@ -27,6 +27,8 @@ export default class Paragraph extends Block { | |||||
} | } | ||||
onKeyUp(e) { | onKeyUp(e) { | ||||
if (!this.wrapper) return; | |||||
this.show_hide_block_list(true); | |||||
if (e.code !== 'Backspace' && e.code !== 'Delete') { | if (e.code !== 'Backspace' && e.code !== 'Delete') { | ||||
return; | return; | ||||
} | } | ||||
@@ -34,55 +36,86 @@ export default class Paragraph extends Block { | |||||
const {textContent} = this._element; | const {textContent} = this._element; | ||||
if (textContent === '') { | if (textContent === '') { | ||||
this.show_hide_block_list(); | |||||
this._element.innerHTML = ''; | this._element.innerHTML = ''; | ||||
} | } | ||||
} | } | ||||
show_hide_block_list(hide) { | |||||
let $wrapper = $(this.wrapper).hasClass('ce-paragraph') ? $(this.wrapper.parentElement) : $(this.wrapper); | |||||
let $block_list_container = $wrapper.find('.block-list-container.dropdown-list'); | |||||
$block_list_container.removeClass('hidden'); | |||||
hide && $block_list_container.addClass('hidden'); | |||||
} | |||||
drawView() { | drawView() { | ||||
let div = document.createElement('DIV'); | let div = document.createElement('DIV'); | ||||
div.classList.add(this._CSS.wrapper, this._CSS.block, 'widget'); | div.classList.add(this._CSS.wrapper, this._CSS.block, 'widget'); | ||||
div.contentEditable = false; | div.contentEditable = false; | ||||
div.dataset.placeholder = this.api.i18n.t(this._placeholder); | |||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
div.contentEditable = true; | div.contentEditable = true; | ||||
div.addEventListener('focus', () => { | |||||
const {textContent} = this._element; | |||||
if (textContent !== '') return; | |||||
this.show_hide_block_list(); | |||||
}); | |||||
div.addEventListener('blur', () => { | |||||
setTimeout(() => this.show_hide_block_list(true), 10); | |||||
}); | |||||
div.dataset.placeholder = this.api.i18n.t(this._placeholder); | |||||
div.addEventListener('keyup', this.onKeyUp); | div.addEventListener('keyup', this.onKeyUp); | ||||
} | } | ||||
return div; | return div; | ||||
} | } | ||||
open_block_list() { | |||||
let dropdown_title = 'Templates'; | |||||
let $block_list_container = $(` | |||||
<div class="block-list-container dropdown-list"> | |||||
<div class="dropdown-title">${dropdown_title.toUpperCase()}</div> | |||||
</div> | |||||
`); | |||||
let all_blocks = frappe.workspace_block.blocks; | |||||
Object.keys(all_blocks).forEach(key => { | |||||
let $block_list_item = $(` | |||||
<div class="block-list-item dropdown-item"> | |||||
<span class="dropdown-item-icon">${all_blocks[key].toolbox.icon}</span> | |||||
<span class="dropdown-item-label">${__(all_blocks[key].toolbox.title)}</span> | |||||
</div> | |||||
`); | |||||
$block_list_item.click(event => { | |||||
event.stopPropagation(); | |||||
const index = this.api.blocks.getCurrentBlockIndex(); | |||||
this.api.blocks.delete(); | |||||
this.api.blocks.insert(key, {}, {}, index); | |||||
this.api.caret.setToBlock(index); | |||||
}); | |||||
$block_list_container.append($block_list_item); | |||||
}); | |||||
$block_list_container.addClass('hidden'); | |||||
$block_list_container.appendTo(this.wrapper); | |||||
} | |||||
render() { | render() { | ||||
this.wrapper = document.createElement('div'); | this.wrapper = document.createElement('div'); | ||||
this.wrapper.contentEditable = this.readOnly ? 'false' : 'true'; | |||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
let $para_control = $(`<div class="paragraph-control"></div>`); | |||||
let $para_control = $(`<div class="widget-control paragraph-control"></div>`); | |||||
this.wrapper.appendChild(this._element); | this.wrapper.appendChild(this._element); | ||||
this._element.classList.remove('widget'); | this._element.classList.remove('widget'); | ||||
$para_control.appendTo(this.wrapper); | $para_control.appendTo(this.wrapper); | ||||
this.wrapper.classList.add('widget'); | |||||
this.wrapper.classList.add('widget', 'paragraph', 'edit-mode'); | |||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('dot-horizontal', 'xs'), | |||||
(event) => { | |||||
let evn = event; | |||||
!$('.ce-settings.ce-settings--opened').length && | |||||
setTimeout(() => { | |||||
this.api.toolbar.toggleBlockSettings(); | |||||
var position = $(evn.target).offset(); | |||||
$('.ce-settings.ce-settings--opened').offset({ | |||||
top: position.top + 25, | |||||
left: position.left - 77 | |||||
}); | |||||
}, 50); | |||||
}, | |||||
"tune-btn", | |||||
`${__('Tune')}`, | |||||
null, | |||||
$para_control | |||||
); | |||||
this.open_block_list(); | |||||
this.add_new_block_button(); | |||||
this.add_settings_button(); | |||||
frappe.utils.add_custom_button( | frappe.utils.add_custom_button( | ||||
frappe.utils.icon('drag', 'xs'), | frappe.utils.icon('drag', 'xs'), | ||||
@@ -93,15 +126,6 @@ export default class Paragraph extends Block { | |||||
$para_control | $para_control | ||||
); | ); | ||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('delete', 'xs'), | |||||
() => this.api.blocks.delete(), | |||||
"delete-paragraph", | |||||
`${__('Delete')}`, | |||||
null, | |||||
$para_control | |||||
); | |||||
return this.wrapper; | return this.wrapper; | ||||
} | } | ||||
return this._element; | return this._element; | ||||
@@ -132,8 +156,7 @@ export default class Paragraph extends Block { | |||||
} | } | ||||
rendered() { | rendered() { | ||||
var e = this._element.closest('.ce-block'); | |||||
e.classList.add("col-" + this.get_col()); | |||||
super.rendered(this._element); | |||||
} | } | ||||
onPaste(event) { | onPaste(event) { | ||||
@@ -144,20 +167,14 @@ export default class Paragraph extends Block { | |||||
this.data = data; | this.data = data; | ||||
} | } | ||||
static get conversionConfig() { | |||||
return { | |||||
export: 'text', // to convert Paragraph to other block, use 'text' property of saved data | |||||
import: 'text' // to covert other block's exported string to Paragraph, fill 'text' property of tool data | |||||
}; | |||||
} | |||||
static get sanitize() { | static get sanitize() { | ||||
return { | return { | ||||
text: { | text: { | ||||
br: true, | br: true, | ||||
b: true, | b: true, | ||||
i: true, | i: true, | ||||
a: true | |||||
a: true, | |||||
span: true | |||||
} | } | ||||
}; | }; | ||||
} | } | ||||
@@ -188,8 +205,8 @@ export default class Paragraph extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
icon: '<svg viewBox="0.2 -0.3 9 11.4" width="12" height="14"><path d="M0 2.77V.92A1 1 0 01.2.28C.35.1.56 0 .83 0h7.66c.28.01.48.1.63.28.14.17.21.38.21.64v1.85c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26a1 1 0 01-.21-.66V1.69H5.6v7.58h.5c.25 0 .45.08.6.23.17.16.25.35.25.6s-.08.45-.24.6a.87.87 0 01-.62.22H3.21a.87.87 0 01-.61-.22.78.78 0 01-.24-.6c0-.25.08-.44.24-.6a.85.85 0 01.61-.23h.5V1.7H1.73v1.08c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26A1 1 0 010 2.77z"/></svg>', | |||||
title: 'Text' | |||||
title: 'Text', | |||||
icon: frappe.utils.icon('text', 'sm') | |||||
}; | }; | ||||
} | } | ||||
} | } |
@@ -3,7 +3,7 @@ export default class Shortcut extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
title: 'Shortcut', | title: 'Shortcut', | ||||
icon: '<svg height="18" width="18" viewBox="0 0 122.88 115.71"><path d="M116.56 3.69l-3.84 53.76-17.69-15c-19.5 8.72-29.96 23.99-30.51 43.77-17.95-26.98-7.46-50.4 12.46-65.97L64.96 3l51.6.69zM28.3 0h14.56v19.67H32.67c-4.17 0-7.96 1.71-10.72 4.47-2.75 2.75-4.46 6.55-4.46 10.72l-.03 46c.03 4.16 1.75 7.95 4.5 10.71 2.76 2.76 6.56 4.48 10.71 4.48h58.02c4.15 0 7.95-1.72 10.71-4.48 2.76-2.76 4.48-6.55 4.48-10.71V73.9h17.01v11.33c0 7.77-3.2 17.04-8.32 22.16-5.12 5.12-12.21 8.32-19.98 8.32H28.3c-7.77 0-14.86-3.2-19.98-8.32C3.19 102.26 0 95.18 0 87.41l.03-59.1c-.03-7.79 3.16-14.88 8.28-20C13.43 3.19 20.51 0 28.3 0z" fill-rule="evenodd" clip-rule="evenodd"/></svg>' | |||||
icon: frappe.utils.icon('shortcut', 'sm') | |||||
}; | }; | ||||
} | } | ||||
@@ -13,17 +13,39 @@ export default class Shortcut extends Block { | |||||
constructor({ data, api, config, readOnly, block }) { | constructor({ data, api, config, readOnly, block }) { | ||||
super({ data, api, config, readOnly, block }); | super({ data, api, config, readOnly, block }); | ||||
this.col = this.data.col ? this.data.col : "4"; | |||||
this.col = this.data.col ? this.data.col : "3"; | |||||
this.allow_customization = !this.readOnly; | this.allow_customization = !this.readOnly; | ||||
this.options = { | this.options = { | ||||
allow_sorting: this.allow_customization, | allow_sorting: this.allow_customization, | ||||
allow_create: this.allow_customization, | allow_create: this.allow_customization, | ||||
allow_delete: this.allow_customization, | allow_delete: this.allow_customization, | ||||
allow_hiding: false, | allow_hiding: false, | ||||
allow_edit: true | |||||
allow_edit: true, | |||||
allow_resize: true | |||||
}; | }; | ||||
} | } | ||||
rendered() { | |||||
super.rendered(); | |||||
this.remove_last_divider(); | |||||
$(window).resize(() => { | |||||
this.remove_last_divider(); | |||||
}); | |||||
} | |||||
remove_last_divider() { | |||||
let block = this.wrapper.closest('.ce-block'); | |||||
let container_offset_right = $('.layout-main-section')[0].offsetWidth; | |||||
let block_offset_right = block.offsetLeft + block.offsetWidth; | |||||
if (container_offset_right - block_offset_right <= 110) { | |||||
$(block).find('.divider').addClass('hidden'); | |||||
} else { | |||||
$(block).find('.divider').removeClass('hidden'); | |||||
} | |||||
} | |||||
render() { | render() { | ||||
this.wrapper = document.createElement('div'); | this.wrapper = document.createElement('div'); | ||||
this.new('shortcut'); | this.new('shortcut'); | ||||
@@ -34,7 +56,14 @@ export default class Shortcut extends Block { | |||||
} | } | ||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
this.add_tune_button(); | |||||
$(this.wrapper).find('.widget').addClass('shortcut edit-mode'); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
} else { | |||||
let $shortcut_icon = frappe.utils.icon('arrow-up-right', 'xs', '', 'stroke: grey', 'ml-2'); | |||||
$(this.wrapper).find('.widget .widget-title').append($shortcut_icon); | |||||
$(this.wrapper).append($(`<div class="divider"></div>`)); | |||||
} | } | ||||
return this.wrapper; | return this.wrapper; | ||||
} | } | ||||
@@ -47,9 +76,9 @@ export default class Shortcut extends Block { | |||||
return true; | return true; | ||||
} | } | ||||
save(blockContent) { | |||||
save() { | |||||
return { | return { | ||||
shortcut_name: blockContent.getAttribute('shortcut_name'), | |||||
shortcut_name: this.wrapper.getAttribute('shortcut_name'), | |||||
col: this.get_col(), | col: this.get_col(), | ||||
new: this.new_block_widget | new: this.new_block_widget | ||||
}; | }; | ||||
@@ -3,7 +3,7 @@ export default class Spacer extends Block { | |||||
static get toolbox() { | static get toolbox() { | ||||
return { | return { | ||||
title: 'Spacer', | title: 'Spacer', | ||||
icon: '<svg width="18" height="18" viewBox="0 0 400 400"><path d="M377.87 24.126C361.786 8.042 342.417 0 319.769 0H82.227C59.579 0 40.211 8.042 24.125 24.126 8.044 40.212.002 59.576.002 82.228v237.543c0 22.647 8.042 42.014 24.123 58.101 16.086 16.085 35.454 24.127 58.102 24.127h237.542c22.648 0 42.011-8.042 58.102-24.127 16.085-16.087 24.126-35.453 24.126-58.101V82.228c-.004-22.648-8.046-42.016-24.127-58.102zm-12.422 295.645c0 12.559-4.47 23.314-13.415 32.264-8.945 8.945-19.698 13.411-32.265 13.411H82.227c-12.563 0-23.317-4.466-32.264-13.411-8.945-8.949-13.418-19.705-13.418-32.264V82.228c0-12.562 4.473-23.316 13.418-32.264 8.947-8.946 19.701-13.418 32.264-13.418h237.542c12.566 0 23.319 4.473 32.265 13.418 8.945 8.947 13.415 19.701 13.415 32.264v237.543h-.001z"/></svg>' | |||||
icon: frappe.utils.icon('spacer', 'sm') | |||||
}; | }; | ||||
} | } | ||||
@@ -18,40 +18,24 @@ export default class Spacer extends Block { | |||||
render() { | render() { | ||||
this.wrapper = document.createElement('div'); | this.wrapper = document.createElement('div'); | ||||
this.wrapper.classList.add('widget', 'spacer'); | |||||
if (!this.readOnly) { | if (!this.readOnly) { | ||||
let $spacer = $(` | let $spacer = $(` | ||||
<div class="widget-head"> | <div class="widget-head"> | ||||
<div></div> | |||||
<div class="spacer-left"></div> | |||||
<div>Spacer</div> | <div>Spacer</div> | ||||
<div class="widget-control"></div> | <div class="widget-control"></div> | ||||
</div> | </div> | ||||
`); | `); | ||||
$spacer.appendTo(this.wrapper); | $spacer.appendTo(this.wrapper); | ||||
this.wrapper.classList.add('widget', 'new-widget'); | |||||
this.wrapper.style.minHeight = 50 + 'px'; | |||||
this.wrapper.classList.add('edit-mode'); | |||||
this.wrapper.style.minHeight = 40 + 'px'; | |||||
let $widget_control = $spacer.find('.widget-control'); | let $widget_control = $spacer.find('.widget-control'); | ||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('dot-horizontal', 'xs'), | |||||
(event) => { | |||||
let evn = event; | |||||
!$('.ce-settings.ce-settings--opened').length && | |||||
setTimeout(() => { | |||||
this.api.toolbar.toggleBlockSettings(); | |||||
var position = $(evn.target).offset(); | |||||
$('.ce-settings.ce-settings--opened').offset({ | |||||
top: position.top + 25, | |||||
left: position.left - 77 | |||||
}); | |||||
}, 50); | |||||
}, | |||||
"tune-btn", | |||||
`${__('Tune')}`, | |||||
null, | |||||
$widget_control | |||||
); | |||||
this.add_settings_button(); | |||||
this.add_new_block_button(); | |||||
frappe.utils.add_custom_button( | frappe.utils.add_custom_button( | ||||
frappe.utils.icon('drag', 'xs'), | frappe.utils.icon('drag', 'xs'), | ||||
@@ -61,15 +45,6 @@ export default class Spacer extends Block { | |||||
null, | null, | ||||
$widget_control | $widget_control | ||||
); | ); | ||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('delete', 'xs'), | |||||
() => this.api.blocks.delete(), | |||||
"delete-spacer", | |||||
`${__('Delete')}`, | |||||
null, | |||||
$widget_control | |||||
); | |||||
} | } | ||||
return this.wrapper; | return this.wrapper; | ||||
} | } | ||||
@@ -1,123 +0,0 @@ | |||||
export default class SpacingTune { | |||||
static get isTune() { | |||||
return true; | |||||
} | |||||
constructor({api, settings}) { | |||||
this.api = api; | |||||
this.settings = settings; | |||||
this.CSS = { | |||||
button: 'ce-settings__button', | |||||
wrapper: 'ce-tune-layout', | |||||
sidebar: 'cdx-settings-sidebar', | |||||
animation: 'wobble', | |||||
}; | |||||
this.data = { colWidth: 12 }; | |||||
this.wrapper = undefined; | |||||
this.sidebar = undefined; | |||||
} | |||||
render() { | |||||
let me = this; | |||||
let layoutWrapper = document.createElement('div'); | |||||
layoutWrapper.classList.add(this.CSS.wrapper); | |||||
let decreaseWidthButton = document.createElement('div'); | |||||
decreaseWidthButton.classList.add(this.CSS.button, 'ce-shrink-button'); | |||||
let increaseWidthButton = document.createElement('div'); | |||||
increaseWidthButton.classList.add(this.CSS.button, 'ce-expand-button'); | |||||
layoutWrapper.appendChild(decreaseWidthButton); | |||||
layoutWrapper.appendChild(increaseWidthButton); | |||||
decreaseWidthButton.innerHTML = `<svg version="1.1" height="10" x="0px" y="0px" viewBox="-674 380 17 10" style="enable-background:new -674 380 17 10;" xml:space="preserve"><path d="M-674,383.9h3.6l-1.7-1.7c-0.4-0.4-0.4-1.2,0-1.6c0.4-0.4,1.1-0.4,1.6,0l3.2,3.2c0.6,0.2,0.8,0.8,0.6,1.4 c-0.1,0.1-0.1,0.3-0.2,0.4l-3.8,3.8c-0.4,0.4-1.1,0.4-1.5,0c-0.4-0.4-0.4-1.1,0-1.5l1.8-1.8h-3.6V383.9z"/><path d="M-657,386.1h-3.6l1.7,1.7c0.4,0.4,0.4,1.2,0,1.6c-0.4,0.4-1.1,0.4-1.6,0l-3.2-3.2c-0.6-0.2-0.8-0.8-0.6-1.4 c0.1-0.1,0.1-0.3,0.2-0.4l3.8-3.8c0.4-0.4,1.1-0.4,1.5,0c0.4,0.4,0.4,1.1,0,1.5l-1.8,1.8h3.6V386.1z"/></svg>`; | |||||
this.api.tooltip.onHover(decreaseWidthButton, 'Shrink', { | |||||
placement: 'top', | |||||
hidingDelay: 500, | |||||
}); | |||||
this.api.listeners.on( | |||||
decreaseWidthButton, | |||||
'click', | |||||
() => me.decreaseWidth(), | |||||
false | |||||
); | |||||
increaseWidthButton.innerHTML = `<svg width="17" height="10" viewBox="0 0 17 10"><path d="M13.568 5.925H4.056l1.703 1.703a1.125 1.125 0 0 1-1.59 1.591L.962 6.014A1.069 1.069 0 0 1 .588 4.26L4.38.469a1.069 1.069 0 0 1 1.512 1.511L4.084 3.787h9.606l-1.85-1.85a1.069 1.069 0 1 1 1.512-1.51l3.792 3.791a1.069 1.069 0 0 1-.475 1.788L13.514 9.16a1.125 1.125 0 0 1-1.59-1.591l1.644-1.644z"/></svg>`; | |||||
this.api.tooltip.onHover(increaseWidthButton, 'Expand', { | |||||
placement: 'top', | |||||
hidingDelay: 500, | |||||
}); | |||||
this.api.listeners.on( | |||||
increaseWidthButton, | |||||
'click', | |||||
() => me.increaseWidth(), | |||||
false | |||||
); | |||||
this.wrapper = layoutWrapper; | |||||
return layoutWrapper; | |||||
} | |||||
decreaseWidth() { | |||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex(); | |||||
if (currentBlockIndex < 0) { | |||||
return; | |||||
} | |||||
let currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex); | |||||
if (!currentBlock) { | |||||
return; | |||||
} | |||||
let currentBlockElement = currentBlock.holder; | |||||
let className = 'col-12'; | |||||
let colClass = new RegExp(/\bcol-.+?\b/, 'g'); | |||||
if (currentBlockElement.className.match(colClass)) { | |||||
currentBlockElement.classList.forEach( cn => { | |||||
if (cn.match(colClass)) { | |||||
className = cn; | |||||
} | |||||
}); | |||||
let parts = className.split('-'); | |||||
let width = parseInt(parts[1]); | |||||
if (width >= 4) { | |||||
currentBlockElement.classList.remove('col-'+width); | |||||
width = width - 1; | |||||
currentBlockElement.classList.add('col-'+width); | |||||
} | |||||
} | |||||
} | |||||
increaseWidth() { | |||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex(); | |||||
if (currentBlockIndex < 0) { | |||||
return; | |||||
} | |||||
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex); | |||||
if (!currentBlock) { | |||||
return; | |||||
} | |||||
const currentBlockElement = currentBlock.holder; | |||||
let className = 'col-12'; | |||||
const colClass = new RegExp(/\bcol-.+?\b/, 'g'); | |||||
if (currentBlockElement.className.match(colClass)) { | |||||
currentBlockElement.classList.forEach( cn => { | |||||
if (cn.match(colClass)) { | |||||
className = cn; | |||||
} | |||||
}); | |||||
let parts = className.split('-'); | |||||
let width = parseInt(parts[1]); | |||||
if (width <= 11) { | |||||
currentBlockElement.classList.remove('col-'+width); | |||||
width = width + 1; | |||||
currentBlockElement.classList.add('col-'+width); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -160,17 +160,17 @@ export default class WebForm extends frappe.ui.FieldGroup { | |||||
} | } | ||||
setup_primary_action() { | setup_primary_action() { | ||||
this.add_button_to_header(this.button_label || "Save", "primary", () => | |||||
this.add_button_to_header(this.button_label || __("Save", null, "Button in web form"), "primary", () => | |||||
this.save() | this.save() | ||||
); | ); | ||||
this.add_button_to_footer(this.button_label || "Save", "primary", () => | |||||
this.add_button_to_footer(this.button_label || __("Save", null, "Button in web form"), "primary", () => | |||||
this.save() | this.save() | ||||
); | ); | ||||
} | } | ||||
setup_cancel_button() { | setup_cancel_button() { | ||||
this.add_button_to_header(__("Cancel"), "light", () => this.cancel()); | |||||
this.add_button_to_header(__("Cancel", null, "Button in web form"), "light", () => this.cancel()); | |||||
} | } | ||||
setup_delete_button() { | setup_delete_button() { | ||||
@@ -216,16 +216,18 @@ export default class WebForm extends frappe.ui.FieldGroup { | |||||
let message = ''; | let message = ''; | ||||
if (invalid_values.length) { | if (invalid_values.length) { | ||||
message += __('Invalid values for fields:') + '<br><br><ul><li>' + invalid_values.join('<li>') + '</ul>'; | |||||
message += __('Invalid values for fields:', null, 'Error message in web form'); | |||||
message += '<br><br><ul><li>' + invalid_values.join('<li>') + '</ul>'; | |||||
} | } | ||||
if (errors.length) { | if (errors.length) { | ||||
message += __('Mandatory fields required:') + '<br><br><ul><li>' + errors.join('<li>') + '</ul>'; | |||||
message += __('Mandatory fields required:', null, 'Error message in web form'); | |||||
message += '<br><br><ul><li>' + errors.join('<li>') + '</ul>'; | |||||
} | } | ||||
if (invalid_values.length || errors.length) { | if (invalid_values.length || errors.length) { | ||||
frappe.msgprint({ | frappe.msgprint({ | ||||
title: __('Error'), | |||||
title: __('Error', null, 'Title of error message in web form'), | |||||
message: message, | message: message, | ||||
indicator: 'orange' | indicator: 'orange' | ||||
}); | }); | ||||
@@ -34,16 +34,6 @@ export default class Widget { | |||||
this.action_area | this.action_area | ||||
); | ); | ||||
options.allow_delete && | |||||
frappe.utils.add_custom_button( | |||||
frappe.utils.icon('delete', 'xs'), | |||||
() => this.delete(), | |||||
"", | |||||
`${__('Delete')}`, | |||||
null, | |||||
this.action_area | |||||
); | |||||
if (options.allow_hiding) { | if (options.allow_hiding) { | ||||
if (this.hidden) { | if (this.hidden) { | ||||
this.widget.removeClass("hidden"); | this.widget.removeClass("hidden"); | ||||
@@ -71,27 +61,11 @@ export default class Widget { | |||||
frappe.utils.add_custom_button( | frappe.utils.add_custom_button( | ||||
frappe.utils.icon("edit", "xs"), | frappe.utils.icon("edit", "xs"), | ||||
() => this.edit(), | () => this.edit(), | ||||
null, | |||||
"edit-button", | |||||
`${__('Edit')}`, | `${__('Edit')}`, | ||||
null, | null, | ||||
this.action_area | this.action_area | ||||
); | ); | ||||
if (options.allow_resize) { | |||||
const title = this.width == 'Full'? `${__('Collapse')}` : `${__('Expand')}`; | |||||
frappe.utils.add_custom_button( | |||||
'<i class="fa fa-expand" aria-hidden="true"></i>', | |||||
() => this.toggle_width(), | |||||
"resize-button", | |||||
title, | |||||
null, | |||||
this.action_area | |||||
); | |||||
this.resize_button = this.action_area.find( | |||||
".resize-button" | |||||
); | |||||
} | |||||
} | } | ||||
make() { | make() { | ||||
@@ -100,9 +74,7 @@ export default class Widget { | |||||
} | } | ||||
make_widget() { | make_widget() { | ||||
this.widget = $(`<div class="widget | |||||
${ this.shadow ? "widget-shadow" : " " } | |||||
" data-widget-name="${this.name ? this.name : ''}"> | |||||
this.widget = $(`<div class="widget" data-widget-name="${this.name ? this.name : ''}"> | |||||
<div class="widget-head"> | <div class="widget-head"> | ||||
<div class="widget-label"> | <div class="widget-label"> | ||||
<div class="widget-title"></div> | <div class="widget-title"></div> | ||||
@@ -110,10 +82,8 @@ export default class Widget { | |||||
</div> | </div> | ||||
<div class="widget-control"></div> | <div class="widget-control"></div> | ||||
</div> | </div> | ||||
<div class="widget-body"> | |||||
</div> | |||||
<div class="widget-footer"> | |||||
</div> | |||||
<div class="widget-body"></div> | |||||
<div class="widget-footer"></div> | |||||
</div>`); | </div>`); | ||||
this.title_field = this.widget.find(".widget-title"); | this.title_field = this.widget.find(".widget-title"); | ||||
@@ -28,7 +28,7 @@ export default class ChartWidget extends Widget { | |||||
} | } | ||||
set_chart_title() { | set_chart_title() { | ||||
const max_chars = this.widget.width() < 600 ? 20 : 60; | |||||
const max_chars = this.widget.width() < 600 ? 40 : 60; | |||||
this.set_title(max_chars); | this.set_title(max_chars); | ||||
} | } | ||||
@@ -80,7 +80,9 @@ export default class LinksWidget extends Widget { | |||||
return $(`<a href="${route}" class="link-item ellipsis ${ | return $(`<a href="${route}" class="link-item ellipsis ${ | ||||
item.onboard ? "onboard-spotlight" : "" | item.onboard ? "onboard-spotlight" : "" | ||||
} ${disabled_dependent(item)}" type="${item.type}"> | |||||
} ${disabled_dependent(item)}" type="${item.type}" title="${ | |||||
item.label ? item.label : item.name | |||||
}"> | |||||
<span class="indicator-pill no-margin ${get_indicator_color(item)}"></span> | <span class="indicator-pill no-margin ${get_indicator_color(item)}"></span> | ||||
${get_link_for_item(item)} | ${get_link_for_item(item)} | ||||
</a>`); | </a>`); | ||||
@@ -9,6 +9,7 @@ class WidgetDialog { | |||||
this.setup_dialog_events(); | this.setup_dialog_events(); | ||||
this.dialog.show(); | this.dialog.show(); | ||||
window.cur_dialog = this.dialog; | |||||
this.editing && this.set_default_values(); | this.editing && this.set_default_values(); | ||||
} | } | ||||
@@ -181,19 +182,16 @@ class CardDialog extends WidgetDialog { | |||||
fieldtype: "Select", | fieldtype: "Select", | ||||
in_list_view: 1, | in_list_view: 1, | ||||
label: "Link Type", | label: "Link Type", | ||||
options: ["DocType", "Page", "Report"], | |||||
onchange: (e) => { | |||||
me.link_to = e.currentTarget.value; | |||||
} | |||||
options: ["DocType", "Page", "Report"] | |||||
}, | }, | ||||
{ | { | ||||
fieldname: "link_to", | fieldname: "link_to", | ||||
fieldtype: "Dynamic Link", | fieldtype: "Dynamic Link", | ||||
in_list_view: 1, | in_list_view: 1, | ||||
label: "Link To", | label: "Link To", | ||||
options: "link_type", | |||||
get_options: () => { | |||||
return me.link_to; | |||||
get_options: (df) => { | |||||
return df.doc.link_type; | |||||
} | } | ||||
}, | }, | ||||
{ | { | ||||
@@ -506,7 +504,7 @@ class NumberCardDialog extends WidgetDialog { | |||||
setup_dialog_events() { | setup_dialog_events() { | ||||
if (!this.document_type) { | if (!this.document_type) { | ||||
if (this.default_values['doctype']) { | |||||
if (this.default_values && this.default_values['doctype']) { | |||||
this.document_type = this.default_values['doctype']; | this.document_type = this.default_values['doctype']; | ||||
this.setup_filter(this.default_values['doctype']); | this.setup_filter(this.default_values['doctype']); | ||||
this.set_aggregate_function_fields(); | this.set_aggregate_function_fields(); | ||||
@@ -518,7 +516,7 @@ class NumberCardDialog extends WidgetDialog { | |||||
set_aggregate_function_fields() { | set_aggregate_function_fields() { | ||||
let aggregate_function_fields = []; | let aggregate_function_fields = []; | ||||
if (this.document_type) { | |||||
if (this.document_type && frappe.get_meta(this.document_type)) { | |||||
frappe.get_meta(this.document_type).fields.map(df => { | frappe.get_meta(this.document_type).fields.map(df => { | ||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) { | if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) { | ||||
if (df.fieldtype == 'Currency') { | if (df.fieldtype == 'Currency') { | ||||
@@ -537,7 +535,7 @@ class NumberCardDialog extends WidgetDialog { | |||||
if (data.new_or_existing == 'Existing Card') { | if (data.new_or_existing == 'Existing Card') { | ||||
data.name = data.card; | data.name = data.card; | ||||
} | } | ||||
data.stats_filter = JSON.stringify(this.filter_group.get_filters()); | |||||
data.stats_filter = this.filter_group && JSON.stringify(this.filter_group.get_filters()); | |||||
data.document_type = this.document_type; | data.document_type = this.document_type; | ||||
return data; | return data; | ||||
@@ -62,7 +62,7 @@ | |||||
background-color: var(--control-bg); | background-color: var(--control-bg); | ||||
color: var(--text-color); | color: var(--text-color); | ||||
&:hover, &:active { | &:hover, &:active { | ||||
background-color: var(--gray-300); | |||||
background-color: var(--gray-400); | |||||
color: var(--text-color); | color: var(--text-color); | ||||
} | } | ||||
} | } | ||||
@@ -87,6 +87,8 @@ | |||||
--highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500); | --highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500); | ||||
--shadow-base: 0px 4px 8px rgba(114, 176, 233, 0.06), 0px 0px 4px rgba(112, 172, 228, 0.12); | |||||
// input | // input | ||||
--input-disabled-bg: none; | --input-disabled-bg: none; | ||||
@@ -107,13 +107,20 @@ body { | |||||
} | } | ||||
} | } | ||||
.divider { | |||||
height: 30%; | |||||
position: absolute; | |||||
top: 18px; | |||||
right: 0; | |||||
border-right: 1px solid var(--gray-400); | |||||
} | |||||
.widget { | .widget { | ||||
@include flex(flex, null, null, column); | @include flex(flex, null, null, column); | ||||
min-height: 1px; | min-height: 1px; | ||||
padding: 15px; | |||||
padding: 7px; | |||||
border-radius: var(--border-radius-md); | border-radius: var(--border-radius-md); | ||||
height: 100%; | height: 100%; | ||||
box-shadow: var(--card-shadow); | |||||
background-color: var(--card-bg); | background-color: var(--card-bg); | ||||
.btn { | .btn { | ||||
@@ -143,6 +150,7 @@ body { | |||||
font-weight: 500; | font-weight: 500; | ||||
line-height: 1.3em; | line-height: 1.3em; | ||||
color: var(--heading-color); | color: var(--heading-color); | ||||
cursor: default; | |||||
svg { | svg { | ||||
flex: none; | flex: none; | ||||
@@ -329,9 +337,28 @@ body { | |||||
} | } | ||||
&.onboarding-widget-box { | &.onboarding-widget-box { | ||||
margin-top: var(--margin-xs); | |||||
margin-bottom: var(--margin-2xl); | margin-bottom: var(--margin-2xl); | ||||
padding: var(--padding-lg); | |||||
padding: var(--padding-lg) !important; | |||||
background-color: var(--bg-color); | |||||
&.edit-mode:hover { | |||||
background-color: var(--fg-color); | |||||
.onboarding-step { | |||||
&.active, | |||||
&:hover { | |||||
background-color: var(--bg-color); | |||||
.step-index.step-pending { | |||||
background-color: var(--fg-color); | |||||
} | |||||
} | |||||
.step-index { | |||||
background-color: var(--bg-color); | |||||
} | |||||
} | |||||
} | |||||
.widget-head { | .widget-head { | ||||
display: flex; | display: flex; | ||||
@@ -390,12 +417,6 @@ body { | |||||
.step-index.step-pending { | .step-index.step-pending { | ||||
display: flex; | display: flex; | ||||
} | } | ||||
&.active { | |||||
.step-index.step-pending { | |||||
background-color: var(--fg-color); | |||||
} | |||||
} | |||||
} | } | ||||
&.complete { | &.complete { | ||||
@@ -418,7 +439,11 @@ body { | |||||
&.active, | &.active, | ||||
&:hover { | &:hover { | ||||
background-color: var(--bg-light-gray); | |||||
background-color: var(--fg-color); | |||||
.step-index { | |||||
background-color: var(--bg-color); | |||||
} | |||||
.step-skip { | .step-skip { | ||||
visibility: visible; | visibility: visible; | ||||
@@ -434,7 +459,7 @@ body { | |||||
height: 20px; | height: 20px; | ||||
width: 20px; | width: 20px; | ||||
color: var(--text-on-light-gray); | color: var(--text-on-light-gray); | ||||
background-color: var(--bg-light-gray); | |||||
background-color: var(--fg-color); | |||||
margin-right: var(--margin-sm); | margin-right: var(--margin-sm); | ||||
border-radius: var(--border-radius-full); | border-radius: var(--border-radius-full); | ||||
@@ -447,7 +472,7 @@ body { | |||||
display: none; | display: none; | ||||
background-color: var(--primary); | background-color: var(--primary); | ||||
.icon use { | .icon use { | ||||
stroke: var(--white); | |||||
stroke: var(--var(--fg-color)); | |||||
} | } | ||||
} | } | ||||
@@ -496,7 +521,7 @@ body { | |||||
} | } | ||||
} | } | ||||
@media (max-width: map-get($grid-breakpoints, "sm")) { | |||||
@media (max-width: map-get($grid-breakpoints, "md")) { | |||||
.widget-body { | .widget-body { | ||||
flex-direction: column; | flex-direction: column; | ||||
.onboarding-steps-wrapper { | .onboarding-steps-wrapper { | ||||
@@ -513,9 +538,19 @@ body { | |||||
&.shortcut-widget-box { | &.shortcut-widget-box { | ||||
cursor: pointer; | cursor: pointer; | ||||
.widget-head { | |||||
margin-top: var(--margin-xs); | |||||
margin-bottom: 5px; | |||||
&:hover { | |||||
.widget-title { | |||||
color: var(--blue-500) !important; | |||||
} | |||||
svg.icon-xs { | |||||
stroke: var(--blue-500) !important; | |||||
} | |||||
} | |||||
.widget-title { | |||||
cursor: pointer !important; | |||||
font-size: var(--text-base) !important; | |||||
} | } | ||||
.indicator-pill { | .indicator-pill { | ||||
@@ -631,8 +666,8 @@ body { | |||||
width: 18px; | width: 18px; | ||||
.icon-xs { | .icon-xs { | ||||
width: 8px; | |||||
height: 7px; | |||||
width: 10px; | |||||
height: 10px; | |||||
} | } | ||||
} | } | ||||
@@ -757,6 +792,25 @@ body { | |||||
} | } | ||||
} | } | ||||
.workspace-sidebar-skeleton { | |||||
transition: ease; | |||||
.sidebar-box { | |||||
height: 40px; | |||||
margin-bottom: 10px; | |||||
margin-left: 10px; | |||||
background-color: var(--skeleton-bg); | |||||
&.child { | |||||
margin-left: 30px; | |||||
} | |||||
&.section { | |||||
height: 25px; | |||||
margin-left: 0px; | |||||
} | |||||
} | |||||
} | |||||
[data-page-route="Workspaces"] { | [data-page-route="Workspaces"] { | ||||
@media (min-width: map-get($grid-breakpoints, "lg")) { | @media (min-width: map-get($grid-breakpoints, "lg")) { | ||||
.layout-main { | .layout-main { | ||||
@@ -764,7 +818,6 @@ body { | |||||
.layout-side-section, .layout-main-section-wrapper { | .layout-side-section, .layout-main-section-wrapper { | ||||
height: 100%; | height: 100%; | ||||
overflow-y: auto; | overflow-y: auto; | ||||
padding-right: 25px; | |||||
scrollbar-color: var(--gray-200) transparent; | scrollbar-color: var(--gray-200) transparent; | ||||
[data-theme="dark"] & { | [data-theme="dark"] & { | ||||
scrollbar-color: var(--gray-800) transparent; | scrollbar-color: var(--gray-800) transparent; | ||||
@@ -783,7 +836,12 @@ body { | |||||
} | } | ||||
.layout-side-section { | .layout-side-section { | ||||
margin-right: 20px; | |||||
padding-right: 15px; | |||||
} | |||||
.layout-main-section { | |||||
padding: var(--padding-md); | |||||
margin-bottom: var(--margin-sm); | |||||
} | } | ||||
.desk-sidebar { | .desk-sidebar { | ||||
@@ -792,9 +850,15 @@ body { | |||||
} | } | ||||
} | } | ||||
.layout-main-section { | |||||
background-color: var(--fg-color); | |||||
padding: var(--padding-sm); | |||||
border-radius: var(--border-radius-lg); | |||||
} | |||||
.block-menu-item-icon svg{ | .block-menu-item-icon svg{ | ||||
width: 12px; | |||||
height: 12px; | |||||
width: 18px; | |||||
height: 18px; | |||||
margin-right: 5px; | margin-right: 5px; | ||||
} | } | ||||
@@ -803,7 +867,6 @@ body { | |||||
padding: 0px; | padding: 0px; | ||||
.sidebar-item-control { | .sidebar-item-control { | ||||
> * { | > * { | ||||
align-self: center; | align-self: center; | ||||
margin-left: 3px; | margin-left: 3px; | ||||
@@ -816,7 +879,7 @@ body { | |||||
display: none; | display: none; | ||||
} | } | ||||
.delete-page { | |||||
.setting-btn, .duplicate-page { | |||||
display: none; | display: none; | ||||
} | } | ||||
@@ -824,13 +887,13 @@ body { | |||||
padding: 10px 12px 10px 2px; | padding: 10px 12px 10px 2px; | ||||
} | } | ||||
.sidebar-info { | |||||
display: none; | |||||
} | |||||
svg { | svg { | ||||
margin-right: 0; | margin-right: 0; | ||||
} | } | ||||
.dropdown-list { | |||||
top: 42px; | |||||
} | |||||
} | } | ||||
.sidebar-item-label { | .sidebar-item-label { | ||||
@@ -846,6 +909,7 @@ body { | |||||
} | } | ||||
.sidebar-item-container { | .sidebar-item-container { | ||||
position: relative; | |||||
.sidebar-item-container{ | .sidebar-item-container{ | ||||
margin-left: 10px; | margin-left: 10px; | ||||
@@ -863,19 +927,14 @@ body { | |||||
display: inline-block; | display: inline-block; | ||||
} | } | ||||
.delete-page { | |||||
display: inline-block; | |||||
margin-right: 8px; | |||||
} | |||||
.sidebar-info { | |||||
.setting-btn, .duplicate-page { | |||||
display: inline-block; | display: inline-block; | ||||
margin-right: 8px; | margin-right: 8px; | ||||
} | } | ||||
.drop-icon { | .drop-icon { | ||||
padding: 10px 8px 10px 2px; | padding: 10px 8px 10px 2px; | ||||
margin-left: -4px; | |||||
margin-left: -8px; | |||||
} | } | ||||
} | } | ||||
@@ -899,11 +958,81 @@ body { | |||||
margin: 0px -7px; | margin: 0px -7px; | ||||
padding-bottom: 20px !important; | padding-bottom: 20px !important; | ||||
.ce-block{ | |||||
.ce-block { | |||||
width: 100%; | width: 100%; | ||||
padding-left: 0; | padding-left: 0; | ||||
padding-right: 0; | padding-right: 0; | ||||
.ce-header b { | |||||
font-weight: 600 !important; | |||||
} | |||||
.new-block-button { | |||||
position: absolute; | |||||
top: 14px; | |||||
left: -22px; | |||||
cursor: pointer; | |||||
visibility: hidden; | |||||
opacity: 0; | |||||
transition: visibility 0s, opacity 0.5s ease-in-out; | |||||
} | |||||
.edit-mode { | |||||
.widget-control > *, .paragraph-control > * { | |||||
width: 0px; | |||||
visibility: hidden; | |||||
opacity: 0; | |||||
transition: visibility 0s, opacity 0.5s ease-in-out; | |||||
} | |||||
.link-item { | |||||
pointer-events: none; | |||||
} | |||||
} | |||||
&:hover { | |||||
.widget-control > *, .new-block-button { | |||||
width: auto; | |||||
visibility: visible; | |||||
opacity: 1; | |||||
} | |||||
} | |||||
&.ce-block--focused { | |||||
.widget { | |||||
box-shadow: var(--shadow-base) !important; | |||||
.widget-control > * { | |||||
width: auto; | |||||
visibility: visible; | |||||
opacity: 1; | |||||
} | |||||
&.shortcut, &.header { | |||||
background-color: var(--fg-color) !important; | |||||
} | |||||
&.onboarding { | |||||
background-color: var(--fg-color); | |||||
.onboarding-step { | |||||
&.active, | |||||
&:hover { | |||||
background-color: var(--bg-color); | |||||
.step-index.step-pending { | |||||
background-color: var(--fg-color); | |||||
} | |||||
} | |||||
.step-index { | |||||
background-color: var(--bg-color); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
&.ce-block--selected { | &.ce-block--selected { | ||||
.ce-block__content { | .ce-block__content { | ||||
background-color: inherit; | background-color: inherit; | ||||
@@ -923,50 +1052,125 @@ body { | |||||
pointer-events: none; | pointer-events: none; | ||||
} | } | ||||
.resizer { | |||||
width: 10px; | |||||
height: 100%; | |||||
position:absolute; | |||||
right: 0; | |||||
bottom: 0; | |||||
cursor: col-resize; | |||||
border-color: transparent; | |||||
transition: border-color 0.3s ease-in-out; | |||||
&:hover { | |||||
border-right: 3px solid var(--gray-400) !important; | |||||
} | |||||
} | |||||
.ce-header { | .ce-header { | ||||
padding: 0 !important; | |||||
padding-left: 7px !important; | |||||
margin-bottom: 0 !important; | margin-bottom: 0 !important; | ||||
flex: 1; | flex: 1; | ||||
&:focus { | |||||
outline: none; | |||||
} | |||||
} | |||||
.block-list-container { | |||||
left: 20px; | |||||
top: 55px !important; | |||||
width: 200px !important; | |||||
} | } | ||||
.widget{ | |||||
.dropdown-title { | |||||
padding: 6px 10px; | |||||
font-size: smaller; | |||||
cursor: default; | |||||
} | |||||
.ce-paragraph[data-placeholder]:empty::before { | |||||
opacity: 1; | |||||
} | |||||
.widget { | |||||
&.edit-mode { | |||||
padding: 7px 12px; | |||||
&:hover { | |||||
box-shadow: var(--shadow-base); | |||||
background-color: var(--fg-color); | |||||
} | |||||
&.spacer { | |||||
align-items: inherit; | |||||
color: var(--text-muted); | |||||
border: 1px dashed var(--gray-400); | |||||
cursor: pointer; | |||||
.widget-control > * { | |||||
width: auto; | |||||
} | |||||
.spacer-left { | |||||
min-width: 74px; | |||||
} | |||||
} | |||||
} | |||||
&.spacer { | |||||
height: 18px !important; | |||||
} | |||||
&.ce-paragraph { | |||||
display: block; | |||||
} | |||||
&.paragraph { | |||||
cursor: text; | |||||
.ce-paragraph { | |||||
padding: 2px; | |||||
} | |||||
.paragraph-control { | |||||
display: flex; | |||||
flex-direction: row-reverse; | |||||
position: absolute; | |||||
right: 20px; | |||||
gap: 5px; | |||||
background-color: var(--card-bg); | |||||
padding-left: 5px; | |||||
.drag-handle { | |||||
cursor: all-scroll; | |||||
cursor: grabbing; | |||||
} | |||||
} | |||||
} | |||||
&.header { | &.header { | ||||
display: flex; | display: flex; | ||||
justify-content: center; | justify-content: center; | ||||
flex: 1; | flex: 1; | ||||
padding-left: 15px !important; | |||||
padding-right: 15px !important; | |||||
min-height: 50px; | |||||
padding-left: 0px !important; | |||||
min-height: 40px; | |||||
box-shadow: none; | box-shadow: none; | ||||
background-color: var(--control-bg); | background-color: var(--control-bg); | ||||
color: var(--text-muted); | color: var(--text-muted); | ||||
} | |||||
&:focus { | |||||
outline: none; | |||||
} | |||||
cursor: text; | |||||
&.new-widget { | |||||
align-items: inherit; | |||||
.ce-header { | |||||
padding-left: 14px !important; | |||||
} | |||||
} | } | ||||
&.ce-paragraph { | |||||
display: block; | |||||
&.shortcut { | |||||
background-color: var(--control-bg); | |||||
} | } | ||||
.paragraph-control { | |||||
display: flex; | |||||
flex-direction: row-reverse; | |||||
position: absolute; | |||||
right: 20px; | |||||
gap: 5px; | |||||
background-color: var(--card-bg); | |||||
padding-left: 5px; | |||||
.drag-handle { | |||||
cursor: all-scroll; | |||||
cursor: grabbing; | |||||
} | |||||
&:focus { | |||||
outline: none; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -978,14 +1182,21 @@ body { | |||||
} | } | ||||
.ce-toolbar { | .ce-toolbar { | ||||
&.ce-toolbar--opened { | |||||
display: none; | |||||
} | |||||
svg { | svg { | ||||
fill: currentColor; | fill: currentColor; | ||||
} | } | ||||
.icon { | .icon { | ||||
stroke: none; | stroke: none; | ||||
width: fit-content; | |||||
height: fit-content; | |||||
&.icon--plus { | |||||
width: 14px; | |||||
} | |||||
} | } | ||||
.ce-settings { | .ce-settings { | ||||
@@ -993,6 +1204,10 @@ body { | |||||
.ce-settings__button, .cdx-settings-button { | .ce-settings__button, .cdx-settings-button { | ||||
color: #707684; | color: #707684; | ||||
.icon { | |||||
width: 14px; | |||||
} | |||||
} | } | ||||
.cdx-settings-button--active { | .cdx-settings-button--active { | ||||
@@ -1024,6 +1239,10 @@ body { | |||||
.icon { | .icon { | ||||
fill: currentColor; | fill: currentColor; | ||||
} | } | ||||
svg { | |||||
stroke: none; | |||||
} | |||||
} | } | ||||
@media (min-width: 1199px) { | @media (min-width: 1199px) { | ||||
@@ -1037,25 +1256,63 @@ body { | |||||
} | } | ||||
} | } | ||||
@media (max-width: 1199px) { | |||||
.ce-block.col-4 { | |||||
flex: 0 0 50%; | |||||
max-width: 50%; | |||||
} | |||||
} | |||||
} | |||||
@media (max-width: 750px) { | |||||
.ce-block.col-4 { | |||||
flex: 0 0 100%; | |||||
max-width: 100%; | |||||
} | |||||
} | |||||
@media (max-width: 750px) { | |||||
.ce-block.col-6 { | |||||
flex: 0 0 100%; | |||||
max-width: 100%; | |||||
} | |||||
.cdx-marker { | |||||
background: rgba(245,235,111,0.29); | |||||
padding: 3px 0; | |||||
} | |||||
.header-inline-tool { | |||||
border: none; | |||||
background-color: transparent; | |||||
margin-bottom: 2px; | |||||
} | |||||
.header-level-select { | |||||
display: flex; | |||||
flex-direction: column; | |||||
padding: 6px; | |||||
} | |||||
.header-level-select .header-level { | |||||
border: none; | |||||
background-color: transparent; | |||||
border-radius: var(--border-radius-sm); | |||||
padding: 6px; | |||||
margin: 2px 0px; | |||||
&:hover { | |||||
background-color: var(--fg-hover-color); | |||||
} | } | ||||
} | |||||
.dropdown-btn { | |||||
position: relative; | |||||
} | |||||
.dropdown-list { | |||||
position: absolute; | |||||
background-color: var(--fg-color); | |||||
box-shadow: var(--shadow-base) !important; | |||||
border-radius: var(--border-radius-sm); | |||||
padding: 6px; | |||||
top: 30px; | |||||
right: 0; | |||||
width: 150px; | |||||
z-index: 1; | |||||
} | |||||
.dropdown-list .dropdown-item { | |||||
cursor: pointer; | |||||
padding: 6px 10px; | |||||
font-size: small; | |||||
border-radius: var(--border-radius-sm); | |||||
margin: 1px 0px; | |||||
} | } | ||||
.dropdown-item-icon { | |||||
margin-right: 5px; | |||||
} | |||||
} | } |
@@ -31,12 +31,6 @@ | |||||
@import 'my_account'; | @import 'my_account'; | ||||
body { | |||||
@include media-breakpoint-up(sm) { | |||||
background-color: var(--bg-color); | |||||
} | |||||
} | |||||
.ql-editor.read-mode { | .ql-editor.read-mode { | ||||
padding: 0; | padding: 0; | ||||
line-height: 1.6; | line-height: 1.6; | ||||
@@ -1,7 +1,8 @@ | |||||
//styles for my account and edit-profile page | //styles for my account and edit-profile page | ||||
@include media-breakpoint-up(sm) { | @include media-breakpoint-up(sm) { | ||||
body[data-path="me"], | body[data-path="me"], | ||||
body[data-path="list"] { | |||||
body[data-path="list"], | |||||
body[data-path="update-profile"] { | |||||
background-color: var(--bg-color); | background-color: var(--bg-color); | ||||
} | } | ||||
} | } | ||||
@@ -6,7 +6,7 @@ | |||||
.breadcrumb-container.container { | .breadcrumb-container.container { | ||||
@include media-breakpoint-up(sm) { | @include media-breakpoint-up(sm) { | ||||
padding-left: var(--padding-sm); | |||||
padding-left: 0; | |||||
} | } | ||||
} | } | ||||
@@ -34,6 +34,58 @@ class TestDBUpdate(unittest.TestCase): | |||||
self.assertEqual(fieldtype, table_column.type) | self.assertEqual(fieldtype, table_column.type) | ||||
self.assertIn(cstr(table_column.default) or 'NULL', [cstr(default), "'{}'".format(default)]) | self.assertIn(cstr(table_column.default) or 'NULL', [cstr(default), "'{}'".format(default)]) | ||||
def test_index_and_unique_constraints(self): | |||||
doctype = "User" | |||||
frappe.reload_doctype('User', force=True) | |||||
frappe.model.meta.trim_tables('User') | |||||
make_property_setter(doctype, 'restrict_ip', 'unique', '1', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertTrue(restrict_ip_in_table.unique) | |||||
make_property_setter(doctype, 'restrict_ip', 'unique', '0', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertFalse(restrict_ip_in_table.unique) | |||||
make_property_setter(doctype, 'restrict_ip', 'search_index', '1', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertTrue(restrict_ip_in_table.index) | |||||
make_property_setter(doctype, 'restrict_ip', 'search_index', '0', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertFalse(restrict_ip_in_table.index) | |||||
make_property_setter(doctype, 'restrict_ip', 'search_index', '1', 'Int') | |||||
make_property_setter(doctype, 'restrict_ip', 'unique', '1', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertTrue(restrict_ip_in_table.index) | |||||
self.assertTrue(restrict_ip_in_table.unique) | |||||
make_property_setter(doctype, 'restrict_ip', 'search_index', '1', 'Int') | |||||
make_property_setter(doctype, 'restrict_ip', 'unique', '0', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertTrue(restrict_ip_in_table.index) | |||||
self.assertFalse(restrict_ip_in_table.unique) | |||||
make_property_setter(doctype, 'restrict_ip', 'search_index', '0', 'Int') | |||||
make_property_setter(doctype, 'restrict_ip', 'unique', '1', 'Int') | |||||
frappe.db.updatedb(doctype) | |||||
restrict_ip_in_table = get_table_column("User", "restrict_ip") | |||||
self.assertFalse(restrict_ip_in_table.index) | |||||
self.assertTrue(restrict_ip_in_table.unique) | |||||
# explicitly make a text index | |||||
frappe.db.add_index(doctype, ["email_signature(200)"]) | |||||
frappe.db.updatedb(doctype) | |||||
email_sig_column = get_table_column("User", "email_signature") | |||||
self.assertEqual(email_sig_column.index, 1) | |||||
def get_fieldtype_from_def(field_def): | def get_fieldtype_from_def(field_def): | ||||
fieldtuple = frappe.db.type_map.get(field_def.fieldtype, ('', 0)) | fieldtuple = frappe.db.type_map.get(field_def.fieldtype, ('', 0)) | ||||
fieldtype = fieldtuple[0] | fieldtype = fieldtuple[0] | ||||
@@ -69,4 +121,8 @@ def get_other_fields_meta(meta): | |||||
fields = dict(default_fields_map, **optional_fields_map) | fields = dict(default_fields_map, **optional_fields_map) | ||||
field_map = [frappe._dict({'fieldname': field, 'fieldtype': _type, 'length': _length}) for field, (_type, _length) in fields.items()] | field_map = [frappe._dict({'fieldname': field, 'fieldtype': _type, 'length': _length}) for field, (_type, _length) in fields.items()] | ||||
return field_map | |||||
return field_map | |||||
def get_table_column(doctype, fieldname): | |||||
table_columns = frappe.db.get_table_columns_description('tab{}'.format(doctype)) | |||||
return find(table_columns, lambda d: d.get('name') == fieldname) |
@@ -15,3 +15,18 @@ class TestPatches(unittest.TestCase): | |||||
self.assertTrue(frappe.get_attr(patchmodule.split()[0] + ".execute")) | self.assertTrue(frappe.get_attr(patchmodule.split()[0] + ".execute")) | ||||
frappe.flags.in_install = False | frappe.flags.in_install = False | ||||
def test_get_patch_list(self): | |||||
pre = patch_handler.get_patches_from_app("frappe", patch_handler.PatchType.pre_model_sync) | |||||
post = patch_handler.get_patches_from_app("frappe", patch_handler.PatchType.post_model_sync) | |||||
all_patches = patch_handler.get_patches_from_app("frappe") | |||||
self.assertGreater(len(pre), 0) | |||||
self.assertGreater(len(post), 0) | |||||
self.assertEqual(len(all_patches), len(pre) + len(post)) | |||||
def test_all_patches_are_marked_completed(self): | |||||
all_patches = patch_handler.get_patches_from_app("frappe") | |||||
finished_patches = frappe.db.count("Patch Log") | |||||
self.assertGreaterEqual(finished_patches, len(all_patches)) |
@@ -222,9 +222,10 @@ def disable_2fa(): | |||||
def toggle_2fa_all_role(state=None): | def toggle_2fa_all_role(state=None): | ||||
'''Enable or disable 2fa for 'all' role on the system.''' | '''Enable or disable 2fa for 'all' role on the system.''' | ||||
all_role = frappe.get_doc('Role','All') | all_role = frappe.get_doc('Role','All') | ||||
if state is None: | |||||
state = False if all_role.two_factor_auth == True else False | |||||
if state not in [True, False]: return | |||||
state = state if state is not None else False | |||||
if type(state) != bool: | |||||
return | |||||
all_role.two_factor_auth = cint(state) | all_role.two_factor_auth = cint(state) | ||||
all_role.save(ignore_permissions=True) | all_role.save(ignore_permissions=True) | ||||
frappe.db.commit() | frappe.db.commit() | ||||
@@ -248,10 +248,11 @@ def create_topic_and_reply(web_page): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def update_webform_to_multistep(): | def update_webform_to_multistep(): | ||||
doc = frappe.get_doc("Web Form", "edit-profile") | |||||
_doc = frappe.copy_doc(doc) | |||||
_doc.is_multi_step_form = 1 | |||||
_doc.title = "update-profile-duplicate" | |||||
_doc.route = "update-profile-duplicate" | |||||
_doc.is_standard = False | |||||
_doc.save() | |||||
if not frappe.db.exists("Web Form", "update-profile-duplicate"): | |||||
doc = frappe.get_doc("Web Form", "edit-profile") | |||||
_doc = frappe.copy_doc(doc) | |||||
_doc.is_multi_step_form = 1 | |||||
_doc.title = "update-profile-duplicate" | |||||
_doc.route = "update-profile-duplicate" | |||||
_doc.is_standard = False | |||||
_doc.save() |
@@ -1393,6 +1393,7 @@ Is Spam,ist Spam, | |||||
Is Standard,Ist Standard, | Is Standard,Ist Standard, | ||||
Is Submittable,Ist übertragbar, | Is Submittable,Ist übertragbar, | ||||
Is Table,ist eine Tabelle, | Is Table,ist eine Tabelle, | ||||
Is Template, Ist Vorlage, | |||||
Is Your Company Address,Ist Ihre Unternehmensadresse, | Is Your Company Address,Ist Ihre Unternehmensadresse, | ||||
It is risky to delete this file: {0}. Please contact your System Manager.,"Es ist riskant, diese Datei zu löschen: {0}. Bitte kontaktieren Sie Ihren System-Manager.", | It is risky to delete this file: {0}. Please contact your System Manager.,"Es ist riskant, diese Datei zu löschen: {0}. Bitte kontaktieren Sie Ihren System-Manager.", | ||||
Item cannot be added to its own descendents,Artikel kann nicht zu seinen eigenen Abkömmlingen hinzugefügt werden, | Item cannot be added to its own descendents,Artikel kann nicht zu seinen eigenen Abkömmlingen hinzugefügt werden, | ||||
@@ -901,10 +901,11 @@ def dictify(arg): | |||||
def add_user_info(user, user_info): | def add_user_info(user, user_info): | ||||
if user not in user_info: | if user not in user_info: | ||||
info = frappe.db.get_value("User", | info = frappe.db.get_value("User", | ||||
user, ["full_name", "user_image", "name", 'email'], as_dict=True) or frappe._dict() | |||||
user, ["full_name", "user_image", "name", 'email', 'time_zone'], as_dict=True) or frappe._dict() | |||||
user_info[user] = frappe._dict( | user_info[user] = frappe._dict( | ||||
fullname = info.full_name or user, | fullname = info.full_name or user, | ||||
image = info.user_image, | image = info.user_image, | ||||
name = user, | name = user, | ||||
email = info.email | |||||
email = info.email, | |||||
time_zone = info.time_zone | |||||
) | ) |
@@ -20,11 +20,17 @@ from frappe.utils.redis_queue import RedisQueue | |||||
from frappe.utils.commands import log | from frappe.utils.commands import log | ||||
common_site_config = frappe.get_file_json("common_site_config.json") | |||||
custom_workers_config = common_site_config.get("workers", {}) | |||||
default_timeout = 300 | default_timeout = 300 | ||||
queue_timeout = { | queue_timeout = { | ||||
'long': 1500, | |||||
'default': 300, | |||||
'short': 300 | |||||
"default": default_timeout, | |||||
"short": default_timeout, | |||||
"long": 1500, | |||||
**{ | |||||
worker: config.get("timeout", default_timeout) | |||||
for worker, config in custom_workers_config.items() | |||||
} | |||||
} | } | ||||
redis_connection = None | redis_connection = None | ||||
@@ -1,6 +1,11 @@ | |||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | ||||
# License: MIT. See LICENSE | # License: MIT. See LICENSE | ||||
import frappe, os, re, git | |||||
import git | |||||
import os | |||||
import re | |||||
import frappe | |||||
from frappe.utils import touch_file, cstr | from frappe.utils import touch_file, cstr | ||||
def make_boilerplate(dest, app_name, no_git=False): | def make_boilerplate(dest, app_name, no_git=False): | ||||
@@ -22,7 +22,7 @@ class RedisWrapper(redis.Redis): | |||||
if shared: | if shared: | ||||
return key | return key | ||||
if user: | if user: | ||||
if user == True: | |||||
if user is True: | |||||
user = frappe.session.user | user = frappe.session.user | ||||
key = "user:{0}:{1}".format(user, key) | key = "user:{0}:{1}".format(user, key) | ||||
@@ -39,7 +39,7 @@ | |||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"in_list_view": 1, | "in_list_view": 1, | ||||
"label": "Fieldtype", | "label": "Fieldtype", | ||||
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" | |||||
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nTime\nSection Break\nColumn Break" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "label", | "fieldname": "label", | ||||
@@ -146,7 +146,7 @@ | |||||
], | ], | ||||
"istable": 1, | "istable": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-04-30 12:02:25.422345", | |||||
"modified": "2022-01-24 20:43:25.422345", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Form Field", | "name": "Web Form Field", | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"charts": [], | "charts": [], | ||||
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Website\", \"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Blog Post\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Blogger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Web Page\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Web Form\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Website Settings\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Blog\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Web Site\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Portal\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Knowledge Base\", \"col\": 4}}]", | |||||
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Website\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Blog Post\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Blogger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Page\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Setup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Blog\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Web Site\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Portal\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Knowledge Base\",\"col\":4}}]", | |||||
"creation": "2020-03-02 14:13:51.089373", | "creation": "2020-03-02 14:13:51.089373", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "Workspace", | "doctype": "Workspace", | ||||
@@ -232,7 +232,7 @@ | |||||
"type": "Link" | "type": "Link" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-08-05 12:16:03.154033", | |||||
"modified": "2022-01-13 17:49:41.527194", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Website", | "name": "Website", | ||||
@@ -241,7 +241,7 @@ | |||||
"public": 1, | "public": 1, | ||||
"restrict_to_domain": "", | "restrict_to_domain": "", | ||||
"roles": [], | "roles": [], | ||||
"sequence_id": 28, | |||||
"sequence_id": 28.0, | |||||
"shortcuts": [ | "shortcuts": [ | ||||
{ | { | ||||
"color": "Green", | "color": "Green", | ||||
@@ -2,9 +2,8 @@ | |||||
# License: MIT. See LICENSE | # License: MIT. See LICENSE | ||||
import frappe, os, copy, json, re | import frappe, os, copy, json, re | ||||
from frappe import _ | |||||
from frappe import _, get_module_path | |||||
from frappe.modules import get_doc_path | |||||
from frappe.core.doctype.access_log.access_log import make_access_log | from frappe.core.doctype.access_log.access_log import make_access_log | ||||
from frappe.utils import cint, sanitize_html, strip_html | from frappe.utils import cint, sanitize_html, strip_html | ||||
from frappe.utils.jinja_globals import is_rtl | from frappe.utils.jinja_globals import is_rtl | ||||
@@ -251,8 +250,9 @@ def get_print_format(doctype, print_format): | |||||
frappe.DoesNotExistError) | frappe.DoesNotExistError) | ||||
# server, find template | # server, find template | ||||
path = os.path.join(get_doc_path(frappe.db.get_value("DocType", doctype, "module"), | |||||
"Print Format", print_format.name), frappe.scrub(print_format.name) + ".html") | |||||
module = print_format.module or frappe.db.get_value("DocType", doctype, "module") | |||||
path = os.path.join(get_module_path(module, "Print Format", print_format.name), | |||||
frappe.scrub(print_format.name) + ".html") | |||||
if os.path.exists(path): | if os.path.exists(path): | ||||
with open(path, "r") as pffile: | with open(path, "r") as pffile: | ||||
@@ -2774,15 +2774,10 @@ nan@^2.13.2: | |||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" | ||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== | integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== | ||||
nanoid@^3.1.22: | |||||
version "3.1.22" | |||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" | |||||
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== | |||||
nanoid@^3.1.23: | |||||
version "3.1.23" | |||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" | |||||
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== | |||||
nanoid@^3.1.22, nanoid@^3.1.23: | |||||
version "3.2.0" | |||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" | |||||
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== | |||||
native-request@^1.0.5: | native-request@^1.0.5: | ||||
version "1.0.8" | version "1.0.8" | ||||