@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template | from .utils.jinja import get_jenv, get_template, render_template | ||||
__version__ = '7.2.19' | |||||
__version__ = '7.2.20' | |||||
__title__ = "Frappe Framework" | __title__ = "Frappe Framework" | ||||
local = Local() | local = Local() | ||||
@@ -51,6 +51,7 @@ $.extend(frappe.desktop, { | |||||
desktop_items: all_icons, | desktop_items: all_icons, | ||||
})); | })); | ||||
frappe.desktop.setup_help_messages(); | |||||
frappe.desktop.setup_module_click(); | frappe.desktop.setup_module_click(); | ||||
// notifications | // notifications | ||||
@@ -60,6 +61,80 @@ $.extend(frappe.desktop, { | |||||
}); | }); | ||||
$(document).trigger("desktop-render"); | $(document).trigger("desktop-render"); | ||||
}, | |||||
setup_help_messages: function() { | |||||
// { | |||||
// title: 'Sign up for a Premium Plan', | |||||
// description: 'Sign up for a premium plan and add users, get more disk space and priority support', | |||||
// action: 'Select Plan', | |||||
// route: 'usage-info' | |||||
// } | |||||
if(!frappe.user.has_role('System Manager')) { | |||||
return; | |||||
} | |||||
frappe.call({ | |||||
method: 'frappe.core.page.desktop.desktop.get_help_messages', | |||||
callback: function(r) { | |||||
frappe.desktop.render_help_messages(r.message); | |||||
} | |||||
}); | |||||
}, | |||||
render_help_messages: function(help_messages) { | |||||
var wrapper = frappe.desktop.wrapper.find('.help-message-wrapper'); | |||||
var $help_messages = wrapper.find('.help-messages'); | |||||
set_current_message = function(idx) { | |||||
idx = cint(idx); | |||||
wrapper.current_message_idx = idx; | |||||
wrapper.find('.left-arrow, .right-arrow').addClass('disabled'); | |||||
wrapper.find('.help-message-item').addClass('hidden'); | |||||
wrapper.find('[data-message-idx="'+idx+'"]').removeClass('hidden'); | |||||
if(idx > 0) { | |||||
wrapper.find('.left-arrow').removeClass('disabled'); | |||||
} | |||||
if(idx < help_messages.length - 1) { | |||||
wrapper.find('.right-arrow').removeClass('disabled'); | |||||
} | |||||
} | |||||
if(help_messages) { | |||||
wrapper.removeClass('hidden'); | |||||
help_messages.forEach(function(message, i) { | |||||
var $message = $('<div class="help-message-item hidden"></div>') | |||||
.attr('data-message-idx', i) | |||||
.html($.format('<div><span class="indicator blue">{0}</span></div>\ | |||||
<p>{1}</p>\ | |||||
<div><a class="btn btn-sm btn-default" href="#{2}">{3}</a></div>', | |||||
[message.title, message.description, message.route, message.action])) | |||||
.appendTo($help_messages); | |||||
}); | |||||
set_current_message(0); | |||||
wrapper.find('.close').on('click', function() { | |||||
wrapper.addClass('hidden'); | |||||
}); | |||||
} | |||||
wrapper.find('.left-arrow').on('click', function() { | |||||
if(wrapper.current_message_idx) { | |||||
set_current_message(wrapper.current_message_idx - 1); | |||||
} | |||||
}) | |||||
wrapper.find('.right-arrow').on('click', function() { | |||||
if(help_messages.length > wrapper.current_message_idx + 1) { | |||||
set_current_message(wrapper.current_message_idx + 1); | |||||
} | |||||
}); | |||||
}, | }, | ||||
setup_module_click: function() { | setup_module_click: function() { | ||||
@@ -0,0 +1,23 @@ | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
@frappe.whitelist() | |||||
def get_help_messages(): | |||||
'''Return help messages for the desktop (called via `get_help_messages` hook) | |||||
Format for message: | |||||
{ | |||||
title: _('Add Employees to Manage Them'), | |||||
description: _('Add your Employees so you can manage leaves, expenses and payroll'), | |||||
action: 'Add Employee', | |||||
route: 'List/Employee' | |||||
} | |||||
''' | |||||
messages = [] | |||||
for fn in frappe.get_hooks('get_help_messages'): | |||||
messages += frappe.get_attr(fn)() | |||||
return sorted(messages, lambda a, b: cmp(a.get('count'), b.get('count'))) |
@@ -4,5 +4,18 @@ | |||||
{{ frappe.render_template("desktop_module_icon", desktop_items[i]) }} | {{ frappe.render_template("desktop_module_icon", desktop_items[i]) }} | ||||
{% } %} | {% } %} | ||||
</div> | </div> | ||||
<div class="help-message-wrapper hidden"> | |||||
<div class="help-message-container"> | |||||
<a class="close pull-right" style="margin-right: -7px;"> | |||||
<i class="octicon octicon-x"></i></a> | |||||
<div class="help-messages"> | |||||
</div> | |||||
<a class="left-arrow octicon octicon-chevron-left"> | |||||
</a> | |||||
<a class="right-arrow octicon octicon-chevron-right"> | |||||
</a> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
<div style="clear: both"></div> | <div style="clear: both"></div> |
@@ -123,7 +123,9 @@ class EmailAccount(Document): | |||||
try: | try: | ||||
email_server.connect() | email_server.connect() | ||||
except (error_proto, imaplib.IMAP4.error), e: | except (error_proto, imaplib.IMAP4.error), e: | ||||
if in_receive and ("authentication failed" in e.message.lower() or "log in via your web browser" in e.message.lower()): | |||||
message = e.message.lower().replace(" ","") | |||||
if in_receive and any(map(lambda t: t in message, ['authenticationfail', 'loginviayourwebbrowser', #abbreviated to work with both failure and failed | |||||
'loginfailed', 'err[auth]', 'errtemporaryerror'])): #temporary error to deal with godaddy | |||||
# if called via self.receive and it leads to authentication error, disable incoming | # if called via self.receive and it leads to authentication error, disable incoming | ||||
# and send email to system manager | # and send email to system manager | ||||
self.handle_incoming_connect_error( | self.handle_incoming_connect_error( | ||||
@@ -186,3 +186,51 @@ body[data-route=""] .navbar-set-desktop-icons, | |||||
body[data-route="desktop"] .navbar-set-desktop-icons { | body[data-route="desktop"] .navbar-set-desktop-icons { | ||||
display: block; | display: block; | ||||
} | } | ||||
.help-message-wrapper { | |||||
position: fixed; | |||||
bottom: 30px; | |||||
width: 100%; | |||||
} | |||||
.help-message-wrapper .help-message-container { | |||||
position: relative; | |||||
text-align: left; | |||||
margin: auto; | |||||
width: 500px; | |||||
background-color: #fff; | |||||
padding: 10px 15px 15px 15px; | |||||
border-radius: 3px; | |||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); | |||||
} | |||||
.help-message-wrapper h5 { | |||||
margin-top: 5px; | |||||
} | |||||
.help-message-wrapper .help-message-item { | |||||
font-size: 12px; | |||||
} | |||||
.help-message-wrapper .octicon { | |||||
color: #8D99A6; | |||||
cursor: pointer; | |||||
width: 20px; | |||||
} | |||||
.help-message-wrapper .octicon.disabled { | |||||
color: #d1d8dd; | |||||
} | |||||
.help-message-wrapper .octicon:hover { | |||||
color: #36414C; | |||||
text-decoration: none; | |||||
} | |||||
.help-message-wrapper .left-arrow { | |||||
position: absolute; | |||||
right: 30px; | |||||
bottom: 15px; | |||||
text-align: left; | |||||
} | |||||
.help-message-wrapper .right-arrow { | |||||
position: absolute; | |||||
right: 15px; | |||||
bottom: 15px; | |||||
text-align: right; | |||||
} | |||||
.help-message-wrapper .indicator { | |||||
color: #36414C; | |||||
} |
@@ -144,9 +144,15 @@ | |||||
padding-bottom: 15px; | padding-bottom: 15px; | ||||
} | } | ||||
.form-links .document-link { | .form-links .document-link { | ||||
margin-bottom: 5px; | |||||
margin-bottom: 10px; | |||||
height: 22px; | height: 22px; | ||||
} | } | ||||
.form-links .document-link:hover .badge-link { | |||||
text-decoration: underline; | |||||
} | |||||
.form-links .document-link:hover .badge-link[disabled='disabled'] { | |||||
text-decoration: none; | |||||
} | |||||
.form-links .count { | .form-links .count { | ||||
display: inline-block; | display: inline-block; | ||||
margin-left: 5px; | margin-left: 5px; | ||||
@@ -295,7 +295,6 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||||
if(me.only_input) { | if(me.only_input) { | ||||
make_input(); | make_input(); | ||||
update_input(); | update_input(); | ||||
me.$input && me.$input.prop("disabled", true); | |||||
} else { | } else { | ||||
$(me.input_area).toggle(false); | $(me.input_area).toggle(false); | ||||
if (me.disp_area) { | if (me.disp_area) { | ||||
@@ -303,6 +302,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||||
$(me.disp_area).toggle(true); | $(me.disp_area).toggle(true); | ||||
} | } | ||||
} | } | ||||
me.$input && me.$input.prop("disabled", true); | |||||
} | } | ||||
me.set_description(); | me.set_description(); | ||||
@@ -826,11 +826,20 @@ frappe.ui.form.ControlText = frappe.ui.form.ControlData.extend({ | |||||
make_wrapper: function() { | make_wrapper: function() { | ||||
this._super(); | this._super(); | ||||
this.$wrapper.find(".like-disabled-input").addClass("for-description"); | this.$wrapper.find(".like-disabled-input").addClass("for-description"); | ||||
}, | |||||
make_input: function() { | |||||
this._super(); | |||||
this.$input.css({'height': '300px'}) | |||||
} | } | ||||
}); | }); | ||||
frappe.ui.form.ControlLongText = frappe.ui.form.ControlText; | frappe.ui.form.ControlLongText = frappe.ui.form.ControlText; | ||||
frappe.ui.form.ControlSmallText = frappe.ui.form.ControlText; | |||||
frappe.ui.form.ControlSmallText = frappe.ui.form.ControlText.extend({ | |||||
make_input: function() { | |||||
this._super(); | |||||
this.$input.css({'height': '150px'}) | |||||
} | |||||
}); | |||||
frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ | frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ | ||||
input_type: "checkbox", | input_type: "checkbox", | ||||
@@ -146,6 +146,16 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
} | } | ||||
}, | }, | ||||
after_refresh: function() { | |||||
var me = this; | |||||
// show / hide new buttons (if allowed) | |||||
this.links_area.find('.btn-new').each(function() { | |||||
if(me.frm.can_create($(this).attr('data-doctype'))) { | |||||
$(this).removeClass('hidden'); | |||||
} | |||||
}); | |||||
}, | |||||
init_data: function() { | init_data: function() { | ||||
this.data = this.frm.meta.__dashboard || {}; | this.data = this.frm.meta.__dashboard || {}; | ||||
if(!this.data.transactions) this.data.transactions = []; | if(!this.data.transactions) this.data.transactions = []; | ||||
@@ -177,12 +187,16 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
render_links: function() { | render_links: function() { | ||||
var me = this; | var me = this; | ||||
this.links_area.removeClass('hidden'); | this.links_area.removeClass('hidden'); | ||||
this.links_area.find('.btn-new').addClass('hidden'); | |||||
if(this.data_rendered) { | if(this.data_rendered) { | ||||
return; | return; | ||||
} | } | ||||
$(frappe.render_template('form_links', | |||||
{transactions: this.data.transactions})) | |||||
//this.transactions_area.empty(); | |||||
this.data.frm = this.frm; | |||||
$(frappe.render_template('form_links', this.data)) | |||||
.appendTo(this.transactions_area) | .appendTo(this.transactions_area) | ||||
// bind links | // bind links | ||||
@@ -195,6 +209,11 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
me.open_document_list($(this).parent(), true); | me.open_document_list($(this).parent(), true); | ||||
}); | }); | ||||
// bind new | |||||
this.transactions_area.find('.btn-new').on('click', function() { | |||||
me.frm.make_new($(this).attr('data-doctype')); | |||||
}); | |||||
this.data_rendered = true; | this.data_rendered = true; | ||||
}, | }, | ||||
open_document_list: function($link, show_open) { | open_document_list: function($link, show_open) { | ||||
@@ -10,7 +10,12 @@ | |||||
data-doctype="{{ doctype }}"> | data-doctype="{{ doctype }}"> | ||||
<a class="badge-link small">{{ __(doctype) }}</a> | <a class="badge-link small">{{ __(doctype) }}</a> | ||||
<span class="text-muted small count"></span> | <span class="text-muted small count"></span> | ||||
<span class="open-notification hidden" title="{{ __("Open {0}", [__(doctype)])}}"></span> | |||||
<span class="open-notification hidden" | |||||
title="{{ __("Open {0}", [__(doctype)])}}"></span> | |||||
{% if !internal_links[doctype] %} | |||||
<button class="btn btn-new btn-default btn-xs pull-right hidden" | |||||
data-doctype="{{ doctype }}">{{ __("New") }}</button> | |||||
{% endif %} | |||||
</div> | </div> | ||||
{% } %} | {% } %} | ||||
</div> | </div> | ||||
@@ -254,7 +254,7 @@ frappe.ui.Page = Class.extend({ | |||||
add_inner_button: function(label, action, group) { | add_inner_button: function(label, action, group) { | ||||
if(group) { | if(group) { | ||||
var $group = this.get_inner_group_button(group); | var $group = this.get_inner_group_button(group); | ||||
$('<li><a>'+label+'</a></li>').on('click', action).appendTo($group.find(".dropdown-menu")); | |||||
return $('<li><a>'+label+'</a></li>').on('click', action).appendTo($group.find(".dropdown-menu")); | |||||
} else { | } else { | ||||
return $('<button class="btn btn-default btn-xs" style="margin-left: 10px;">'+__(label)+'</btn>') | return $('<button class="btn btn-default btn-xs" style="margin-left: 10px;">'+__(label)+'</btn>') | ||||
.on("click", action).appendTo(this.inner_toolbar.removeClass("hide")) | .on("click", action).appendTo(this.inner_toolbar.removeClass("hide")) | ||||
@@ -330,7 +330,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||||
docfield = columnDef.report_docfield; | docfield = columnDef.report_docfield; | ||||
docfield.link_onclick = | docfield.link_onclick = | ||||
repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s")', | |||||
repl('frappe.container.page.reportview.filter_or_open("%(fieldname)s", "%(value)s")', | |||||
{fieldname:docfield.fieldname, value:value}); | {fieldname:docfield.fieldname, value:value}); | ||||
} | } | ||||
return frappe.format(value, docfield, {for_print: for_print, always_show_decimals: true}, dataContext); | return frappe.format(value, docfield, {for_print: for_print, always_show_decimals: true}, dataContext); | ||||
@@ -340,6 +340,25 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||||
}); | }); | ||||
}, | }, | ||||
filter_or_open: function(fieldname, value) { | |||||
// set filter on click, if filter is set, open the document | |||||
var filter_set = false; | |||||
this.filter_list.get_filters().forEach(function(f) { | |||||
if(f[1]===fieldname) { | |||||
filter_set = true; | |||||
} | |||||
}); | |||||
if(!filter_set) { | |||||
this.set_filter(fieldname, value); | |||||
} else { | |||||
var df = frappe.meta.get_docfield(this.doctype, fieldname); | |||||
if(df.fieldtype==='Link') { | |||||
frappe.set_route('Form', df.options, value); | |||||
} | |||||
} | |||||
}, | |||||
// render data | // render data | ||||
render_list: function() { | render_list: function() { | ||||
var me = this; | var me = this; | ||||
@@ -448,5 +448,53 @@ _f.Frm.prototype.set_indicator_formatter = function(fieldname, get_color, get_te | |||||
return ''; | return ''; | ||||
} | } | ||||
}; | }; | ||||
} | |||||
_f.Frm.prototype.can_create = function(doctype) { | |||||
// return true or false if the user can make a particlar doctype | |||||
// will check permission, `can_make_methods` if exists, or will decided on | |||||
// basis of whether the document is submittable | |||||
if(!frappe.model.can_create(doctype)) { | |||||
return false; | |||||
} | |||||
if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { | |||||
// if the button is present, then show make | |||||
return !!this.custom_buttons[this.custom_make_buttons[doctype]]; | |||||
} | |||||
if(this.can_make_methods && this.can_make_methods[doctype]) { | |||||
return this.can_make_methods[doctype](this); | |||||
} else { | |||||
if(this.meta.is_submittable && !this.doc.docstatus==1) { | |||||
return false; | |||||
} else { | |||||
return true; | |||||
} | |||||
} | |||||
} | |||||
_f.Frm.prototype.make_new = function(doctype) { | |||||
// make new doctype from the current form | |||||
// will handover to `make_methods` if defined | |||||
// or will create and match link fields | |||||
var me = this; | |||||
if(this.make_methods && this.make_methods[doctype]) { | |||||
return this.make_methods[doctype](this); | |||||
} else if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { | |||||
this.custom_buttons[this.custom_make_buttons[doctype]].trigger('click'); | |||||
} else { | |||||
frappe.model.with_doctype(doctype, function() { | |||||
new_doc = frappe.model.get_new_doc(doctype); | |||||
} | |||||
// set link fields (if found) | |||||
frappe.get_meta(doctype).fields.forEach(function(df) { | |||||
if(df.fieldtype==='Link' && df.options===me.doctype) { | |||||
new_doc[df.fieldname] = me.doc.name; | |||||
} | |||||
}); | |||||
frappe.set_route('Form', doctype, new_doc.name); | |||||
}); | |||||
} | |||||
} |
@@ -34,6 +34,7 @@ _f.Frm = function(doctype, parent, in_form) { | |||||
var me = this; | var me = this; | ||||
this.opendocs = {}; | this.opendocs = {}; | ||||
this.custom_buttons = {}; | |||||
this.sections = []; | this.sections = []; | ||||
this.grids = []; | this.grids = []; | ||||
this.cscript = new frappe.ui.form.Controller({frm:this}); | this.cscript = new frappe.ui.form.Controller({frm:this}); | ||||
@@ -518,10 +519,13 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) { | |||||
this.script_manager.trigger("onload_post_render"); | this.script_manager.trigger("onload_post_render"); | ||||
} | } | ||||
// update dashboard after refresh | |||||
this.dashboard.after_refresh(); | |||||
// focus on first input | // focus on first input | ||||
if(this.doc.docstatus==0) { | |||||
var first = this.form_wrapper.find('.form-layout :input:first'); | |||||
if(this.is_new()) { | |||||
var first = this.form_wrapper.find('.form-layout input:first'); | |||||
if(!in_list(["Date", "Datetime"], first.attr("data-fieldtype"))) { | if(!in_list(["Date", "Datetime"], first.attr("data-fieldtype"))) { | ||||
first.focus(); | first.focus(); | ||||
} | } | ||||
@@ -888,12 +892,15 @@ _f.Frm.prototype.set_footnote = function(txt) { | |||||
_f.Frm.prototype.add_custom_button = function(label, fn, group) { | _f.Frm.prototype.add_custom_button = function(label, fn, group) { | ||||
// temp! old parameter used to be icon | // temp! old parameter used to be icon | ||||
if(group && group.indexOf("fa fa-")!==-1) group = null; | if(group && group.indexOf("fa fa-")!==-1) group = null; | ||||
return this.page.add_inner_button(label, fn, group); | |||||
var btn = this.page.add_inner_button(label, fn, group); | |||||
this.custom_buttons[label] = btn; | |||||
return btn; | |||||
} | } | ||||
_f.Frm.prototype.clear_custom_buttons = function() { | _f.Frm.prototype.clear_custom_buttons = function() { | ||||
this.page.clear_inner_toolbar(); | this.page.clear_inner_toolbar(); | ||||
this.page.clear_user_actions(); | this.page.clear_user_actions(); | ||||
this.custom_buttons = {}; | |||||
} | } | ||||
_f.Frm.prototype.add_fetch = function(link_field, src_field, tar_field) { | _f.Frm.prototype.add_fetch = function(link_field, src_field, tar_field) { | ||||
@@ -234,3 +234,64 @@ body[data-route=""] .navbar-set-desktop-icons, | |||||
body[data-route="desktop"] .navbar-set-desktop-icons { | body[data-route="desktop"] .navbar-set-desktop-icons { | ||||
display: block; | display: block; | ||||
} | } | ||||
.help-message-wrapper { | |||||
position: fixed; | |||||
bottom: 30px; | |||||
width: 100%; | |||||
.help-message-container { | |||||
position: relative; | |||||
text-align: left; | |||||
margin: auto; | |||||
width: 500px; | |||||
background-color: #fff; | |||||
padding: 10px 15px 15px 15px; | |||||
border-radius: 3px; | |||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); | |||||
} | |||||
h5 { | |||||
margin-top: 5px; | |||||
} | |||||
.help-message-item { | |||||
font-size: 12px; | |||||
} | |||||
.octicon { | |||||
color: @text-muted; | |||||
cursor: pointer; | |||||
width: 20px; | |||||
} | |||||
.octicon.disabled { | |||||
color: @text-extra-muted; | |||||
} | |||||
.octicon:hover { | |||||
color: @text-color; | |||||
text-decoration: none; | |||||
} | |||||
.left-arrow { | |||||
position: absolute; | |||||
right: 30px; | |||||
bottom: 15px; | |||||
text-align: left; | |||||
// margin-top: 2px; | |||||
} | |||||
.right-arrow { | |||||
position: absolute; | |||||
right: 15px; | |||||
bottom: 15px; | |||||
text-align: right; | |||||
// margin-top: -18px; | |||||
} | |||||
.indicator { | |||||
color: @text-color; | |||||
} | |||||
} | |||||
@@ -187,10 +187,18 @@ | |||||
.form-links { | .form-links { | ||||
.document-link { | .document-link { | ||||
margin-bottom: 5px; | |||||
margin-bottom: 10px; | |||||
height: 22px; | height: 22px; | ||||
} | } | ||||
.document-link:hover .badge-link { | |||||
text-decoration: underline; | |||||
} | |||||
.document-link:hover .badge-link[disabled='disabled'] { | |||||
text-decoration: none; | |||||
} | |||||
.count { | .count { | ||||
display: inline-block; | display: inline-block; | ||||
margin-left: 5px; | margin-left: 5px; | ||||