@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||
from .exceptions import * | |||
from .utils.jinja import get_jenv, get_template, render_template | |||
__version__ = '7.2.19' | |||
__version__ = '7.2.20' | |||
__title__ = "Frappe Framework" | |||
local = Local() | |||
@@ -51,6 +51,7 @@ $.extend(frappe.desktop, { | |||
desktop_items: all_icons, | |||
})); | |||
frappe.desktop.setup_help_messages(); | |||
frappe.desktop.setup_module_click(); | |||
// notifications | |||
@@ -60,6 +61,80 @@ $.extend(frappe.desktop, { | |||
}); | |||
$(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() { | |||
@@ -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]) }} | |||
{% } %} | |||
</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 style="clear: both"></div> |
@@ -123,7 +123,9 @@ class EmailAccount(Document): | |||
try: | |||
email_server.connect() | |||
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 | |||
# and send email to system manager | |||
self.handle_incoming_connect_error( | |||
@@ -186,3 +186,51 @@ body[data-route=""] .navbar-set-desktop-icons, | |||
body[data-route="desktop"] .navbar-set-desktop-icons { | |||
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; | |||
} | |||
.form-links .document-link { | |||
margin-bottom: 5px; | |||
margin-bottom: 10px; | |||
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 { | |||
display: inline-block; | |||
margin-left: 5px; | |||
@@ -295,7 +295,6 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
if(me.only_input) { | |||
make_input(); | |||
update_input(); | |||
me.$input && me.$input.prop("disabled", true); | |||
} else { | |||
$(me.input_area).toggle(false); | |||
if (me.disp_area) { | |||
@@ -303,6 +302,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
$(me.disp_area).toggle(true); | |||
} | |||
} | |||
me.$input && me.$input.prop("disabled", true); | |||
} | |||
me.set_description(); | |||
@@ -826,11 +826,20 @@ frappe.ui.form.ControlText = frappe.ui.form.ControlData.extend({ | |||
make_wrapper: function() { | |||
this._super(); | |||
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.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({ | |||
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() { | |||
this.data = this.frm.meta.__dashboard || {}; | |||
if(!this.data.transactions) this.data.transactions = []; | |||
@@ -177,12 +187,16 @@ frappe.ui.form.Dashboard = Class.extend({ | |||
render_links: function() { | |||
var me = this; | |||
this.links_area.removeClass('hidden'); | |||
this.links_area.find('.btn-new').addClass('hidden'); | |||
if(this.data_rendered) { | |||
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) | |||
// bind links | |||
@@ -195,6 +209,11 @@ frappe.ui.form.Dashboard = Class.extend({ | |||
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; | |||
}, | |||
open_document_list: function($link, show_open) { | |||
@@ -10,7 +10,12 @@ | |||
data-doctype="{{ doctype }}"> | |||
<a class="badge-link small">{{ __(doctype) }}</a> | |||
<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> | |||
@@ -254,7 +254,7 @@ frappe.ui.Page = Class.extend({ | |||
add_inner_button: function(label, action, group) { | |||
if(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 { | |||
return $('<button class="btn btn-default btn-xs" style="margin-left: 10px;">'+__(label)+'</btn>') | |||
.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.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}); | |||
} | |||
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_list: function() { | |||
var me = this; | |||
@@ -448,5 +448,53 @@ _f.Frm.prototype.set_indicator_formatter = function(fieldname, get_color, get_te | |||
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; | |||
this.opendocs = {}; | |||
this.custom_buttons = {}; | |||
this.sections = []; | |||
this.grids = []; | |||
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"); | |||
} | |||
// update dashboard after refresh | |||
this.dashboard.after_refresh(); | |||
// 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"))) { | |||
first.focus(); | |||
} | |||
@@ -888,12 +892,15 @@ _f.Frm.prototype.set_footnote = function(txt) { | |||
_f.Frm.prototype.add_custom_button = function(label, fn, group) { | |||
// temp! old parameter used to be icon | |||
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() { | |||
this.page.clear_inner_toolbar(); | |||
this.page.clear_user_actions(); | |||
this.custom_buttons = {}; | |||
} | |||
_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 { | |||
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 { | |||
.document-link { | |||
margin-bottom: 5px; | |||
margin-bottom: 10px; | |||
height: 22px; | |||
} | |||
.document-link:hover .badge-link { | |||
text-decoration: underline; | |||
} | |||
.document-link:hover .badge-link[disabled='disabled'] { | |||
text-decoration: none; | |||
} | |||
.count { | |||
display: inline-block; | |||
margin-left: 5px; | |||