@@ -121,6 +121,7 @@ jobs: | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
- name: Upload coverage data | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: MariaDB | |||
@@ -124,6 +124,7 @@ jobs: | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
- name: Upload coverage data | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: Postgres | |||
@@ -69,13 +69,11 @@ def make_tree_args(**kwarg): | |||
doctype = kwarg['doctype'] | |||
parent_field = 'parent_' + doctype.lower().replace(' ', '_') | |||
name_field = kwarg.get('name_field', doctype.lower().replace(' ', '_') + '_name') | |||
if kwarg['is_root'] == 'false': kwarg['is_root'] = False | |||
if kwarg['is_root'] == 'true': kwarg['is_root'] = True | |||
kwarg.update({ | |||
name_field: kwarg[name_field], | |||
parent_field: kwarg.get("parent") or kwarg.get(parent_field) | |||
}) | |||
@@ -131,7 +131,7 @@ frappe.ui.form.Control = class BaseControl { | |||
if (!this.doc.__islocal) { | |||
new frappe.views.TranslationManager({ | |||
'df': this.df, | |||
'source_text': value, | |||
'source_text': this.value, | |||
'target_language': this.doc.language, | |||
'doc': this.doc | |||
}); | |||
@@ -104,8 +104,10 @@ frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.Cont | |||
return [ | |||
['bold', 'italic', 'underline'], | |||
['blockquote', 'code-block'], | |||
[{ 'direction': "rtl" }], | |||
['link', 'image'], | |||
[{ 'list': 'ordered' }, { 'list': 'bullet' }], | |||
[{ 'align': [] }], | |||
['clean'] | |||
]; | |||
} | |||
@@ -164,8 +164,11 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for | |||
['bold', 'italic', 'underline', 'clean'], | |||
[{ 'color': [] }, { 'background': [] }], | |||
['blockquote', 'code-block'], | |||
// Adding Direction tool to give the user the ability to change text direction. | |||
[{ 'direction': "rtl" }], | |||
['link', 'image'], | |||
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }], | |||
[{ 'align': [] }], | |||
[{ 'indent': '-1'}, { 'indent': '+1' }], | |||
[{'table': [ | |||
'insert-table', | |||
@@ -1152,6 +1152,10 @@ frappe.ui.form.Form = class FrappeForm { | |||
return btn; | |||
} | |||
change_custom_button_type(label, group, type) { | |||
this.page.change_inner_button_type(label, group, type); | |||
} | |||
clear_custom_buttons() { | |||
this.page.clear_inner_toolbar(); | |||
this.page.clear_user_actions(); | |||
@@ -4,7 +4,7 @@ export default class BulkOperations { | |||
this.doctype = doctype; | |||
} | |||
print(docs) { | |||
print (docs) { | |||
const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings'); | |||
const allow_print_for_draft = cint(print_settings.allow_print_for_draft); | |||
const is_submittable = frappe.model.is_submittable(this.doctype); | |||
@@ -27,31 +27,38 @@ export default class BulkOperations { | |||
if (valid_docs.length > 0) { | |||
const dialog = new frappe.ui.Dialog({ | |||
title: __('Print Documents'), | |||
fields: [{ | |||
'fieldtype': 'Check', | |||
'label': __('With Letterhead'), | |||
'fieldname': 'with_letterhead' | |||
}, | |||
{ | |||
'fieldtype': 'Select', | |||
'label': __('Print Format'), | |||
'fieldname': 'print_sel', | |||
options: frappe.meta.get_print_formats(this.doctype) | |||
}] | |||
fields: [ | |||
{ | |||
'fieldtype': 'Select', | |||
'label': __('Letter Head'), | |||
'fieldname': 'letter_sel', | |||
'default': __('No Letterhead'), | |||
options: this.get_letterhead_options() | |||
}, | |||
{ | |||
'fieldtype': 'Select', | |||
'label': __('Print Format'), | |||
'fieldname': 'print_sel', | |||
options: frappe.meta.get_print_formats(this.doctype) | |||
} | |||
] | |||
}); | |||
dialog.set_primary_action(__('Print'), args => { | |||
if (!args) return; | |||
const default_print_format = frappe.get_meta(this.doctype).default_print_format; | |||
const with_letterhead = args.with_letterhead ? 1 : 0; | |||
const with_letterhead = args.letter_sel == __("No Letterhead") ? 0 : 1; | |||
const print_format = args.print_sel ? args.print_sel : default_print_format; | |||
const json_string = JSON.stringify(valid_docs); | |||
const letterhead = args.letter_sel; | |||
const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' + | |||
'doctype=' + encodeURIComponent(this.doctype) + | |||
'&name=' + encodeURIComponent(json_string) + | |||
'&format=' + encodeURIComponent(print_format) + | |||
'&no_letterhead=' + (with_letterhead ? '0' : '1')); | |||
'&no_letterhead=' + (with_letterhead ? '0' : '1') + | |||
'&letterhead=' + encodeURIComponent(letterhead) | |||
); | |||
if (!w) { | |||
frappe.msgprint(__('Please enable pop-ups')); | |||
return; | |||
@@ -64,7 +71,28 @@ export default class BulkOperations { | |||
} | |||
} | |||
delete(docnames, done = null) { | |||
get_letterhead_options () { | |||
const letterhead_options = [__("No Letterhead")]; | |||
frappe.call({ | |||
method: "frappe.client.get_list", | |||
args: { | |||
doctype: 'Letter Head', | |||
fields: ['name', 'is_default'], | |||
limit: 0 | |||
}, | |||
async: false, | |||
callback (r) { | |||
if (r.message) { | |||
r.message.forEach(letterhead => { | |||
letterhead_options.push(letterhead.name); | |||
}); | |||
} | |||
} | |||
}); | |||
return letterhead_options; | |||
} | |||
delete (docnames, done = null) { | |||
frappe | |||
.call({ | |||
method: 'frappe.desk.reportview.delete_items', | |||
@@ -88,7 +116,7 @@ export default class BulkOperations { | |||
}); | |||
} | |||
assign(docnames, done) { | |||
assign (docnames, done) { | |||
if (docnames.length > 0) { | |||
const assign_to = new frappe.ui.form.AssignToDialog({ | |||
obj: this, | |||
@@ -106,7 +134,7 @@ export default class BulkOperations { | |||
} | |||
} | |||
apply_assignment_rule(docnames, done) { | |||
apply_assignment_rule (docnames, done) { | |||
if (docnames.length > 0) { | |||
frappe.call('frappe.automation.doctype.assignment_rule.assignment_rule.bulk_apply', { | |||
doctype: this.doctype, | |||
@@ -115,7 +143,7 @@ export default class BulkOperations { | |||
} | |||
} | |||
submit_or_cancel(docnames, action='submit', done=null) { | |||
submit_or_cancel (docnames, action = 'submit', done = null) { | |||
action = action.toLowerCase(); | |||
frappe | |||
.call({ | |||
@@ -140,7 +168,7 @@ export default class BulkOperations { | |||
}); | |||
} | |||
edit(docnames, field_mappings, done) { | |||
edit (docnames, field_mappings, done) { | |||
let field_options = Object.keys(field_mappings).sort(); | |||
const status_regex = /status/i; | |||
@@ -198,16 +226,16 @@ export default class BulkOperations { | |||
if (default_field) set_value_field(dialog); // to set `Value` df based on default `Field` | |||
function set_value_field(dialogObj) { | |||
function set_value_field (dialogObj) { | |||
const new_df = Object.assign({}, | |||
field_mappings[dialogObj.get_value('field')]); | |||
/* if the field label has status in it and | |||
if it has select fieldtype with no default value then | |||
set a default value from the available option. */ | |||
if(new_df.label.match(status_regex) && | |||
if (new_df.label.match(status_regex) && | |||
new_df.fieldtype === 'Select' && !new_df.default) { | |||
let options = []; | |||
if(typeof new_df.options==="string") { | |||
if (typeof new_df.options === "string") { | |||
options = new_df.options.split("\n"); | |||
} | |||
//set second option as default if first option is an empty string | |||
@@ -223,8 +251,7 @@ export default class BulkOperations { | |||
dialog.show(); | |||
} | |||
add_tags(docnames, done) { | |||
add_tags (docnames, done) { | |||
const dialog = new frappe.ui.Dialog({ | |||
title: __('Add Tags'), | |||
fields: [ | |||
@@ -233,7 +260,7 @@ export default class BulkOperations { | |||
fieldname: 'tags', | |||
label: __("Tags"), | |||
reqd: true, | |||
get_data: function(txt) { | |||
get_data: function (txt) { | |||
return frappe.db.get_link_options("Tag", txt); | |||
} | |||
}, | |||
@@ -262,7 +289,7 @@ export default class BulkOperations { | |||
dialog.show(); | |||
} | |||
export(doctype, docnames) { | |||
export (doctype, docnames) { | |||
frappe.require('data_import_tools.bundle.js', () => { | |||
const data_exporter = new frappe.data_import.DataExporter(doctype, 'Insert New Records'); | |||
data_exporter.dialog.set_value('export_records', 'by_filter'); | |||
@@ -618,6 +618,23 @@ frappe.ui.Page = class Page { | |||
} | |||
} | |||
change_inner_button_type(label, group, type) { | |||
let btn; | |||
if (group) { | |||
var $group = this.get_inner_group_button(__(group)); | |||
if ($group.length) { | |||
btn = $group.find(`.dropdown-item[data-label="${encodeURIComponent(label)}"]`); | |||
} | |||
} else { | |||
btn = this.inner_toolbar.find(`button[data-label="${encodeURIComponent(label)}"]`); | |||
} | |||
if (btn) { | |||
btn.removeClass().addClass(`btn btn-${type} ellipsis`); | |||
} | |||
} | |||
add_inner_message(message) { | |||
let $message = $(`<span class='inner-page-message text-muted small'>${message}</div>`); | |||
this.inner_toolbar.find('.inner-page-message').remove(); | |||
@@ -155,7 +155,7 @@ frappe.views.Workspace = class Workspace { | |||
}); | |||
// Scroll sidebar to selected page if it is not in viewport. | |||
!frappe.dom.is_element_in_viewport(this.sidebar.find('.selected')) | |||
!frappe.dom.is_element_in_viewport(this.sidebar.find('.selected')) | |||
&& this.sidebar.find('.selected')[0].scrollIntoView(); | |||
} | |||
@@ -185,7 +185,7 @@ frappe.views.Workspace = class Workspace { | |||
} | |||
append_item(item, container) { | |||
let is_current_page = frappe.router.slug(item.title) == frappe.router.slug(this.get_page_to_show().name) | |||
let is_current_page = frappe.router.slug(item.title) == frappe.router.slug(this.get_page_to_show().name) | |||
&& item.public == this.get_page_to_show().public; | |||
if (is_current_page) { | |||
item.selected = true; | |||
@@ -253,11 +253,13 @@ frappe.views.Workspace = class Workspace { | |||
if (!this.page_data || Object.keys(this.page_data).length === 0) return; | |||
return frappe.dashboard_utils.get_dashboard_settings().then(settings => { | |||
let chart_config = settings.chart_config ? JSON.parse(settings.chart_config) : {}; | |||
if (this.page_data.charts && this.page_data.charts.items) { | |||
this.page_data.charts.items.map(chart => { | |||
chart.chart_settings = chart_config[chart.chart_name] || {}; | |||
}); | |||
if (settings) { | |||
let chart_config = settings.chart_config ? JSON.parse(settings.chart_config) : {}; | |||
if (this.page_data.charts && this.page_data.charts.items) { | |||
this.page_data.charts.items.map(chart => { | |||
chart.chart_settings = chart_config[chart.chart_name] || {}; | |||
}); | |||
} | |||
} | |||
}); | |||
}); | |||
@@ -560,7 +562,7 @@ frappe.views.Workspace = class Workspace { | |||
fieldname: 'is_public', | |||
depends_on: `eval:${this.has_access}`, | |||
onchange: function() { | |||
d.set_df_property('parent', 'options', | |||
d.set_df_property('parent', 'options', | |||
this.get_value() ? me.public_parent_pages : me.private_parent_pages); | |||
} | |||
}, | |||
@@ -704,9 +706,9 @@ frappe.views.Workspace = class Workspace { | |||
} | |||
}); | |||
let blocks = outputData.blocks.filter( | |||
item => item.type != 'card' || | |||
(item.data.card_name !== 'Custom Documents' && | |||
let blocks = outputData.blocks.filter( | |||
item => item.type != 'card' || | |||
(item.data.card_name !== 'Custom Documents' && | |||
item.data.card_name !== 'Custom Reports') | |||
); | |||
@@ -752,4 +754,4 @@ frappe.views.Workspace = class Workspace { | |||
this.setup_pages(); | |||
this.undo.readOnly = true; | |||
} | |||
}; | |||
}; |
@@ -17,10 +17,10 @@ body { | |||
} | |||
.standard-sidebar-section { | |||
margin-top: var(--margin-xl); | |||
margin-bottom: var(--margin-xl); | |||
&:first-of-type { | |||
margin-top: var(--margin-sm); | |||
&:last-of-type { | |||
margin-bottom: var(--margin-sm); | |||
} | |||
} | |||
} | |||
@@ -143,7 +143,7 @@ body { | |||
font-weight: 500; | |||
line-height: 1.3em; | |||
color: var(--heading-color); | |||
svg { | |||
flex: none; | |||
margin-right: 6px; | |||
@@ -863,7 +863,7 @@ body { | |||
.drag-handle { | |||
display: inline-block; | |||
} | |||
.delete-page { | |||
display: inline-block; | |||
margin-right: 8px; | |||
@@ -888,44 +888,44 @@ body { | |||
.codex-editor { | |||
min-height: 630px; | |||
.codex-editor__redactor{ | |||
display: flex; | |||
flex-wrap: wrap; | |||
flex-direction: row; | |||
margin: 0px -7px; | |||
padding-bottom: 20px !important; | |||
.ce-block{ | |||
width: 100%; | |||
padding-left: 0; | |||
padding-right: 0; | |||
&.ce-block--selected { | |||
.ce-block__content { | |||
background-color: inherit; | |||
} | |||
} | |||
.ce-block__content { | |||
max-width: 100%; | |||
height: 100%; | |||
padding: 7px; | |||
&> div { | |||
height: 100%; | |||
} | |||
.tune-btn > * { | |||
pointer-events: none; | |||
} | |||
.ce-header { | |||
padding: 0 !important; | |||
margin-bottom: 0 !important; | |||
flex: 1; | |||
} | |||
.widget{ | |||
&.header { | |||
display: flex; | |||
@@ -938,11 +938,11 @@ body { | |||
background-color: var(--control-bg); | |||
color: var(--text-muted); | |||
} | |||
&:focus { | |||
outline: none; | |||
} | |||
&.new-widget { | |||
align-items: inherit; | |||
} | |||
@@ -959,7 +959,7 @@ body { | |||
gap: 5px; | |||
background-color: var(--card-bg); | |||
padding-left: 5px; | |||
.drag-handle { | |||
cursor: all-scroll; | |||
cursor: -webkit-grabbing; | |||
@@ -969,22 +969,22 @@ body { | |||
} | |||
} | |||
} | |||
svg { | |||
fill: none; | |||
} | |||
.ce-toolbar { | |||
svg { | |||
fill: currentColor; | |||
} | |||
.icon { | |||
stroke: none; | |||
width: fit-content; | |||
height: fit-content; | |||
} | |||
.ce-settings { | |||
width: fit-content; | |||
@@ -1011,18 +1011,18 @@ body { | |||
border-radius: 0 4px 4px 0;z-index: 0; | |||
} | |||
} | |||
.ce-toolbar__settings-btn { | |||
display: none; | |||
} | |||
} | |||
.ce-inline-tool, .ce-inline-toolbar__dropdown { | |||
.icon { | |||
fill: currentColor; | |||
} | |||
} | |||
@media (min-width: 1199px) { | |||
.ce-toolbar__content { | |||
max-width: 930px; | |||
@@ -1033,14 +1033,14 @@ body { | |||
max-width: 760px; | |||
} | |||
} | |||
@media (max-width: 1199px) { | |||
.ce-block.col-4 { | |||
flex: 0 0 50%; | |||
max-width: 50%; | |||
} | |||
} | |||
@media (max-width: 750px) { | |||
.ce-block.col-4 { | |||
flex: 0 0 100%; | |||
@@ -1053,6 +1053,6 @@ body { | |||
max-width: 100%; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
import unittest | |||
import frappe | |||
from frappe.www.list import get_list_context | |||
class TestWebsite(unittest.TestCase): | |||
def test_get_context_hook_of_webform(self): | |||
create_custom_doctype() | |||
create_webform() | |||
# check context for apps without any hook | |||
context_list = get_list_context("", "Custom Doctype", "test-webform") | |||
self.assertFalse(context_list) | |||
# create a hook to get webform_context | |||
set_webform_hook( | |||
"webform_list_context", | |||
"frappe.www._test._test_webform.webform_list_context", | |||
) | |||
# check context for apps with hook | |||
context_list = get_list_context("", "Custom Doctype", "test-webform") | |||
self.assertTrue(context_list) | |||
def create_custom_doctype(): | |||
frappe.get_doc( | |||
{ | |||
"doctype": "DocType", | |||
"name": "Custom Doctype", | |||
"module": "Core", | |||
"custom": 1, | |||
"fields": [{"label": "Title", "fieldname": "title", "fieldtype": "Data"}], | |||
} | |||
).insert(ignore_if_duplicate=True) | |||
def create_webform(): | |||
frappe.get_doc( | |||
{ | |||
"doctype": "Web Form", | |||
"module": "Core", | |||
"title": "Test Webform", | |||
"route": "test-webform", | |||
"doc_type": "Custom Doctype", | |||
"web_form_fields": [ | |||
{ | |||
"doctype": "Web Form Field", | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"label": "Title", | |||
} | |||
], | |||
} | |||
).insert(ignore_if_duplicate=True) | |||
def set_webform_hook(key, value): | |||
from frappe import hooks | |||
# reset hooks | |||
for hook in "webform_list_context": | |||
if hasattr(hooks, hook): | |||
delattr(hooks, hook) | |||
setattr(hooks, key, value) | |||
frappe.cache().delete_key("app_hooks") |
@@ -821,6 +821,9 @@ def update_translations_for_source(source=None, translation_dict=None): | |||
translation_dict = json.loads(translation_dict) | |||
if is_html(source): | |||
source = strip_html_tags(source) | |||
# for existing records | |||
translation_records = frappe.db.get_values('Translation', { | |||
'source_text': source | |||
@@ -0,0 +1,6 @@ | |||
def webform_list_context(module): | |||
return {"get_list": get_webform_context_list} | |||
def get_webform_context_list(): | |||
pass |
@@ -161,6 +161,14 @@ def get_list_context(context, doctype, web_form_name=None): | |||
module = load_doctype_module(doctype) | |||
list_context = update_context_from_module(module, list_context) | |||
# get context for custom webform | |||
if meta.custom and web_form_name: | |||
webform_list_contexts = frappe.get_hooks('webform_list_context') | |||
if webform_list_contexts: | |||
out = frappe._dict(frappe.get_attr(webform_list_contexts[0])(meta.module) or {}) | |||
if out: | |||
list_context = out | |||
# get context from web form module | |||
if web_form_name: | |||
web_form = frappe.get_doc('Web Form', web_form_name) | |||