Merge branch 'develop' of github.com:frappe/frappe into thumbnail-for-images
This commit is contained in:
commit
51fa09c4ab
137 changed files with 3466 additions and 2009 deletions
2
.github/workflows/ui-tests.yml
vendored
2
.github/workflows/ui-tests.yml
vendored
|
|
@ -137,7 +137,7 @@ jobs:
|
|||
|
||||
- name: UI Tests
|
||||
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:
|
||||
CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ context('Control Barcode', () => {
|
|||
get_dialog_with_barcode().as('dialog');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.focus()
|
||||
.type('123456789')
|
||||
.blur();
|
||||
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');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.focus()
|
||||
.type('123456789')
|
||||
.blur();
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
|
|
|
|||
|
|
@ -19,18 +19,18 @@ context('Control Icon', () => {
|
|||
get_dialog_with_icon().as('dialog');
|
||||
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 => {
|
||||
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 => {
|
||||
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)
|
||||
}
|
||||
];
|
||||
|
||||
datetime_formats.forEach(d => {
|
||||
it(`test datetime format ${d.date_format} ${d.time_format}`, () => {
|
||||
cy.set_value('System Settings', 'System Settings', {
|
||||
|
|
|
|||
|
|
@ -77,11 +77,11 @@ context('MultiSelectDialog', () => {
|
|||
|
||||
it('tests more button', () => {
|
||||
cy.get_open_dialog()
|
||||
.get(`.frappe-control[data-fieldname="more_btn"]`)
|
||||
.get(`.frappe-control[data-fieldname="more_child_btn"]`)
|
||||
.should('exist')
|
||||
.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);
|
||||
});
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ context('MultiSelectDialog', () => {
|
|||
cy.get('@more-btn').find('button').click({force: true});
|
||||
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) {
|
||||
throw new Error("More button doesn't work");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ context('Report View', () => {
|
|||
cy.visit('/app/website');
|
||||
cy.insert_doc('DocType', custom_submittable_doctype, true);
|
||||
cy.clear_cache();
|
||||
});
|
||||
it('Field with enabled allow_on_submit should be editable.', () => {
|
||||
cy.insert_doc(doctype_name, {
|
||||
'title': 'Doc 1',
|
||||
'description': 'Random Text',
|
||||
|
|
@ -16,6 +14,8 @@ context('Report View', () => {
|
|||
// submit document
|
||||
'docstatus': 1
|
||||
}, 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.visit(`/app/List/${doctype_name}/Report`);
|
||||
// check status column added from docstatus
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ context('Timeline Email', () => {
|
|||
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.get('.list-row > .level-left > .list-subject').eq(0).click();
|
||||
|
||||
//Creating a new email
|
||||
cy.get('.timeline-actions > .btn').click();
|
||||
cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
|
||||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
|
||||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').type('Test Mail');
|
||||
|
||||
|
|
@ -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 > .dropdown-menu > li > .grey-link').eq(9).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.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click();
|
||||
|
||||
|
|
@ -57,11 +59,11 @@ context('Timeline Email', () => {
|
|||
cy.wait(500);
|
||||
|
||||
//To check if the discard button functionality in email is working correctly
|
||||
cy.get('.timeline-actions > .btn').click();
|
||||
cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
|
||||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
|
||||
cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click();
|
||||
cy.wait(500);
|
||||
cy.get('.timeline-actions > .btn').click();
|
||||
cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
|
||||
cy.wait(500);
|
||||
cy.get_field('recipients', 'MultiSelect').should('have.text', '');
|
||||
cy.get('.modal-header:visible > .modal-actions > .btn-modal-close > .icon').click();
|
||||
|
|
|
|||
|
|
@ -33,44 +33,39 @@ context('Workspace 2.0', () => {
|
|||
});
|
||||
|
||||
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(".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(".ce-block:last").find('.ce-paragraph').should('exist');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
@ -79,7 +74,10 @@ context('Workspace 2.0', () => {
|
|||
cy.get('.codex-editor__redactor .ce-block');
|
||||
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.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click();
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ def as_unicode(text, encoding='utf-8'):
|
|||
'''Convert to unicode if required'''
|
||||
if isinstance(text, str):
|
||||
return text
|
||||
elif text==None:
|
||||
elif text is None:
|
||||
return ''
|
||||
elif isinstance(text, bytes):
|
||||
return str(text, encoding)
|
||||
|
|
@ -294,7 +294,7 @@ def get_conf(site=None):
|
|||
|
||||
class init_site:
|
||||
def __init__(self, site=None):
|
||||
'''If site==None, initialize it for empty site ('') to load common_site_config.json'''
|
||||
'''If site is None, initialize it for empty site ('') to load common_site_config.json'''
|
||||
self.site = site or ''
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -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)
|
||||
|
||||
def emit_js(js, user=False, **kwargs):
|
||||
if user == False:
|
||||
if user is False:
|
||||
user = session.user
|
||||
publish_realtime('eval_js', js, user=user, **kwargs)
|
||||
|
||||
|
|
@ -1661,7 +1661,7 @@ def local_cache(namespace, key, generator, regenerate_if_none=False):
|
|||
if key not in local.cache[namespace]:
|
||||
local.cache[namespace][key] = generator()
|
||||
|
||||
elif local.cache[namespace][key]==None and regenerate_if_none:
|
||||
elif local.cache[namespace][key] is None and regenerate_if_none:
|
||||
# if key exists but the previous result was None
|
||||
local.cache[namespace][key] = generator()
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ class LoginManager:
|
|||
self.user_type = None
|
||||
|
||||
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
|
||||
|
||||
# run login triggers
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ def apply(doc=None, method=None, doctype=None, name=None):
|
|||
for todo in todos_to_close:
|
||||
_todo = frappe.get_doc("ToDo", todo)
|
||||
_todo.status = "Closed"
|
||||
_todo.save()
|
||||
_todo.save(ignore_permissions=True)
|
||||
break
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-05 12:16:02.839181",
|
||||
"modified": "2022-01-13 17:48:48.456763",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 26,
|
||||
"sequence_id": 26.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "ToDo",
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ def load_conf_settings(bootinfo):
|
|||
if key in conf: bootinfo[key] = conf.get(key)
|
||||
|
||||
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.dashboards = frappe.get_all("Dashboard")
|
||||
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ def build_table_count_cache():
|
|||
data = (
|
||||
frappe.qb.from_(information_schema.tables).select(table_name, table_rows)
|
||||
).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)
|
||||
|
||||
return counts
|
||||
|
|
|
|||
|
|
@ -952,7 +952,7 @@ def trim_database(context, dry_run, format, no_backup):
|
|||
doctype_tables = frappe.get_all("DocType", pluck="name")
|
||||
|
||||
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):
|
||||
TABLES_TO_DROP.append(x)
|
||||
|
||||
|
|
@ -966,7 +966,7 @@ def trim_database(context, dry_run, format, no_backup):
|
|||
|
||||
odb = scheduled_backup(
|
||||
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,
|
||||
force=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import evaluate_filters
|
||||
from frappe.model.naming import parse_naming_series
|
||||
from frappe import _
|
||||
|
||||
class DocumentNamingRule(Document):
|
||||
|
|
@ -27,7 +28,9 @@ class DocumentNamingRule(Document):
|
|||
return
|
||||
|
||||
counter = frappe.db.get_value(self.doctype, self.name, 'counter', for_update=True) or 0
|
||||
doc.name = self.prefix + ('%0'+str(self.prefix_digits)+'d') % (counter + 1)
|
||||
naming_series = parse_naming_series(self.prefix, doc=doc)
|
||||
|
||||
doc.name = naming_series + ('%0'+str(self.prefix_digits)+'d') % (counter + 1)
|
||||
frappe.db.set_value(self.doctype, self.name, 'counter', counter + 1)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
|
|
@ -34,19 +34,7 @@ def run_server_script_for_doc_event(doc, event):
|
|||
if scripts:
|
||||
# run all scripts for this doctype + event
|
||||
for script_name in scripts:
|
||||
try:
|
||||
frappe.get_doc('Server Script', script_name).execute_doc(doc)
|
||||
except Exception as e:
|
||||
message = frappe._('Error executing Server Script {0}. Open Browser Console to see traceback.').format(
|
||||
frappe.utils.get_link_to_form('Server Script', script_name)
|
||||
)
|
||||
exception = type(e)
|
||||
if getattr(frappe, 'request', None):
|
||||
# all exceptions throw 500 which is internal server error
|
||||
# however server script error is a user error
|
||||
# so we should throw 417 which is expectation failed
|
||||
exception.http_status_code = 417
|
||||
frappe.throw(title=frappe._('Server Script Error'), msg=message, exc=exception)
|
||||
frappe.get_doc('Server Script', script_name).execute_doc(doc)
|
||||
|
||||
def get_server_script_map():
|
||||
# fetch cached server script methods
|
||||
|
|
|
|||
|
|
@ -31,4 +31,15 @@ class test(Document):
|
|||
def get_value(self, fields, filters, **kwargs):
|
||||
# return []
|
||||
with open("data_file.json", "r") as read_file:
|
||||
return [json.load(read_file)]
|
||||
return [json.load(read_file)]
|
||||
|
||||
def get_count(self, args):
|
||||
# return []
|
||||
with open("data_file.json", "r") as read_file:
|
||||
return [json.load(read_file)]
|
||||
|
||||
def get_stats(self, args):
|
||||
# return []
|
||||
with open("data_file.json", "r") as read_file:
|
||||
return [json.load(read_file)]
|
||||
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ class User(Document):
|
|||
|
||||
frappe.sendmail(recipients=self.email, sender=sender, subject=subject,
|
||||
template=template, args=args, header=[subject, "green"],
|
||||
delayed=(not now) if now!=None else self.flags.delay_emails, retry=3)
|
||||
delayed=(not now) if now is not None else self.flags.delay_emails, retry=3)
|
||||
|
||||
def a_system_manager_should_exist(self):
|
||||
if not self.get_other_system_managers():
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class UserType(Document):
|
|||
|
||||
for child_table in doc.get_table_fields():
|
||||
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)
|
||||
|
||||
if select_doctypes:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -222,7 +222,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-05 21:14:52.384816",
|
||||
"modified": "2022-01-13 17:26:02.736366",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Build",
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 5,
|
||||
"sequence_id": 5.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"doc_view": "",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -367,7 +367,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-05 12:16:03.456174",
|
||||
"modified": "2022-01-13 17:49:59.586909",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Settings",
|
||||
|
|
@ -376,7 +376,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 29,
|
||||
"sequence_id": 29.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"icon": "setting",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-05 12:16:03.010205",
|
||||
"modified": "2022-01-13 17:49:08.912772",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Users",
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 27,
|
||||
"sequence_id": 27.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "User",
|
||||
|
|
|
|||
|
|
@ -107,20 +107,26 @@ class CustomizeForm(Document):
|
|||
def set_name_translation(self):
|
||||
'''Create, update custom translation for this doctype'''
|
||||
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
|
||||
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):
|
||||
doc_type = self.doc_type
|
||||
|
|
@ -377,7 +383,7 @@ class CustomizeForm(Document):
|
|||
|
||||
def make_property_setter(self, prop, value, property_type, fieldname=None,
|
||||
apply_on=None, row_name = None):
|
||||
delete_property_setter(self.doc_type, prop, fieldname)
|
||||
delete_property_setter(self.doc_type, prop, fieldname, row_name)
|
||||
|
||||
property_value = self.get_existing_property_value(prop, fieldname)
|
||||
|
||||
|
|
|
|||
|
|
@ -304,3 +304,25 @@ class TestCustomizeForm(unittest.TestCase):
|
|||
|
||||
action = [d for d in event.actions if d.label=='Test Action']
|
||||
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, "")
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class PropertySetter(Document):
|
|||
def validate(self):
|
||||
self.validate_fieldtype_change()
|
||||
if self.is_new():
|
||||
delete_property_setter(self.doc_type, self.property, self.field_name)
|
||||
delete_property_setter(self.doc_type, self.property, self.field_name, self.row_name)
|
||||
|
||||
# clear cache
|
||||
frappe.clear_cache(doctype = self.doc_type)
|
||||
|
|
@ -91,11 +91,13 @@ def make_property_setter(doctype, fieldname, property, value, property_type, for
|
|||
property_setter.insert()
|
||||
return property_setter
|
||||
|
||||
def delete_property_setter(doc_type, property, field_name=None):
|
||||
def delete_property_setter(doc_type, property, field_name=None, row_name=None):
|
||||
"""delete other property setters on this, if this is new"""
|
||||
filters = dict(doc_type = doc_type, property=property)
|
||||
filters = dict(doc_type=doc_type, property=property)
|
||||
if field_name:
|
||||
filters['field_name'] = field_name
|
||||
if row_name:
|
||||
filters["row_name"] = row_name
|
||||
|
||||
frappe.db.delete('Property Setter', filters)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-11-24 16:20:03.500885",
|
||||
"modified": "2022-01-13 17:28:08.345794",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customization",
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 8,
|
||||
"sequence_id": 8.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -245,9 +245,16 @@ class MariaDBDatabase(Database):
|
|||
column_name as 'name',
|
||||
column_type as 'type',
|
||||
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'
|
||||
from information_schema.columns
|
||||
from information_schema.columns as columns
|
||||
where table_name = '{table_name}' '''.format(table_name=table_name), as_dict=1)
|
||||
|
||||
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()))
|
||||
|
||||
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
|
||||
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 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:
|
||||
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."""
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode('utf-8')
|
||||
|
||||
|
||||
# MariaDB's driver treats None as an empty string
|
||||
# So Postgres should do the same
|
||||
|
||||
if s is None:
|
||||
if s is None:
|
||||
s = ''
|
||||
|
||||
if percent:
|
||||
|
|
@ -308,18 +308,20 @@ class PostgresDatabase(Database):
|
|||
WHEN 'timestamp without time zone' THEN 'timestamp'
|
||||
ELSE a.data_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,
|
||||
BOOL_OR(b.unique) AS unique
|
||||
FROM information_schema.columns a
|
||||
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
|
||||
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}'
|
||||
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):
|
||||
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()
|
||||
if column_defs: add_text += ',\n'.join(column_defs)
|
||||
|
||||
# index
|
||||
# index_defs = self.get_index_definitions()
|
||||
# TODO: set docstatus length
|
||||
# create table
|
||||
frappe.db.sql("""create table `%s` (
|
||||
|
|
@ -28,8 +26,25 @@ class PostgresTable(DBTable):
|
|||
idx bigint not null default '0',
|
||||
%s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text))
|
||||
|
||||
self.create_indexes()
|
||||
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):
|
||||
for col in self.columns.values():
|
||||
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(
|
||||
col.fieldname,
|
||||
get_definition(col.fieldtype, precision=col.precision, length=col.length),
|
||||
using_clause)
|
||||
)
|
||||
using_clause
|
||||
))
|
||||
|
||||
for col in self.set_default:
|
||||
if col.fieldname=="name":
|
||||
|
|
@ -73,37 +88,54 @@ class PostgresTable(DBTable):
|
|||
|
||||
query.append("ALTER COLUMN `{}` SET DEFAULT {}".format(col.fieldname, col_default))
|
||||
|
||||
create_index_query = ""
|
||||
create_contraint_query = ""
|
||||
for col in self.add_index:
|
||||
# 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,
|
||||
table_name=self.table_name,
|
||||
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:
|
||||
# primary key
|
||||
if col.fieldname != 'name':
|
||||
# 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))
|
||||
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]
|
||||
|
||||
for dt in doctype:
|
||||
dt = re.sub("tab", "", dt)
|
||||
dt = re.sub("^tab", "", dt)
|
||||
if not frappe.has_permission(
|
||||
dt,
|
||||
"select",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class DBTable:
|
|||
self.change_name = []
|
||||
self.add_unique = []
|
||||
self.add_index = []
|
||||
self.drop_unique = []
|
||||
self.drop_index = []
|
||||
self.set_default = []
|
||||
|
||||
|
|
@ -219,8 +220,10 @@ class DbColumn:
|
|||
self.table.change_type.append(self)
|
||||
|
||||
# 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)
|
||||
elif (current_def['unique'] and not self.unique) and column_type not in ('text', 'longtext'):
|
||||
self.table.drop_unique.append(self)
|
||||
|
||||
# default
|
||||
if (self.default_changed(current_def)
|
||||
|
|
@ -230,9 +233,7 @@ class DbColumn:
|
|||
self.table.set_default.append(self)
|
||||
|
||||
# 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)
|
||||
|
||||
elif (not current_def['index'] and self.set_index) and not (column_type in ('text', 'longtext')):
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ def set_default(key, value, parent, parenttype="__default"):
|
|||
"defkey": key,
|
||||
"parent": parent
|
||||
})
|
||||
if value != None:
|
||||
if value is not None:
|
||||
add_default(key, value, parent)
|
||||
else:
|
||||
_clear_cache(parent)
|
||||
|
|
@ -187,7 +187,7 @@ def get_defaults_for(parent="__default"):
|
|||
"""get all defaults"""
|
||||
defaults = frappe.cache().hget("defaults", parent)
|
||||
|
||||
if defaults==None:
|
||||
if defaults is None:
|
||||
# sort descending because first default must get precedence
|
||||
table = DocType("DefaultValue")
|
||||
res = frappe.qb.from_(table).where(
|
||||
|
|
|
|||
|
|
@ -56,31 +56,6 @@ class Workspace:
|
|||
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()
|
||||
|
||||
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):
|
||||
"""Returns true if Has Role is not set or the user is allowed."""
|
||||
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
|
||||
"""
|
||||
try:
|
||||
wspace = Workspace(loads(page))
|
||||
wspace.build_workspace()
|
||||
workspace = Workspace(loads(page))
|
||||
workspace.build_workspace()
|
||||
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:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
return {}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_wspace_sidebar_items():
|
||||
def get_workspace_sidebar_items():
|
||||
"""Get list of sidebar items for desk"""
|
||||
has_access = "Workspace Manager" in frappe.get_roles()
|
||||
|
||||
|
|
@ -385,8 +360,8 @@ def get_wspace_sidebar_items():
|
|||
# Filter Page based on Permission
|
||||
for page in all_pages:
|
||||
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:
|
||||
pages.append(page)
|
||||
elif page.for_user == frappe.session.user:
|
||||
|
|
@ -453,25 +428,24 @@ def get_custom_report_list(module):
|
|||
return out
|
||||
|
||||
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
|
||||
if widgets:
|
||||
clean_up(doc, blocks)
|
||||
clean_up(doc, blocks)
|
||||
|
||||
try:
|
||||
doc.save(ignore_permissions=True)
|
||||
except (ValidationError, TypeError) as e:
|
||||
# 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
|
||||
log = \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:label",
|
||||
"beta": 1,
|
||||
"creation": "2020-01-23 13:45:59.470592",
|
||||
|
|
@ -141,7 +142,7 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "sequence_id",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Float",
|
||||
"label": "Sequence Id"
|
||||
},
|
||||
{
|
||||
|
|
@ -158,7 +159,7 @@
|
|||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-16 12:01:06.450622",
|
||||
"modified": "2021-12-15 19:33:00.805265",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import frappe
|
|||
from frappe import _
|
||||
from frappe.modules.export_file import export_to_files
|
||||
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.utils import validate_route_conflict
|
||||
|
||||
|
|
@ -121,77 +122,157 @@ def get_report_type(report):
|
|||
report_type = frappe.get_value("Report", report, "report_type")
|
||||
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()
|
||||
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)
|
||||
if save:
|
||||
doc = frappe.new_doc('Workspace')
|
||||
doc.title = title
|
||||
doc.icon = icon
|
||||
doc.content = blocks
|
||||
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])
|
||||
filters = {
|
||||
'public': public,
|
||||
'label': title
|
||||
}
|
||||
|
||||
doc.content = blocks
|
||||
doc.save(ignore_permissions=True)
|
||||
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])
|
||||
|
||||
if loads(new_widgets):
|
||||
save_new_widget(doc, title, blocks, new_widgets)
|
||||
doc.content = blocks
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
if loads(sb_public_items) or loads(sb_private_items):
|
||||
sort_pages(loads(sb_public_items), loads(sb_private_items))
|
||||
|
||||
if loads(deleted_pages):
|
||||
return delete_pages(loads(deleted_pages))
|
||||
save_new_widget(doc, title, blocks, new_widgets)
|
||||
|
||||
return {"name": title, "public": public, "label": doc.label}
|
||||
|
||||
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")}
|
||||
@frappe.whitelist()
|
||||
def update_page(name, title, icon, parent, public):
|
||||
public = frappe.parse_json(public)
|
||||
|
||||
if frappe.db.exists("Workspace", page.get("name")):
|
||||
frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True)
|
||||
doc = frappe.get_doc("Workspace", name)
|
||||
|
||||
return {"name": "Home", "public": 1, "label": "Home"}
|
||||
filters = {
|
||||
'parent_page': doc.title,
|
||||
'public': doc.public
|
||||
}
|
||||
child_docs = frappe.get_list("Workspace", filters=filters)
|
||||
|
||||
if doc:
|
||||
doc.title = title
|
||||
doc.icon = icon
|
||||
doc.parent_page = parent
|
||||
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)
|
||||
|
||||
if name != doc.label:
|
||||
rename_doc("Workspace", name, doc.label, force=True, ignore_permissions=True)
|
||||
|
||||
# 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)
|
||||
|
||||
return {"name": doc.title, "public": doc.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)
|
||||
|
||||
return doc
|
||||
|
||||
@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": page.get("name"), "public": page.get("public"), "title": page.get("title")}
|
||||
|
||||
@frappe.whitelist()
|
||||
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:
|
||||
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():
|
||||
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 page in wspace_pages:
|
||||
for page in workspace_pages:
|
||||
if page.title == d.get('title'):
|
||||
doc = frappe.get_doc('Workspace', page.name)
|
||||
doc.sequence_id = seq + 1
|
||||
|
|
@ -199,6 +280,27 @@ def sort_page(wspace_pages, pages):
|
|||
doc.save(ignore_permissions=True)
|
||||
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):
|
||||
return frappe.get_list("Workspace", fields=fields, filters=filters, order_by='sequence_id asc')
|
||||
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ def get_last_modified(doctype):
|
|||
raise
|
||||
|
||||
# hack: save as -1 so that it is cached
|
||||
if last_modified==None:
|
||||
if last_modified is None:
|
||||
last_modified = -1
|
||||
|
||||
return last_modified
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def get_energy_points_percentage_chart_data(user, field):
|
|||
as_list = True)
|
||||
|
||||
return {
|
||||
"labels": [r[0] for r in result if r[0] != None],
|
||||
"labels": [r[0] for r in result if r[0] is not None],
|
||||
"datasets": [{
|
||||
"values": [r[1] for r in result]
|
||||
}]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from frappe.utils import add_user_info
|
|||
def get():
|
||||
args = get_form_params()
|
||||
# If virtual doctype get data from controller het_list method
|
||||
if frappe.db.get_value("DocType", filters={"name": args.doctype}, fieldname="is_virtual"):
|
||||
if is_virtual_doctype(args.doctype):
|
||||
controller = get_controller(args.doctype)
|
||||
data = compress(controller(args.doctype).get_list(args))
|
||||
else:
|
||||
|
|
@ -29,17 +29,31 @@ def get():
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_list():
|
||||
# uncompressed (refactored from frappe.model.db_query.get_list)
|
||||
return execute(**get_form_params())
|
||||
args = get_form_params()
|
||||
|
||||
if is_virtual_doctype(args.doctype):
|
||||
controller = get_controller(args.doctype)
|
||||
data = controller(args.doctype).get_list(args)
|
||||
else:
|
||||
# uncompressed (refactored from frappe.model.db_query.get_list)
|
||||
data = execute(**args)
|
||||
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_count():
|
||||
args = get_form_params()
|
||||
|
||||
distinct = 'distinct ' if args.distinct=='true' else ''
|
||||
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
|
||||
return execute(**args)[0].get('total_count')
|
||||
if is_virtual_doctype(args.doctype):
|
||||
controller = get_controller(args.doctype)
|
||||
data = controller(args.doctype).get_count(args)
|
||||
else:
|
||||
distinct = 'distinct ' if args.distinct=='true' else ''
|
||||
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
|
||||
data = execute(**args)[0].get('total_count')
|
||||
|
||||
return data
|
||||
|
||||
def execute(doctype, *args, **kwargs):
|
||||
return DatabaseQuery(doctype).execute(*args, **kwargs)
|
||||
|
|
@ -438,7 +452,14 @@ def get_sidebar_stats(stats, doctype, filters=None):
|
|||
if filters is None:
|
||||
filters = []
|
||||
|
||||
return {"stats": get_stats(stats, doctype, filters)}
|
||||
if is_virtual_doctype(doctype):
|
||||
controller = get_controller(doctype)
|
||||
args = {"stats": stats, "filters": filters}
|
||||
data = controller(doctype).get_stats(args)
|
||||
else:
|
||||
data = get_stats(stats, doctype, filters)
|
||||
|
||||
return {"stats": data}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
@ -560,7 +581,7 @@ def get_match_cond(doctype, as_condition=True):
|
|||
return ((' and ' + cond) if cond else "").replace("%", "%%")
|
||||
|
||||
def build_match_conditions(doctype, user=None, as_condition=True):
|
||||
match_conditions = DatabaseQuery(doctype, user=user).build_match_conditions(as_condition=as_condition)
|
||||
match_conditions = DatabaseQuery(doctype, user=user).build_match_conditions(as_condition=as_condition)
|
||||
if as_condition:
|
||||
return match_conditions.replace("%", "%%")
|
||||
else:
|
||||
|
|
@ -598,3 +619,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with
|
|||
else:
|
||||
cond = ''
|
||||
return cond
|
||||
|
||||
def is_virtual_doctype(doctype):
|
||||
return frappe.db.get_value("DocType", doctype, "is_virtual")
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
else:
|
||||
filters.append([doctype, f[0], "=", f[1]])
|
||||
|
||||
if filters==None:
|
||||
if filters is None:
|
||||
filters = []
|
||||
or_filters = []
|
||||
|
||||
|
|
|
|||
19
frappe/installer.py
Executable file → Normal file
19
frappe/installer.py
Executable file → Normal file
|
|
@ -154,7 +154,7 @@ def install_app(name, verbose=False, set_as_patched=True):
|
|||
|
||||
for before_install in app_hooks.before_install or []:
|
||||
out = frappe.get_attr(before_install)()
|
||||
if out==False:
|
||||
if out is False:
|
||||
return
|
||||
|
||||
if name != "frappe":
|
||||
|
|
@ -346,14 +346,15 @@ def post_install(rebuild_website=False):
|
|||
|
||||
|
||||
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():
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ def get_latest_backup_file(with_files=False):
|
|||
return database, config
|
||||
|
||||
|
||||
def get_file_size(file_path, unit):
|
||||
if not unit:
|
||||
unit = "MB"
|
||||
|
||||
def get_file_size(file_path, unit='MB'):
|
||||
file_size = os.path.getsize(file_path)
|
||||
|
||||
memory_size_unit_mapper = {"KB": 1, "MB": 2, "GB": 3, "TB": 4}
|
||||
|
|
@ -99,7 +96,7 @@ def get_chunk_site(file_size):
|
|||
def validate_file_size():
|
||||
frappe.flags.create_new_backup = True
|
||||
latest_file, site_config = get_latest_backup_file()
|
||||
file_size = get_file_size(latest_file, unit="GB")
|
||||
file_size = get_file_size(latest_file, unit="GB") if latest_file else 0
|
||||
|
||||
if file_size > 1:
|
||||
frappe.flags.create_new_backup = False
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
|
|
@ -260,7 +260,7 @@
|
|||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-05 12:16:00.355268",
|
||||
"modified": "2022-01-13 17:39:01.292154",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Integrations",
|
||||
|
|
@ -269,7 +269,7 @@
|
|||
"public": 1,
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 15,
|
||||
"sequence_id": 15.0,
|
||||
"shortcuts": [],
|
||||
"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.search.website_search import build_index_for_all_routes
|
||||
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):
|
||||
|
|
@ -59,16 +61,13 @@ Otherwise, check the server logs and ensure that all the required services are r
|
|||
|
||||
clear_global_cache()
|
||||
|
||||
#run before_migrate hooks
|
||||
for app in frappe.get_installed_apps():
|
||||
for fn in frappe.get_hooks('before_migrate', app_name=app):
|
||||
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.modules.patch_handler.run_all(skip_failing=skip_failing, patch_type=PatchType.post_model_sync)
|
||||
frappe.translate.clear_cache()
|
||||
sync_jobs()
|
||||
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()
|
||||
|
||||
# syncs statics
|
||||
# syncs static files
|
||||
clear_website_cache()
|
||||
|
||||
# updating installed applications data
|
||||
frappe.get_single('Installed Applications').update_versions()
|
||||
|
||||
#run after_migrate hooks
|
||||
for app in frappe.get_installed_apps():
|
||||
for fn in frappe.get_hooks('after_migrate', app_name=app):
|
||||
frappe.get_attr(fn)()
|
||||
|
||||
# build web_routes index
|
||||
if not skip_search_index:
|
||||
# Run this last as it updates the current session
|
||||
print('Building search index for {}'.format(frappe.local.site))
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ class BaseDocument(object):
|
|||
...
|
||||
})
|
||||
"""
|
||||
if value==None:
|
||||
if value is None:
|
||||
value={}
|
||||
if isinstance(value, (dict, BaseDocument)):
|
||||
if not self.__dict__.get(key):
|
||||
|
|
@ -272,7 +272,7 @@ class BaseDocument(object):
|
|||
)):
|
||||
d[fieldname] = str(d[fieldname])
|
||||
|
||||
if d[fieldname] == None and ignore_nulls:
|
||||
if d[fieldname] is None and ignore_nulls:
|
||||
del d[fieldname]
|
||||
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ class DatabaseQuery(object):
|
|||
|
||||
elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, str) and
|
||||
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])):
|
||||
value = "" if f.value==None else f.value
|
||||
value = "" if f.value is None else f.value
|
||||
fallback = "''"
|
||||
|
||||
if f.operator.lower() in ("like", "not like") and isinstance(value, str):
|
||||
|
|
|
|||
|
|
@ -211,13 +211,13 @@ class Document(BaseDocument):
|
|||
|
||||
self.flags.notifications_executed = []
|
||||
|
||||
if ignore_permissions!=None:
|
||||
if ignore_permissions is not None:
|
||||
self.flags.ignore_permissions = ignore_permissions
|
||||
|
||||
if ignore_links!=None:
|
||||
if ignore_links is not None:
|
||||
self.flags.ignore_links = ignore_links
|
||||
|
||||
if ignore_mandatory!=None:
|
||||
if ignore_mandatory is not None:
|
||||
self.flags.ignore_mandatory = ignore_mandatory
|
||||
|
||||
self.set("__islocal", True)
|
||||
|
|
@ -297,7 +297,7 @@ class Document(BaseDocument):
|
|||
|
||||
self.flags.notifications_executed = []
|
||||
|
||||
if ignore_permissions!=None:
|
||||
if ignore_permissions is not None:
|
||||
self.flags.ignore_permissions = ignore_permissions
|
||||
|
||||
self.flags.ignore_version = frappe.flags.in_test if ignore_version is None else ignore_version
|
||||
|
|
@ -441,7 +441,7 @@ class Document(BaseDocument):
|
|||
values = self.as_dict()
|
||||
# format values
|
||||
for key, value in values.items():
|
||||
if value==None:
|
||||
if value is None:
|
||||
values[key] = ""
|
||||
return values
|
||||
|
||||
|
|
@ -489,7 +489,7 @@ class Document(BaseDocument):
|
|||
frappe.flags.currently_saving.append((self.doctype, self.name))
|
||||
|
||||
def set_docstatus(self):
|
||||
if self.docstatus==None:
|
||||
if self.docstatus is None:
|
||||
self.docstatus=0
|
||||
|
||||
for d in self.get_all_children():
|
||||
|
|
@ -887,14 +887,14 @@ class Document(BaseDocument):
|
|||
if (frappe.flags.in_import and frappe.flags.mute_emails) or frappe.flags.in_patch or frappe.flags.in_install:
|
||||
return
|
||||
|
||||
if self.flags.notifications_executed==None:
|
||||
if self.flags.notifications_executed is None:
|
||||
self.flags.notifications_executed = []
|
||||
|
||||
from frappe.email.doctype.notification.notification import evaluate_alert
|
||||
|
||||
if self.flags.notifications == None:
|
||||
if self.flags.notifications is None:
|
||||
alerts = frappe.cache().hget('notifications', self.doctype)
|
||||
if alerts==None:
|
||||
if alerts is None:
|
||||
alerts = frappe.get_all('Notification', fields=['name', 'event', 'method'],
|
||||
filters={'enabled': 1, 'document_type': self.doctype})
|
||||
frappe.cache().hset('notifications', self.doctype, alerts)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def get_dynamic_link_map(for_delete=False):
|
|||
|
||||
Note: Will not map single doctypes
|
||||
'''
|
||||
if getattr(frappe.local, 'dynamic_link_map', None)==None or frappe.flags.in_test:
|
||||
if getattr(frappe.local, 'dynamic_link_map', None) is None or frappe.flags.in_test:
|
||||
# Build from scratch
|
||||
dynamic_link_map = {}
|
||||
for df in get_dynamic_links():
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ def rename_doc(
|
|||
|
||||
if doctype=='DocType':
|
||||
rename_doctype(doctype, old, new, force)
|
||||
update_customizations(old, new)
|
||||
|
||||
update_attachments(doctype, old, new)
|
||||
|
||||
|
|
@ -174,6 +175,8 @@ def update_user_settings(old, new, link_fields):
|
|||
else:
|
||||
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):
|
||||
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
|
||||
""" 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
|
||||
|
||||
|
||||
[post_model_sync]
|
||||
app.module.patch3
|
||||
```
|
||||
|
||||
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")`
|
||||
"""
|
||||
Execute Patch Files
|
||||
|
||||
To run directly
|
||||
import configparser
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
python lib/wnf.py patch patch1, patch2 etc
|
||||
python lib/wnf.py patch -f patch1, patch2 etc
|
||||
import frappe
|
||||
|
||||
where patch1, patch2 is module name
|
||||
"""
|
||||
import frappe, frappe.permissions, time
|
||||
|
||||
class PatchError(Exception): pass
|
||||
class PatchError(Exception):
|
||||
pass
|
||||
|
||||
def run_all(skip_failing=False):
|
||||
|
||||
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"""
|
||||
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 = []
|
||||
|
||||
def run_patch(patch):
|
||||
try:
|
||||
if not run_single(patchmodule = patch):
|
||||
log(patch + ': failed: STOPPED')
|
||||
print(patch + ': failed: STOPPED')
|
||||
raise PatchError(patch)
|
||||
except Exception:
|
||||
if not skip_failing:
|
||||
raise
|
||||
else:
|
||||
log('Failed to execute patch')
|
||||
print('Failed to execute patch')
|
||||
|
||||
for patch in get_all_patches():
|
||||
patches = get_all_patches(patch_type=patch_type)
|
||||
|
||||
for patch in patches:
|
||||
if patch and (patch not in executed):
|
||||
run_patch(patch)
|
||||
|
||||
|
|
@ -40,18 +79,57 @@ def run_all(skip_failing=False):
|
|||
patch = patch.replace('finally:', '')
|
||||
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 = []
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# empty file
|
||||
if not parser.sections():
|
||||
return []
|
||||
|
||||
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):
|
||||
import frappe.modules
|
||||
run_single(method = frappe.modules.reload_doc, methodargs = args)
|
||||
|
|
@ -73,7 +151,7 @@ def execute_patch(patchmodule, method=None, methodargs=None):
|
|||
frappe.db.begin()
|
||||
start_time = time.time()
|
||||
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))
|
||||
if patchmodule:
|
||||
if patchmodule.startswith("finally:"):
|
||||
|
|
@ -96,7 +174,7 @@ def execute_patch(patchmodule, method=None, methodargs=None):
|
|||
frappe.db.commit()
|
||||
end_time = time.time()
|
||||
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
|
||||
|
||||
|
|
@ -109,10 +187,7 @@ def executed(patchmodule):
|
|||
if patchmodule.startswith('finally:'):
|
||||
# patches are saved without the finally: tag
|
||||
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):
|
||||
"""stop/start execution till patch is run"""
|
||||
|
|
@ -128,6 +203,3 @@ def check_session_stopped():
|
|||
if frappe.db.get_global("__session_status")=='stop':
|
||||
frappe.msgprint(frappe.db.get_global("__session_status_message"))
|
||||
raise frappe.SessionStopped('Session Stopped')
|
||||
|
||||
def log(msg):
|
||||
print (msg)
|
||||
|
|
|
|||
|
|
@ -257,6 +257,12 @@ def make_boilerplate(template, doc, opts=None):
|
|||
pass
|
||||
|
||||
def get_list(self, args):
|
||||
pass
|
||||
|
||||
def get_count(self, args):
|
||||
pass
|
||||
|
||||
def get_stats(self, args):
|
||||
pass"""
|
||||
|
||||
with open(target_file_path, 'w') as target:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
[pre_model_sync]
|
||||
frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3
|
||||
execute:frappe.utils.global_search.setup_global_search_table()
|
||||
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.v12_0.set_primary_key_in_series
|
||||
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.replace_null_values_in_tables
|
||||
frappe.patches.v12_0.reset_home_settings
|
||||
|
|
@ -123,6 +123,8 @@ frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
|
|||
frappe.patches.v12_0.remove_example_email_thread_notify
|
||||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v12_0.set_correct_url_in_files
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype')
|
||||
execute:frappe.reload_doc('custom', 'doctype', 'property_setter')
|
||||
frappe.patches.v13_0.remove_invalid_options_for_data_fields
|
||||
frappe.patches.v13_0.website_theme_custom_scss
|
||||
frappe.patches.v13_0.make_user_type
|
||||
|
|
@ -154,7 +156,6 @@ frappe.patches.v13_0.rename_notification_fields
|
|||
frappe.patches.v13_0.remove_duplicate_navbar_items
|
||||
frappe.patches.v13_0.set_social_icons
|
||||
frappe.patches.v12_0.set_default_password_reset_limit
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True)
|
||||
frappe.patches.v13_0.set_route_for_blog_category
|
||||
frappe.patches.v13_0.enable_custom_script
|
||||
frappe.patches.v13_0.update_newsletter_content_type
|
||||
|
|
@ -180,16 +181,17 @@ frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings
|
|||
frappe.patches.v13_0.remove_twilio_settings
|
||||
frappe.patches.v12_0.rename_uploaded_files_with_proper_name
|
||||
frappe.patches.v13_0.queryreport_columns
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype')
|
||||
frappe.patches.v13_0.jinja_hook
|
||||
frappe.patches.v13_0.update_notification_channel_if_empty
|
||||
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.copy_mail_data #08.03.21
|
||||
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.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.transform_todo_schema
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def execute():
|
|||
continue
|
||||
skip_for_doctype = user_permission.skip_for_doctype.split('\n')
|
||||
else: # while migrating from v10 -> v11
|
||||
if skip_for_doctype_map.get((user_permission.allow, user_permission.user)) == None:
|
||||
if skip_for_doctype_map.get((user_permission.allow, user_permission.user)) is None:
|
||||
skip_for_doctype = get_doctypes_to_skip(user_permission.allow, user_permission.user)
|
||||
# cache skip for doctype for same user and doctype
|
||||
skip_for_doctype_map[(user_permission.allow, user_permission.user)] = skip_for_doctype
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ import frappe
|
|||
|
||||
|
||||
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
|
||||
for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1, "use_imap": 1}):
|
||||
# get all data from Email Account
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("desk", "doctype", "kanban_board_column")
|
||||
indicator_map = {
|
||||
'blue': 'Blue',
|
||||
'orange': 'Orange',
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from frappe import _
|
|||
def execute():
|
||||
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)
|
||||
update_wspace(doc, seq, content)
|
||||
update_workspace(doc, seq, content)
|
||||
frappe.db.commit()
|
||||
|
||||
def create_content(doc):
|
||||
|
|
@ -49,7 +49,7 @@ def create_content(doc):
|
|||
del doc.links[doc.links.index(l)]
|
||||
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:
|
||||
doc.sequence_id = seq + 1
|
||||
doc.content = json.dumps(content)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ def print_has_permission_check_logs(func):
|
|||
frappe.flags['has_permission_check_logs'] = []
|
||||
result = func(*args, **kwargs)
|
||||
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
|
||||
# and if user is checking his own permission
|
||||
|
|
@ -559,7 +559,9 @@ def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=
|
|||
return (allowed_doc, default_doc) if with_default_doc else allowed_doc
|
||||
|
||||
def push_perm_check_log(log):
|
||||
if frappe.flags.get('has_permission_check_logs') == None: return
|
||||
if frappe.flags.get('has_permission_check_logs') is None:
|
||||
return
|
||||
|
||||
frappe.flags.get('has_permission_check_logs').append(_(log))
|
||||
|
||||
def has_child_table_permission(child_doctype, ptype="read", child_doc=None,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 104 KiB |
|
|
@ -96,6 +96,7 @@ import "./frappe/ui/sort_selector.js";
|
|||
|
||||
import "./frappe/change_log.html";
|
||||
import "./frappe/ui/workspace_loading_skeleton.html";
|
||||
import "./frappe/ui/workspace_sidebar_loading_skeleton.html";
|
||||
import "./frappe/desk.js";
|
||||
import "./frappe/query_string.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -214,19 +214,20 @@ frappe.Application = class Application {
|
|||
|
||||
email_password_prompt(email_account,user,i) {
|
||||
var me = this;
|
||||
const email_id = email_account[i]["email_id"];
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('Password missing in Email Account'),
|
||||
fields: [
|
||||
{
|
||||
'fieldname': '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
|
||||
},
|
||||
{
|
||||
"fieldname": "submit",
|
||||
"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",
|
||||
range: true,
|
||||
autoClose: true,
|
||||
toggleSelected: false
|
||||
toggleSelected: false,
|
||||
firstDay: frappe.datetime.get_first_day_of_the_week_index()
|
||||
};
|
||||
this.datepicker_options.dateFormat =
|
||||
(frappe.boot.sysdefaults.date_format || 'yyyy-mm-dd');
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ frappe.ui.form.ControlDynamicLink = class ControlDynamicLink extends frappe.ui.f
|
|||
get_options() {
|
||||
let options = '';
|
||||
if (this.df.get_options) {
|
||||
options = this.df.get_options();
|
||||
options = this.df.get_options(this);
|
||||
} else if (this.docname==null && cur_dialog) {
|
||||
//for dialog box
|
||||
options = cur_dialog.get_value(this.df.options);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ class BaseTimeline {
|
|||
this.wrapper = this.timeline_wrapper;
|
||||
this.timeline_items_wrapper = $(`<div class="timeline-items">`);
|
||||
this.timeline_actions_wrapper = $(`
|
||||
<div class="timeline-actions">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-items timeline-actions">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot"></div>
|
||||
<div class="timeline-content action-buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ class BaseTimeline {
|
|||
${label}
|
||||
</button>`);
|
||||
action_btn.click(action);
|
||||
this.timeline_actions_wrapper.append(action_btn);
|
||||
this.timeline_actions_wrapper.find('.action-buttons').append(action_btn);
|
||||
return action_btn;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,12 +77,14 @@ class FormTimeline extends BaseTimeline {
|
|||
const message = __("Add to this activity by mailing to {0}", [link.bold()]);
|
||||
|
||||
this.document_email_link_wrapper = $(`
|
||||
<div class="document-email-link-container">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-dot"></div>
|
||||
<span class="ellipsis">${message}</span>
|
||||
<div class="timeline-content">
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
this.timeline_wrapper.append(this.document_email_link_wrapper);
|
||||
this.timeline_actions_wrapper.append(this.document_email_link_wrapper);
|
||||
|
||||
this.document_email_link_wrapper
|
||||
.find('.document-email-link')
|
||||
|
|
|
|||
|
|
@ -943,7 +943,10 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
// re-enable buttons
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1146,8 +1149,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
subject: __(this.meta.name) + ': ' + this.docname,
|
||||
recipients: this.doc.email || this.doc.email_id || this.doc.contact_email,
|
||||
attach_document_print: true,
|
||||
message: message,
|
||||
real_name: this.doc.real_name || this.doc.contact_display || this.doc.contact_name
|
||||
message: message
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
$(btn).prop("disabled", true);
|
||||
|
||||
// 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)];
|
||||
|
||||
var freeze_message = working_label ? __(working_label) : "";
|
||||
|
|
@ -154,8 +154,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
if (error_fields.length) {
|
||||
let meta = frappe.get_meta(doc.doctype);
|
||||
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 {
|
||||
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]);
|
||||
}
|
||||
|
|
@ -276,4 +276,3 @@ frappe.ui.form.update_calling_link = (newdoc) => {
|
|||
frappe._from_link = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<ul class="list-unstyled sidebar-menu user-actions hidden"></ul>
|
||||
<ul class="list-unstyled sidebar-menu sidebar-image-section hidden-xs hidden-sm hide">
|
||||
<ul class="list-unstyled sidebar-menu sidebar-image-section hide">
|
||||
<li class="sidebar-image-wrapper">
|
||||
<img class="sidebar-image">
|
||||
<div class="sidebar-standard-image">
|
||||
|
|
|
|||
|
|
@ -183,7 +183,8 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
filters: (me.list_view.filter_area ? me.list_view.get_filters_for_args() : me.default_filters) || []
|
||||
},
|
||||
callback: function(r) {
|
||||
me.render_stat((r.message.stats || {})["_user_tags"]);
|
||||
let stats = (r.message.stats || {})["_user_tags"] || [];
|
||||
me.render_stat(stats);
|
||||
let stats_dropdown = me.sidebar.find('.list-stats-dropdown');
|
||||
frappe.utils.setup_search(stats_dropdown, '.stat-link', '.stat-label');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
frappe.render_template("list_view_permission_restrictions", {
|
||||
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() {
|
||||
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(
|
||||
`${__("Add")} ${frappe.router.doctype_layout || __(this.doctype)}`,
|
||||
label,
|
||||
() => {
|
||||
if (this.settings.primary_action) {
|
||||
this.settings.primary_action();
|
||||
|
|
@ -320,9 +325,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
setup_freeze_area() {
|
||||
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();
|
||||
this.$result.append(this.$freeze);
|
||||
}
|
||||
|
|
@ -460,8 +465,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
? __("No {0} found", [__(this.doctype)])
|
||||
: __("You haven't created a {0} yet", [__(this.doctype)]);
|
||||
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 =
|
||||
this.settings.empty_state_image ||
|
||||
"/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
|
||||
? `<p><button class="btn btn-primary btn-sm btn-new-doc hidden-xs">
|
||||
${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">
|
||||
|
|
@ -486,7 +493,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
if (this.list_view_settings && !this.list_view_settings.disable_count) {
|
||||
this.$result
|
||||
.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({
|
||||
shortcut: "down",
|
||||
action: () => handle_navigation("down"),
|
||||
description: __("Navigate list down"),
|
||||
description: __("Navigate list down", null, "Description of a list view shortcut"),
|
||||
page: this.page,
|
||||
});
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: "up",
|
||||
action: () => handle_navigation("up"),
|
||||
description: __("Navigate list up"),
|
||||
description: __("Navigate list up", null, "Description of a list view shortcut"),
|
||||
page: this.page,
|
||||
});
|
||||
|
||||
|
|
@ -1100,7 +1107,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
check_row($list_row);
|
||||
focus_next();
|
||||
},
|
||||
description: __("Select multiple list items"),
|
||||
description: __("Select multiple list items", null, "Description of a list view shortcut"),
|
||||
page: this.page,
|
||||
});
|
||||
|
||||
|
|
@ -1112,7 +1119,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
check_row($list_row);
|
||||
focus_prev();
|
||||
},
|
||||
description: __("Select multiple list items"),
|
||||
description: __("Select multiple list items", null, "Description of a list view shortcut"),
|
||||
page: this.page,
|
||||
});
|
||||
|
||||
|
|
@ -1126,7 +1133,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
description: __("Open list item"),
|
||||
description: __("Open list item", null, "Description of a list view shortcut"),
|
||||
page: this.page,
|
||||
});
|
||||
|
||||
|
|
@ -1140,7 +1147,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
description: __("Select list item"),
|
||||
description: __("Select list item", null, "Description of a list view shortcut"),
|
||||
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)) {
|
||||
items.push({
|
||||
label: __("Import"),
|
||||
label: __("Import", null, "Button in list view menu"),
|
||||
action: () =>
|
||||
frappe.set_route("list", "data-import", {
|
||||
reference_doctype: doctype,
|
||||
|
|
@ -1526,7 +1533,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
if (frappe.model.can_set_user_permissions(doctype)) {
|
||||
items.push({
|
||||
label: __("User Permissions"),
|
||||
label: __("User Permissions", null, "Button in list view menu"),
|
||||
action: () =>
|
||||
frappe.set_route("list", "user-permission", {
|
||||
allow: doctype,
|
||||
|
|
@ -1537,7 +1544,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
if (frappe.user_roles.includes("System Manager")) {
|
||||
items.push({
|
||||
label: __("Role Permissions Manager"),
|
||||
label: __("Role Permissions Manager", null, "Button in list view menu"),
|
||||
action: () =>
|
||||
frappe.set_route("permission-manager", {
|
||||
doctype,
|
||||
|
|
@ -1546,7 +1553,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
});
|
||||
|
||||
items.push({
|
||||
label: __("Customize"),
|
||||
label: __("Customize", null, "Button in list view menu"),
|
||||
action: () => {
|
||||
if (!this.meta) return;
|
||||
if (this.meta.custom) {
|
||||
|
|
@ -1563,7 +1570,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
items.push({
|
||||
label: __("Toggle Sidebar"),
|
||||
label: __("Toggle Sidebar", null, "Button in list view menu"),
|
||||
action: () => this.toggle_side_bar(),
|
||||
condition: () => !this.hide_sidebar,
|
||||
standard: true,
|
||||
|
|
@ -1571,7 +1578,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
});
|
||||
|
||||
items.push({
|
||||
label: __("Share URL"),
|
||||
label: __("Share URL", null, "Button in list view menu"),
|
||||
action: () => this.share_url(),
|
||||
standard: true,
|
||||
shortcut: "Ctrl+L",
|
||||
|
|
@ -1583,7 +1590,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
) {
|
||||
// edit doctype
|
||||
items.push({
|
||||
label: __("Edit DocType"),
|
||||
label: __("Edit DocType", null, "Button in list view menu"),
|
||||
action: () => frappe.set_route("form", "doctype", doctype),
|
||||
standard: true,
|
||||
});
|
||||
|
|
@ -1591,7 +1598,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
if (frappe.user.has_role("System Manager")) {
|
||||
items.push({
|
||||
label: __("List Settings"),
|
||||
label: __("List Settings", null, "Button in list view menu"),
|
||||
action: () => this.show_list_settings(),
|
||||
standard: true,
|
||||
});
|
||||
|
|
@ -1682,7 +1689,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
// utility
|
||||
const bulk_assignment = () => {
|
||||
return {
|
||||
label: __("Assign To"),
|
||||
label: __("Assign To", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.assign(
|
||||
|
|
@ -1700,7 +1707,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_assignment_rule = () => {
|
||||
return {
|
||||
label: __("Apply Assignment Rule"),
|
||||
label: __("Apply Assignment Rule", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.apply_assignment_rule(
|
||||
|
|
@ -1718,7 +1725,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_add_tags = () => {
|
||||
return {
|
||||
label: __("Add Tags"),
|
||||
label: __("Add Tags", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.add_tags(
|
||||
|
|
@ -1736,7 +1743,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_printing = () => {
|
||||
return {
|
||||
label: __("Print"),
|
||||
label: __("Print", null, "Button in list view actions menu"),
|
||||
action: () => bulk_operations.print(this.get_checked_items()),
|
||||
standard: true,
|
||||
};
|
||||
|
|
@ -1744,13 +1751,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_delete = () => {
|
||||
return {
|
||||
label: __("Delete"),
|
||||
label: __("Delete", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
const docnames = this.get_checked_items(true).map(
|
||||
(docname) => docname.toString()
|
||||
);
|
||||
frappe.confirm(
|
||||
__("Delete {0} items permanently?", [docnames.length]),
|
||||
__("Delete {0} items permanently?", [docnames.length], "Title of confirmation dialog"),
|
||||
() => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.delete(docnames, () => {
|
||||
|
|
@ -1767,12 +1774,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_cancel = () => {
|
||||
return {
|
||||
label: __("Cancel"),
|
||||
label: __("Cancel", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
const docnames = this.get_checked_items(true);
|
||||
if (docnames.length > 0) {
|
||||
frappe.confirm(
|
||||
__("Cancel {0} documents?", [docnames.length]),
|
||||
__("Cancel {0} documents?", [docnames.length], "Title of confirmation dialog"),
|
||||
() => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.submit_or_cancel(
|
||||
|
|
@ -1793,12 +1800,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_submit = () => {
|
||||
return {
|
||||
label: __("Submit"),
|
||||
label: __("Submit", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
const docnames = this.get_checked_items(true);
|
||||
if (docnames.length > 0) {
|
||||
frappe.confirm(
|
||||
__("Submit {0} documents?", [docnames.length]),
|
||||
__("Submit {0} documents?", [docnames.length], "Title of confirmation dialog"),
|
||||
() => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.submit_or_cancel(
|
||||
|
|
@ -1820,7 +1827,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_edit = () => {
|
||||
return {
|
||||
label: __("Edit"),
|
||||
label: __("Edit", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
let field_mappings = {};
|
||||
|
||||
|
|
@ -1850,7 +1857,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const bulk_export = () => {
|
||||
return {
|
||||
label: __("Export"),
|
||||
label: __("Export", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
const docnames = this.get_checked_items(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ Object.assign(frappe.model, {
|
|||
}
|
||||
|
||||
frappe.model.sync_docinfo(r);
|
||||
|
||||
return r.docs;
|
||||
},
|
||||
|
||||
rename_after_save: (d, i) => {
|
||||
|
|
|
|||
|
|
@ -133,14 +133,14 @@ frappe.router = {
|
|||
// /app/user/user-001 = ["Form", "User", "user-001"]
|
||||
// /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]]) {
|
||||
// public workspace
|
||||
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
|
||||
route = ['Workspaces', 'private', frappe.workspaces[private_wspace].title];
|
||||
route = ['Workspaces', 'private', frappe.workspaces[private_workspace].title];
|
||||
} else if (this.routes[route[0]]) {
|
||||
// route
|
||||
route = this.set_doctype_route(route);
|
||||
|
|
@ -282,7 +282,7 @@ frappe.router = {
|
|||
resolve();
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}).finally(() => frappe.route_flags = {});
|
||||
},
|
||||
|
||||
get_route_from_arguments(route) {
|
||||
|
|
@ -374,8 +374,9 @@ frappe.router = {
|
|||
// change the URL and call the router
|
||||
if (window.location.pathname !== url) {
|
||||
|
||||
// push state so the browser looks fine
|
||||
history.pushState(null, null, url);
|
||||
// push/replace state so the browser looks fine
|
||||
const method = frappe.route_flags.replace_route ? "replaceState" : "pushState";
|
||||
history[method](null, null, url);
|
||||
|
||||
// now process the route
|
||||
this.route();
|
||||
|
|
|
|||
|
|
@ -57,8 +57,10 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
// show footer
|
||||
this.action = this.action || { primary: { }, secondary: { } };
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ frappe.warn = function(title, message_html, proceed_action, primary_label, is_mi
|
|||
if (proceed_action) proceed_action();
|
||||
d.hide();
|
||||
},
|
||||
secondary_action_label: __("Cancel"),
|
||||
secondary_action_label: __("Cancel", null, "Secondary button in warning dialog"),
|
||||
secondary_action: () => d.hide(),
|
||||
minimizable: is_minimizable
|
||||
});
|
||||
|
|
@ -365,7 +365,7 @@ frappe.show_alert = frappe.toast = function(message, seconds=7, actions={}) {
|
|||
let indicator_icon_map = {
|
||||
'orange': "solid-warning",
|
||||
'yellow': "solid-warning",
|
||||
'blue': "solid-success",
|
||||
'blue': "solid-info",
|
||||
'green': "solid-success",
|
||||
'red': "solid-error"
|
||||
};
|
||||
|
|
@ -387,8 +387,10 @@ frappe.show_alert = frappe.toast = function(message, seconds=7, actions={}) {
|
|||
icon = 'solid-info';
|
||||
}
|
||||
|
||||
const indicator = message.indicator || 'blue';
|
||||
|
||||
const div = $(`
|
||||
<div class="alert desk-alert">
|
||||
<div class="alert desk-alert ${indicator}" role="alert">
|
||||
<div class="alert-message-container">
|
||||
<div class="alert-title-container">
|
||||
<div>${frappe.utils.icon(icon, 'lg')}</div>
|
||||
|
|
@ -398,7 +400,8 @@ frappe.show_alert = frappe.toast = function(message, seconds=7, actions={}) {
|
|||
</div>
|
||||
<div class="alert-body" style="display: none"></div>
|
||||
<a class="close">${frappe.utils.icon('close-alt')}</a>
|
||||
</div>`);
|
||||
</div>
|
||||
`);
|
||||
|
||||
div.hide().appendTo("#alert-container").show();
|
||||
|
||||
|
|
|
|||
|
|
@ -113,41 +113,43 @@ frappe.ui.SortSelector = class SortSelector {
|
|||
if(!this.args.options) {
|
||||
// default options
|
||||
var _options = [
|
||||
{'fieldname': 'modified'}
|
||||
{'fieldname': 'modified'},
|
||||
{'fieldname': 'name'},
|
||||
{'fieldname': 'creation'},
|
||||
{'fieldname': 'idx'},
|
||||
]
|
||||
|
||||
// title field
|
||||
if(meta.title_field) {
|
||||
_options.push({'fieldname': meta.title_field});
|
||||
if (meta.title_field) {
|
||||
_options.splice(1, 0, {'fieldname': meta.title_field});
|
||||
}
|
||||
|
||||
// bold or mandatory
|
||||
// sort field - set via DocType schema or Customize Form
|
||||
if (meta_sort_field) {
|
||||
_options.splice(1, 0, { 'fieldname': meta_sort_field });
|
||||
}
|
||||
|
||||
// bold, mandatory and fields that are available in list view
|
||||
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});
|
||||
}
|
||||
});
|
||||
|
||||
// meta sort field
|
||||
if(meta_sort_field) _options.push({ 'fieldname': meta_sort_field });
|
||||
|
||||
// more default options
|
||||
_options.push(
|
||||
{'fieldname': 'name'},
|
||||
{'fieldname': 'creation'},
|
||||
{'fieldname': 'idx'}
|
||||
)
|
||||
|
||||
// de-duplicate
|
||||
this.args.options = _options.uniqBy(function(obj) {
|
||||
return obj.fieldname;
|
||||
// add missing labels
|
||||
_options.forEach(option => {
|
||||
if (!option.label) {
|
||||
option.label = me.get_label(option.fieldname);
|
||||
}
|
||||
});
|
||||
|
||||
// add missing labels
|
||||
this.args.options.forEach(function(o) {
|
||||
if(!o.label) {
|
||||
o.label = me.get_label(o.fieldname);
|
||||
}
|
||||
// de-duplicate
|
||||
this.args.options = _options.uniqBy(obj => {
|
||||
return obj.fieldname;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
size: "large",
|
||||
size: "extra-large",
|
||||
});
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -263,7 +263,6 @@ frappe.views.CommunicationComposer = class {
|
|||
const subject_field = me.dialog.fields_dict.subject;
|
||||
|
||||
let content = content_field.get_value() || "";
|
||||
content = content.split('<!-- salutation-ends -->')[1] || content;
|
||||
|
||||
content_field.set_value(`${reply.message}<br>${content}`);
|
||||
subject_field.set_value(reply.subject);
|
||||
|
|
@ -725,15 +724,6 @@ frappe.views.CommunicationComposer = class {
|
|||
|
||||
message += await this.get_signature();
|
||||
|
||||
const SALUTATION_END_COMMENT = "<!-- salutation-ends -->";
|
||||
if (this.real_name && !message.includes(SALUTATION_END_COMMENT)) {
|
||||
message = `
|
||||
<p>${__('Dear {0},', [this.real_name], 'Salutation in new email')},</p>
|
||||
${SALUTATION_END_COMMENT}<br>
|
||||
${message}
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.is_a_reply) {
|
||||
message += this.get_earlier_reply();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
|
|||
if (new_name===name) {
|
||||
this.render(doctype_layout, name);
|
||||
} else {
|
||||
frappe.route_flags.replace_route = true;
|
||||
frappe.set_route("Form", doctype_layout, new_name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ frappe.provide("frappe.views");
|
|||
|
||||
function bind_events() {
|
||||
bind_add_column();
|
||||
bind_clickdrag();
|
||||
}
|
||||
|
||||
function setup_sortable() {
|
||||
|
|
@ -392,6 +393,45 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
}
|
||||
|
||||
function bind_clickdrag() {
|
||||
let isDown = false;
|
||||
let startX;
|
||||
let scrollLeft;
|
||||
let draggable = self.$kanban_board[0];
|
||||
|
||||
draggable.addEventListener('mousedown', (e) => {
|
||||
// don't trigger scroll if one of the ancestors of the
|
||||
// clicked element matches any of these selectors
|
||||
let ignoreEl = [
|
||||
'.kanban-column .kanban-column-header',
|
||||
'.kanban-column .add-card',
|
||||
'.kanban-column .kanban-card.new-card-area',
|
||||
'.kanban-card-wrapper',
|
||||
];
|
||||
if (ignoreEl.some((el) => e.target.closest(el))) return;
|
||||
|
||||
isDown = true;
|
||||
draggable.classList.add('clickdrag-active');
|
||||
startX = e.pageX - draggable.offsetLeft;
|
||||
scrollLeft = draggable.scrollLeft;
|
||||
});
|
||||
draggable.addEventListener('mouseleave', () => {
|
||||
isDown = false;
|
||||
draggable.classList.remove('clickdrag-active');
|
||||
});
|
||||
draggable.addEventListener('mouseup', () => {
|
||||
isDown = false;
|
||||
draggable.classList.remove('clickdrag-active');
|
||||
});
|
||||
draggable.addEventListener('mousemove', (e) => {
|
||||
if (!isDown) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - draggable.offsetLeft;
|
||||
const walk = (x - startX);
|
||||
draggable.scrollLeft = scrollLeft - walk;
|
||||
});
|
||||
}
|
||||
|
||||
function setup_restore_columns() {
|
||||
var cur_list = store.getState().cur_list;
|
||||
var columns = store.getState().columns;
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
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',
|
||||
fieldtype: 'Check'
|
||||
}
|
||||
|
|
@ -789,7 +789,10 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
} else {
|
||||
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];
|
||||
this.fields.push(field);
|
||||
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) {
|
||||
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;
|
||||
this.wrapper.innerHTML = '';
|
||||
|
|
@ -28,12 +28,64 @@ export default class Block {
|
|||
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) {
|
||||
let me = this;
|
||||
const dialog_class = get_dialog_constructor(widget_type);
|
||||
let block_name = block+'_name';
|
||||
this.dialog = new dialog_class({
|
||||
|
|
@ -53,13 +105,18 @@ export default class Block {
|
|||
});
|
||||
this.block_widget.customize(this.options);
|
||||
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.add_tune_button();
|
||||
this.add_settings_button();
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.readOnly && this.data && !this.data[block_name]) {
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
"tune-btn",
|
||||
`${__('Tune')}`,
|
||||
null,
|
||||
$widget_control,
|
||||
true
|
||||
);
|
||||
{
|
||||
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')
|
||||
},
|
||||
{
|
||||
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() {
|
||||
let col = this.col || 12;
|
||||
let class_name = "col-12";
|
||||
let class_name = "col-xs-12";
|
||||
let wrapper = this.wrapper.closest('.ce-block');
|
||||
const col_class = new RegExp(/\bcol-.+?\b/, "g");
|
||||
if (wrapper && wrapper.className.match(col_class)) {
|
||||
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("-");
|
||||
col = parseInt(parts[1]);
|
||||
col = parseInt(parts[2]);
|
||||
}
|
||||
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() {
|
||||
return {
|
||||
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_hiding: false,
|
||||
allow_edit: true,
|
||||
allow_resize: true
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +36,9 @@ export default class Card extends Block {
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
@ -49,9 +52,9 @@ export default class Card extends Block {
|
|||
return true;
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
save() {
|
||||
return {
|
||||
card_name: blockContent.getAttribute('card_name'),
|
||||
card_name: this.wrapper.getAttribute('card_name'),
|
||||
col: this.get_col(),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export default class Chart extends Block {
|
|||
static get toolbox() {
|
||||
return {
|
||||
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_hiding: false,
|
||||
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) {
|
||||
this.add_tune_button();
|
||||
$(this.wrapper).find('.widget').addClass('chart edit-mode');
|
||||
this.add_settings_button();
|
||||
this.add_new_block_button();
|
||||
}
|
||||
|
||||
return this.wrapper;
|
||||
|
|
@ -49,9 +53,9 @@ export default class Chart extends Block {
|
|||
return true;
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
save() {
|
||||
return {
|
||||
chart_name: blockContent.getAttribute('chart_name'),
|
||||
chart_name: this.wrapper.getAttribute('chart_name'),
|
||||
col: this.get_col(),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,16 +4,8 @@ export default class Header extends Block {
|
|||
constructor({ data, 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._data = this.normalizeData(data);
|
||||
this.settingsButtons = [];
|
||||
this._element = this.getTag();
|
||||
|
||||
this.data = data;
|
||||
|
|
@ -27,8 +19,7 @@ export default class Header extends Block {
|
|||
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;
|
||||
|
||||
return newData;
|
||||
|
|
@ -36,7 +27,6 @@ export default class Header extends Block {
|
|||
|
||||
render() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.contentEditable = this.readOnly ? 'false' : 'true';
|
||||
if (!this.readOnly) {
|
||||
let $widget_head = $(`<div class="widget-head"></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_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.icon('drag', 'xs'),
|
||||
|
|
@ -76,67 +49,14 @@ export default class Header extends Block {
|
|||
$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._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) {
|
||||
const newData = {
|
||||
text: this.data.text + data.text,
|
||||
level: this.data.level,
|
||||
text: this.data.text + data.text
|
||||
};
|
||||
|
||||
this.data = newData;
|
||||
|
|
@ -146,31 +66,28 @@ export default class Header extends Block {
|
|||
return blockData.text.trim() !== '';
|
||||
}
|
||||
|
||||
save(toolsContent) {
|
||||
save() {
|
||||
this.wrapper = this._element;
|
||||
return {
|
||||
text: toolsContent.innerText,
|
||||
level: this.currentLevel.number,
|
||||
text: this.wrapper.innerHTML.replace(/ /gi, ''),
|
||||
col: this.get_col()
|
||||
};
|
||||
}
|
||||
|
||||
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() {
|
||||
return {
|
||||
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() {
|
||||
this._data.text = this._element.innerHTML;
|
||||
this._data.level = this.currentLevel.number;
|
||||
|
||||
return this._data;
|
||||
}
|
||||
|
|
@ -188,15 +104,11 @@ export default class Header extends Block {
|
|||
set data(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) {
|
||||
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) {
|
||||
|
|
@ -205,11 +117,12 @@ export default class Header extends Block {
|
|||
}
|
||||
|
||||
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) {
|
||||
tag.contentEditable = true;
|
||||
|
|
@ -220,120 +133,10 @@ export default class Header extends Block {
|
|||
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() {
|
||||
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',
|
||||
icon: frappe.utils.icon('header', 'sm')
|
||||
};
|
||||
}
|
||||
}
|
||||
117
frappe/public/js/frappe/views/workspace/blocks/header_size.js
Normal file
117
frappe/public/js/frappe/views/workspace/blocks/header_size.js
Normal file
|
|
@ -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 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,
|
||||
paragraph: Paragraph,
|
||||
card: Card,
|
||||
|
|
@ -22,6 +22,6 @@ frappe.wspace_block.blocks = {
|
|||
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() {
|
||||
return {
|
||||
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_delete: this.allow_customization,
|
||||
allow_hiding: false,
|
||||
allow_edit: true
|
||||
allow_edit: true,
|
||||
allow_resize: false
|
||||
};
|
||||
}
|
||||
|
||||
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')) {
|
||||
$(e).hide();
|
||||
$(block).hide();
|
||||
}
|
||||
e.classList.add("col-" + this.get_col());
|
||||
this.set_col_class(block, this.get_col());
|
||||
}
|
||||
|
||||
new(block, widget_type = block) {
|
||||
let me = this;
|
||||
const dialog_class = get_dialog_constructor(widget_type);
|
||||
let block_name = block+'_name';
|
||||
this.dialog = new dialog_class({
|
||||
|
|
@ -54,13 +56,18 @@ export default class Onboarding extends Block {
|
|||
});
|
||||
this.block_widget.customize(this.options);
|
||||
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.add_tune_button();
|
||||
this.add_settings_button();
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.readOnly && this.data && !this.data[block_name]) {
|
||||
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) {
|
||||
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");
|
||||
return this.wrapper;
|
||||
|
|
@ -119,9 +128,9 @@ export default class Onboarding extends Block {
|
|||
return true;
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
save() {
|
||||
return {
|
||||
onboarding_name: blockContent.getAttribute('onboarding_name'),
|
||||
onboarding_name: this.wrapper.getAttribute('onboarding_name'),
|
||||
col: this.get_col(),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export default class Paragraph extends Block {
|
|||
}
|
||||
|
||||
onKeyUp(e) {
|
||||
if (!this.wrapper) return;
|
||||
this.show_hide_block_list(true);
|
||||
if (e.code !== 'Backspace' && e.code !== 'Delete') {
|
||||
return;
|
||||
}
|
||||
|
|
@ -34,55 +36,86 @@ export default class Paragraph extends Block {
|
|||
const {textContent} = this._element;
|
||||
|
||||
if (textContent === '') {
|
||||
this.show_hide_block_list();
|
||||
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() {
|
||||
let div = document.createElement('DIV');
|
||||
|
||||
div.classList.add(this._CSS.wrapper, this._CSS.block, 'widget');
|
||||
div.contentEditable = false;
|
||||
div.dataset.placeholder = this.api.i18n.t(this._placeholder);
|
||||
|
||||
if (!this.readOnly) {
|
||||
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);
|
||||
}
|
||||
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() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.contentEditable = this.readOnly ? 'false' : 'true';
|
||||
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._element.classList.remove('widget');
|
||||
$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.icon('drag', 'xs'),
|
||||
|
|
@ -93,15 +126,6 @@ export default class Paragraph extends Block {
|
|||
$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._element;
|
||||
|
|
@ -132,8 +156,7 @@ export default class Paragraph extends Block {
|
|||
}
|
||||
|
||||
rendered() {
|
||||
var e = this._element.closest('.ce-block');
|
||||
e.classList.add("col-" + this.get_col());
|
||||
super.rendered(this._element);
|
||||
}
|
||||
|
||||
onPaste(event) {
|
||||
|
|
@ -144,20 +167,14 @@ export default class Paragraph extends Block {
|
|||
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() {
|
||||
return {
|
||||
text: {
|
||||
br: true,
|
||||
b: true,
|
||||
i: true,
|
||||
a: true
|
||||
a: true,
|
||||
span: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -188,8 +205,8 @@ export default class Paragraph extends Block {
|
|||
|
||||
static get toolbox() {
|
||||
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() {
|
||||
return {
|
||||
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 }) {
|
||||
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.options = {
|
||||
allow_sorting: this.allow_customization,
|
||||
allow_create: this.allow_customization,
|
||||
allow_delete: this.allow_customization,
|
||||
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() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.new('shortcut');
|
||||
|
|
@ -34,7 +56,14 @@ export default class Shortcut extends Block {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -47,9 +76,9 @@ export default class Shortcut extends Block {
|
|||
return true;
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
save() {
|
||||
return {
|
||||
shortcut_name: blockContent.getAttribute('shortcut_name'),
|
||||
shortcut_name: this.wrapper.getAttribute('shortcut_name'),
|
||||
col: this.get_col(),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export default class Spacer extends Block {
|
|||
static get toolbox() {
|
||||
return {
|
||||
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() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.classList.add('widget', 'spacer');
|
||||
if (!this.readOnly) {
|
||||
let $spacer = $(`
|
||||
<div class="widget-head">
|
||||
<div></div>
|
||||
<div class="spacer-left"></div>
|
||||
<div>Spacer</div>
|
||||
<div class="widget-control"></div>
|
||||
</div>
|
||||
`);
|
||||
$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');
|
||||
|
||||
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.icon('drag', 'xs'),
|
||||
|
|
@ -61,15 +45,6 @@ export default class Spacer extends Block {
|
|||
null,
|
||||
$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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -160,17 +160,17 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
|
||||
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.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()
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
@ -216,16 +216,18 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
|
||||
let message = '';
|
||||
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) {
|
||||
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) {
|
||||
frappe.msgprint({
|
||||
title: __('Error'),
|
||||
title: __('Error', null, 'Title of error message in web form'),
|
||||
message: message,
|
||||
indicator: 'orange'
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,16 +34,6 @@ export default class Widget {
|
|||
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 (this.hidden) {
|
||||
this.widget.removeClass("hidden");
|
||||
|
|
@ -71,27 +61,11 @@ export default class Widget {
|
|||
frappe.utils.add_custom_button(
|
||||
frappe.utils.icon("edit", "xs"),
|
||||
() => this.edit(),
|
||||
null,
|
||||
"edit-button",
|
||||
`${__('Edit')}`,
|
||||
null,
|
||||
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() {
|
||||
|
|
@ -100,9 +74,7 @@ export default class 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-label">
|
||||
<div class="widget-title"></div>
|
||||
|
|
@ -110,10 +82,8 @@ export default class Widget {
|
|||
</div>
|
||||
<div class="widget-control"></div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
</div>
|
||||
<div class="widget-footer">
|
||||
</div>
|
||||
<div class="widget-body"></div>
|
||||
<div class="widget-footer"></div>
|
||||
</div>`);
|
||||
|
||||
this.title_field = this.widget.find(".widget-title");
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default class ChartWidget extends Widget {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ export default class LinksWidget extends Widget {
|
|||
|
||||
return $(`<a href="${route}" class="link-item ellipsis ${
|
||||
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>
|
||||
${get_link_for_item(item)}
|
||||
</a>`);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class WidgetDialog {
|
|||
this.setup_dialog_events();
|
||||
this.dialog.show();
|
||||
|
||||
window.cur_dialog = this.dialog;
|
||||
this.editing && this.set_default_values();
|
||||
}
|
||||
|
||||
|
|
@ -181,19 +182,16 @@ class CardDialog extends WidgetDialog {
|
|||
fieldtype: "Select",
|
||||
in_list_view: 1,
|
||||
label: "Link Type",
|
||||
options: ["DocType", "Page", "Report"],
|
||||
onchange: (e) => {
|
||||
me.link_to = e.currentTarget.value;
|
||||
}
|
||||
options: ["DocType", "Page", "Report"]
|
||||
},
|
||||
{
|
||||
fieldname: "link_to",
|
||||
fieldtype: "Dynamic Link",
|
||||
in_list_view: 1,
|
||||
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() {
|
||||
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.setup_filter(this.default_values['doctype']);
|
||||
this.set_aggregate_function_fields();
|
||||
|
|
@ -518,7 +516,7 @@ class NumberCardDialog extends WidgetDialog {
|
|||
|
||||
set_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 => {
|
||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
|
||||
if (df.fieldtype == 'Currency') {
|
||||
|
|
@ -537,7 +535,7 @@ class NumberCardDialog extends WidgetDialog {
|
|||
if (data.new_or_existing == 'Existing 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;
|
||||
|
||||
return data;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue