Merge branch 'develop' into updated-requirements

This commit is contained in:
gavin 2019-11-27 14:48:40 +05:30 committed by GitHub
commit 87fec89e97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 999 additions and 831 deletions

View file

@ -0,0 +1,55 @@
context('Control Barcode', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk');
});
function get_dialog_with_barcode() {
return cy.dialog({
title: 'Barcode',
fields: [
{
label: 'Barcode',
fieldname: 'barcode',
fieldtype: 'Barcode'
}
]
});
}
it('should generate barcode on setting a value', () => {
get_dialog_with_barcode().as('dialog');
cy.get('.frappe-control[data-fieldname=barcode] input')
.focus()
.type('123456789')
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('exist');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.contain('<svg');
expect(value).to.contain('data-barcode-value="123456789"');
});
});
it('should reset when input is cleared', () => {
get_dialog_with_barcode().as('dialog');
cy.get('.frappe-control[data-fieldname=barcode] input')
.focus()
.type('123456789')
.blur();
cy.get('.frappe-control[data-fieldname=barcode] input')
.clear()
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('not.exist');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.equal('');
});
});
});

View file

@ -61,12 +61,18 @@ context('Control Link', () => {
cy.server();
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('@todos').then(todos => {
cy.get('.frappe-control[data-fieldname=link] input').type(todos[0]).blur();
cy.get('.frappe-control[data-fieldname=link] input').as('input');
cy.get('@input').focus();
cy.wait('@search_link');
cy.get('@input').type(todos[0]).blur();
cy.wait('@validate_link');
cy.get('.frappe-control[data-fieldname=link] input').focus();
cy.get('.frappe-control[data-fieldname=link] .link-btn').click();
cy.get('@input').focus();
cy.get('.frappe-control[data-fieldname=link] .link-btn')
.should('be.visible')
.click();
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
});
});

View file

@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '12.0.17'
__version__ = '12.0.20'
__title__ = "Frappe Framework"
local = Local()
@ -290,7 +290,7 @@ def log(msg):
debug_log.append(as_unicode(msg))
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False):
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None):
"""Print a message to the user (via HTTP response).
Messages are sent in the `__server_messages` property in the
response JSON and shown in a pop-up / modal.
@ -299,6 +299,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
:param title: [optional] Message title.
:param raise_exception: [optional] Raise given exception and show message.
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
:param primary_action: [optional] Bind a primary server/client side action.
"""
from frappe.utils import encode
@ -338,6 +339,9 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
if alert:
out.alert = 1
if primary_action:
out.primary_action = primary_action
message_log.append(json.dumps(out))
if raise_exception and hasattr(raise_exception, '__name__'):

View file

@ -22,8 +22,7 @@
"fieldtype": "Link",
"label": "User",
"options": "User",
"reqd": 1,
"unique": 1
"reqd": 1
},
{
"default": "Online",

View file

@ -16,8 +16,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Token",
"reqd": 1,
"unique": 1
"reqd": 1
},
{
"fieldname": "ip_address",

View file

@ -1,4 +1,5 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.desk.moduleview import add_setup_section
@ -88,7 +89,7 @@ def get_data():
]
},
{
"label": _("Email"),
"label": _("Email / Notifications"),
"icon": "fa fa-envelope",
"items": [
{
@ -120,6 +121,12 @@ def get_data():
"type": "doctype",
"name": "Newsletter",
"description": _("Create and manage newsletter")
},
{
"type": "doctype",
"route": "Form/Notification Settings/{}".format(frappe.session.user),
"name": "Notification Settings",
"description": _("Configure notifications for mentions, assignments, energy points and more.")
}
]
},

View file

@ -145,30 +145,10 @@ def get_list_context(context=None):
def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by = None):
from frappe.www.list import get_list
user = frappe.session.user
ignore_permissions = False
if is_website_user():
if not filters: filters = []
add_name = []
contact = frappe.db.sql("""
select
address.name
from
`tabDynamic Link` as link
join
`tabAddress` as address on link.parent = address.name
where
link.parenttype = 'Address' and
link_name in(
select
link.link_name from `tabContact` as contact
join
`tabDynamic Link` as link on contact.name = link.parent
where
contact.user = %s)""",(user))
for c in contact:
add_name.append(c[0])
filters.append(("Address", "name", "in", add_name))
ignore_permissions = True
ignore_permissions = True
if not filters: filters = []
filters.append(("Address", "owner", "=", user))
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)

View file

@ -81,9 +81,9 @@ def get_feed_match_conditions(user=None, doctype='Comment'):
if user_permissions:
can_read_docs = []
for doctype, obj in user_permissions.items():
for dt, obj in user_permissions.items():
for n in obj:
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n.get('doc', ''))))
can_read_docs.append('{}|{}'.format(frappe.db.escape(dt), frappe.db.escape(n.get('doc', ''))))
if can_read_docs:
conditions.append("concat_ws('|', `tab{doctype}`.reference_doctype, `tab{doctype}`.reference_name) in ({values})".format(

View file

@ -18,6 +18,10 @@ frappe.ui.form.on("Communication", {
frm.convert_to_click && frm.set_convert_button();
frm.subject_field = "subject";
// content field contains weird table html that does not render well in Quill
// this field is not to be edited directly anyway, so setting it as read only
frm.set_df_property('content', 'read_only', 1);
if(frm.doc.reference_doctype && frm.doc.reference_name) {
frm.add_custom_button(__(frm.doc.reference_name), function() {
frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);

View file

@ -3,19 +3,20 @@
frappe.ui.form.on('Data Import', {
onload: function(frm) {
if(frm.doc.__islocal) {
if (frm.doc.__islocal) {
frm.set_value("action", "");
}
frappe.call({
method: "frappe.core.doctype.data_import.data_import.get_importable_doc",
method: "frappe.core.doctype.data_import.data_import.get_importable_doctypes",
callback: function (r) {
let importable_doctypes = r.message;
frm.set_query("reference_doctype", function () {
return {
"filters": {
"issingle": 0,
"istable": 0,
"name": ['in', r.message]
"name": ['in', importable_doctypes]
}
};
});

View file

@ -30,9 +30,8 @@ class DataImport(Document):
@frappe.whitelist()
def get_importable_doc():
import_lst = frappe.cache().hget("can_import", frappe.session.user)
return import_lst
def get_importable_doctypes():
return frappe.cache().hget("can_import", frappe.session.user)
@frappe.whitelist()
def import_data(data_import):

View file

@ -54,8 +54,10 @@ class Importer:
extension = None
if self.data_import and self.data_import.import_file:
file_doc = frappe.get_doc("File", {"file_url": self.data_import.import_file})
parts = file_doc.get_extension()
extension = parts[1]
content = file_doc.get_content()
extension = file_doc.file_name.split(".")[1]
extension = extension.lstrip(".")
if file_path:
content, extension = self.read_file(file_path)
@ -79,6 +81,12 @@ class Importer:
return file_content, extn
def read_content(self, content, extension):
error_title = _("Template Error")
if extension not in ("csv", "xlsx", "xls"):
frappe.throw(
_("Import template should be of type .csv, .xlsx or .xls"), title=error_title
)
if extension == "csv":
data = read_csv_content(content)
elif extension == "xlsx":
@ -86,6 +94,11 @@ class Importer:
elif extension == "xls":
data = read_xls_file_from_attached_file(content)
if len(data) <= 1:
frappe.throw(
_("Import template should contain a Header and atleast one row."), title=error_title
)
self.header_row = data[0]
self.data = data[1:]
@ -862,15 +875,15 @@ class Importer:
if failed_records:
print("Failed to import {0} records".format(len(failed_records)))
file_name = '{0}_import_on_{1}.txt'.format(self.doctype, frappe.utils.now())
print('Check {0} for errors'.format(os.path.join('sites', file_name)))
file_name = "{0}_import_on_{1}.txt".format(self.doctype, frappe.utils.now())
print("Check {0} for errors".format(os.path.join("sites", file_name)))
text = ""
for w in failed_records:
text += "Row Indexes: {0}\n".format(str(w.get('row_indexes', [])))
text += "Messages:\n{0}\n".format('\n'.join(w.get('messages', [])))
text += "Traceback:\n{0}\n\n".format(w.get('exception'))
text += "Row Indexes: {0}\n".format(str(w.get("row_indexes", [])))
text += "Messages:\n{0}\n".format("\n".join(w.get("messages", [])))
text += "Traceback:\n{0}\n\n".format(w.get("exception"))
with open(file_name, 'w') as f:
with open(file_name, "w") as f:
f.write(text)

View file

@ -25,7 +25,7 @@ class PreparedReport(Document):
enqueue(run_background, prepared_report=self.name, timeout=6000)
def on_trash(self):
remove_all("PreparedReport", self.name, from_delete=True)
remove_all("Prepared Report", self.name)
def run_background(prepared_report):
@ -85,7 +85,8 @@ def create_json_gz_file(data, dt, dn):
"file_name": json_filename,
"attached_to_doctype": dt,
"attached_to_name": dn,
"content": compressed_content
"content": compressed_content,
"is_private": 1
})
_file.save(ignore_permissions=True)

View file

@ -30,7 +30,7 @@ class Report(Document):
if self.is_standard == "No":
# allow only script manager to edit scripts
if frappe.session.user!="Administrator":
if self.report_type != 'Report Builder':
frappe.only_for('Script Manager', True)
if frappe.db.get_value("Report", self.name, "is_standard") == "Yes":

View file

@ -56,8 +56,10 @@ def get_server_script_map():
script_map = frappe.cache().get_value('server_script_map')
if script_map is None:
script_map = {}
for script in frappe.get_all('Server Script', ('name', 'reference_doctype', 'doctype_event',
'api_method', 'script_type')):
enabled_server_scripts = frappe.get_all('Server Script',
fields=('name', 'reference_doctype', 'doctype_event','api_method', 'script_type'),
filters={'disabled': 0})
for script in enabled_server_scripts:
if script.script_type == 'DocType Event':
script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name)
else:

View file

@ -8,6 +8,7 @@ from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_
from frappe import throw, msgprint, _
from frappe.utils.password import update_password as _update_password
from frappe.desk.notifications import clear_notifications
from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings
from frappe.utils.user import get_system_managers
from bs4 import BeautifulSoup
import frappe.permissions
@ -46,6 +47,9 @@ class User(Document):
self.flags.in_insert = True
throttle_user_creation()
def after_insert(self):
create_notification_settings(self.name)
def validate(self):
self.check_demo()
@ -364,6 +368,9 @@ class User(Document):
if frappe.db.exists("Chat Profile", old_name):
frappe.rename_doc("Chat Profile", old_name, new_name, force=True)
if frappe.db.exists("Notification Settings", old_name):
frappe.rename_doc("Notification Settings", old_name, new_name, force=True)
# set email
frappe.db.sql("""UPDATE `tabUser`
SET email = %s

View file

@ -1,3 +1,7 @@
.version-info {
overflow: auto;
}
.version-info pre {
border: 0px;
margin: 0px;
@ -14,4 +18,4 @@
.version-info .danger {
background-color: #f2dede !important;
}
}

View file

@ -10,7 +10,7 @@
"email_content",
"column_break_4",
"document_type",
"seen",
"read",
"document_name",
"from_user"
],
@ -57,14 +57,6 @@
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Seen"
},
{
"fieldname": "document_name",
"fieldtype": "Data",
@ -79,11 +71,19 @@
"options": "User",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Read"
}
],
"in_create": 1,
"modified": "2019-10-23 12:48:01.119356",
"modified_by": "Administrator",
"modified": "2019-11-12 15:22:35.283678",
"modified_by": "umair@erpnext.com",
"module": "Desk",
"name": "Notification Log",
"owner": "Administrator",

View file

@ -7,11 +7,12 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled,
is_email_notifications_enabled, is_email_notifications_enabled_for_type)
is_email_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value)
class NotificationLog(Document):
def after_insert(self):
frappe.publish_realtime('notification', after_commit=True, user=self.for_user)
set_notifications_as_unseen(self.for_user)
if is_email_notifications_enabled(self.for_user):
send_notification_email(self)
@ -41,7 +42,6 @@ def enqueue_create_notification(users, doc):
This breaks new site creation if Redis server is not running.
We do not need any notifications in fresh installation
'''
if frappe.flags.in_install:
return
@ -64,13 +64,13 @@ def make_notification_logs(doc, users):
if is_notifications_enabled(user):
if doc.type == 'Energy Point' and not is_energy_point_enabled():
return
else:
_doc = frappe.new_doc('Notification Log')
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
_doc.insert(ignore_permissions=True)
_doc = frappe.new_doc('Notification Log')
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
_doc.insert(ignore_permissions=True)
def send_notification_email(doc):
is_type_enabled = is_email_notifications_enabled_for_type(doc.for_user, doc.type)
@ -112,11 +112,25 @@ def get_email_header(doc):
@frappe.whitelist()
def mark_as_seen(docname):
if docname:
frappe.db.set_value('Notification Log', docname, 'seen', 1, update_modified=False)
def mark_all_as_read():
unread_docs_list = frappe.db.get_all('Notification Log', filters = {'read': 0, 'for_user': frappe.session.user})
unread_docnames = [doc.name for doc in unread_docs_list]
if unread_docnames:
filters = {'name': ['in', unread_docnames]}
frappe.db.set_value('Notification Log', filters, 'read', 1, update_modified=False)
@frappe.whitelist()
def mark_as_read(docname):
if docname:
frappe.db.set_value('Notification Log', docname, 'read', 1, update_modified=False)
@frappe.whitelist()
def trigger_indicator_hide():
frappe.publish_realtime('indicator_hide', user=frappe.session.user)
def set_notifications_as_unseen(user):
try:
frappe.db.set_value('Notification Settings', user, 'seen', 0)
except frappe.DoesNotExistError:
return

View file

@ -0,0 +1,12 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Notification Settings', {
onload: () => {
frappe.breadcrumbs.add({
label: __('Settings'),
route: '#modules/Settings',
type: 'Custom'
});
}
});

View file

@ -13,7 +13,8 @@
"enable_email_assignment",
"enable_email_energy_point",
"enable_email_share",
"user"
"user",
"seen"
],
"fields": [
{
@ -72,14 +73,20 @@
"fieldname": "user",
"fieldtype": "Link",
"hidden": 1,
"in_list_view": 1,
"label": "User",
"options": "User",
"read_only": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"label": "Seen"
}
],
"in_create": 1,
"modified": "2019-10-23 12:42:56.175928",
"modified": "2019-11-19 12:57:59.356786",
"modified_by": "Administrator",
"module": "Desk",
"name": "Notification Settings",

View file

@ -31,12 +31,12 @@ def is_email_notifications_enabled_for_type(user, notification_type):
return True
return enabled
@frappe.whitelist()
def create_notification_settings():
_doc = frappe.new_doc('Notification Settings')
_doc.name = frappe.session.user
_doc.insert(ignore_permissions=True)
frappe.db.commit()
def create_notification_settings(user):
if not frappe.db.exists("Notification Settings", user):
_doc = frappe.new_doc('Notification Settings')
_doc.name = user
_doc.insert(ignore_permissions=True)
frappe.db.commit()
@frappe.whitelist()
@ -60,3 +60,7 @@ def get_permission_query_conditions(user):
if not user: user = frappe.session.user
return '''(`tabNotification Settings`.user = '{user}')'''.format(user=user)
@frappe.whitelist()
def set_seen_value(value, user):
frappe.db.set_value('Notification Settings', user, 'seen', value, update_modified=False)

View file

@ -234,8 +234,11 @@ def get_config(app, module):
for item in section["items"]:
if item["type"]=="report" and item["name"] in disabled_reports:
continue
# some module links might not have name
if not item.get("name"):
item["name"] = item.get("label")
if not item.get("label"):
item["label"] = _(item["name"])
item["label"] = _(item.get("name"))
items.append(item)
section['items'] = items
@ -297,7 +300,7 @@ def get_onboard_items(app, module):
@frappe.whitelist()
def get_links_for_module(app, module):
return [l.get('label') for l in get_links(app, module)]
return [{'value': l.get('name'), 'label': l.get('label')} for l in get_links(app, module)]
def get_links(app, module):
try:
@ -330,13 +333,13 @@ def get_desktop_settings():
def apply_user_saved_links(module):
module = frappe._dict(module)
all_links = get_links(module.app, module.module_name)
module_links_by_label = {}
module_links_by_name = {}
for link in all_links:
module_links_by_label[link['label']] = link
module_links_by_name[link['name']] = link
if module.module_name in user_saved_links_by_module:
user_links = frappe.parse_json(user_saved_links_by_module[module.module_name])
module.links = [module_links_by_label[l] for l in user_links if l in module_links_by_label]
module.links = [module_links_by_name[l] for l in user_links if l in module_links_by_name]
return module

View file

@ -261,17 +261,24 @@ def delete_bulk(doctype, items):
@frappe.whitelist()
@frappe.read_only()
def get_sidebar_stats(stats, doctype, filters=[]):
_user_tags = []
data = frappe._dict(frappe.local.form_dict)
filters = json.loads(data["filters"])
if not frappe.cache().hget("tags_count", doctype):
tags = [tag.name for tag in frappe.get_list("Tag")]
_user_tags = []
for tag in tags:
count = frappe.db.count("Tag Link", filters={"document_type": doctype, "tag": tag})
if count > 0:
_user_tags.append([tag, count])
frappe.cache().hset("tags_count", doctype, _user_tags)
if not frappe.cache().hget("Tags", doctype):
tags = set([tag.tag for tag in frappe.get_list("Tag Link", filters={"document_type": doctype}, fields=["tag"])])
frappe.cache().hset("Tags", doctype, tags)
return {"stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}}
for tag in list(frappe.cache().hget("Tags", doctype)):
tag_filters = []
tag_filters.extend(filters)
tag_filters.extend([['Tag Link', 'tag', '=', tag]])
count = frappe.get_all(doctype, filters=tag_filters, fields=["count(*)"])
if count[0].get("count(*)") > 0:
_user_tags.append([tag, count[0].get("count(*)")])
return {"stats": {"_user_tags": _user_tags}}
@frappe.whitelist()
@frappe.read_only()

View file

@ -184,7 +184,7 @@ frappe.ui.form.on("Email Account", {
read as well as unread message from server. This may also cause the duplication\
of Communication (emails).");
frappe.confirm(msg, null, function() {
frm.set_value("email_sync_option", "UNSEEN");
frm.set_value("email_sync_option", "ALL");
});
}
}

View file

@ -298,7 +298,7 @@ class EmailServer:
"Connection timed out",
)
for message in messages:
if message in strip(cstr(e.message)) or message in strip(cstr(getattr(e, 'strerror', ''))):
if message in strip(cstr(e)) or message in strip(cstr(getattr(e, 'strerror', ''))):
return True
return False

View file

@ -180,6 +180,33 @@ class RazorpaySettings(Document):
integration_request = create_request_log(kwargs, "Host", "Razorpay")
return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name))
def create_order(self, **kwargs):
# Creating Orders https://razorpay.com/docs/api/orders/
# convert rupees to paisa
kwargs['amount'] *= 100
# Create integration log
integration_request = create_request_log(kwargs, "Host", "Razorpay")
# Setup payment options
payment_options = {
"amount": kwargs.get('amount'),
"currency": kwargs.get('currency', 'INR'),
"receipt": kwargs.get('receipt'),
"payment_capture": kwargs.get('payment_capture')
}
if self.api_key and self.api_secret:
try:
order = make_post_request("https://api.razorpay.com/v1/orders",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
data=payment_options)
order['integration_request'] = integration_request.name
return order # Order returned to be consumed by razorpay.js
except Exception:
frappe.log(frappe.get_traceback())
frappe.throw(_("Could not create razorpay order"))
def create_request(self, data):
self.data = frappe._dict(data)
@ -213,6 +240,10 @@ class RazorpaySettings(Document):
self.integration_request.update_status(data, 'Authorized')
self.flags.status_changed_to = "Authorized"
if resp.get("status") == "captured":
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Completed"
elif data.get('subscription_id'):
if resp.get("status") == "refunded":
# if subscription start date is in future then
@ -222,14 +253,6 @@ class RazorpaySettings(Document):
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Verified"
if resp.get("status") == "captured":
# if subscription starts immediately then
# razorpay charge the actual amount
# thus changing status to Completed
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Completed"
else:
frappe.log_error(str(resp), 'Razorpay Payment not authorized')
@ -242,7 +265,6 @@ class RazorpaySettings(Document):
redirect_to = data.get('redirect_to') or None
redirect_message = data.get('redirect_message') or None
if self.flags.status_changed_to in ("Authorized", "Verified", "Completed"):
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
@ -330,6 +352,63 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
doc.error = frappe.get_traceback()
frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
@frappe.whitelist(allow_guest=True)
def get_api_key():
controller = frappe.get_doc("Razorpay Settings")
return controller.api_key
@frappe.whitelist(allow_guest=True)
def get_order(doctype, docname):
# Order returned to be consumed by razorpay.js
doc = frappe.get_doc(doctype, docname)
try:
# Do not use run_method here as it fails silently
return doc.get_razorpay_order()
except AttributeError:
frappe.log_error(frappe.get_traceback(), _("Controller method get_razorpay_order missing"))
frappe.throw(_("Could not create Razorpay order. Please contact Administrator"))
@frappe.whitelist(allow_guest=True)
def order_payment_success(integration_request, params):
"""Called by razorpay.js on order payment success, the params
contains razorpay_payment_id, razorpay_order_id, razorpay_signature
that is updated in the data field of integration request
Args:
integration_request (string): Name for integration request doc
params (string): Params to be updated for integration request.
"""
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
# Update integration request
integration.update_status(params, integration.status)
integration.reload()
data = json.loads(integration.data)
controller = frappe.get_doc("Razorpay Settings")
# Update payment and integration data for payment controller object
controller.integration_request = integration
controller.data = frappe._dict(data)
# Authorize payment
controller.authorize_payment()
@frappe.whitelist(allow_guest=True)
def order_payment_failure(integration_request, params):
"""Called by razorpay.js on failure
Args:
integration_request (TYPE): Description
params (TYPE): error data to be updated
"""
frappe.log_error(params, 'Razorpay Payment Failure')
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
integration.update_status(params, integration.status)
def convert_rupee_to_paisa(**kwargs):
for addon in kwargs.get('addons'):
addon['item']['amount'] *= 100
@ -383,4 +462,4 @@ def validate_payment_callback(data):
_throw()
def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

View file

@ -332,8 +332,8 @@ def clear_references(doctype, reference_doctype, reference_name,
(reference_doctype, reference_name))
def clear_timeline_references(link_doctype, link_name):
frappe.db.sql("""delete from `tabCommunication Link`
where `tabCommunication Link`.link_doctype='{0}' and `tabCommunication Link`.link_name='{1}'""".format(link_doctype, link_name)) # nosec
frappe.db.sql("""DELETE FROM `tabCommunication Link`
WHERE `tabCommunication Link`.link_doctype=%s AND `tabCommunication Link`.link_name=%s""", (link_doctype, link_name))
def insert_feed(doc):
from frappe.utils import get_fullname

View file

@ -253,7 +253,8 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table
frappe.patches.v12_0.delete_duplicate_indexes
frappe.patches.v12_0.set_default_incoming_email_port
frappe.patches.v12_0.update_global_search
execute:frappe.reload_doc('desk', 'doctype', 'notification_settings')
frappe.patches.v12_0.setup_tags
frappe.patches.v12_0.update_auto_repeat_status_and_not_submittable
frappe.patches.v12_0.copy_to_parent_for_tags
frappe.patches.v12_0.create_notification_settings_for_user
frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26

View file

@ -0,0 +1,19 @@
from __future__ import unicode_literals
import frappe
def execute():
files = frappe.get_all("File", fields=["name", "attached_to_name"], filters={"attached_to_doctype": "Prepared Report", "is_private": 0})
for file_dict in files:
# For some reason Prepared Report doc might not exist, check if it exists first
if frappe.db.exists("Prepared Report", file_dict.attached_to_name):
try:
file_doc = frappe.get_doc("File", file_dict.name)
file_doc.is_private = 1
file_doc.save()
except Exception:
# File might not exist on the file system in that case delete both Prepared Report and File doc
frappe.delete_doc("Prepared Report", file_dict.attached_to_name)
else:
# If Prepared Report doc doesn't exist then the file doc is useless. Delete it.
frappe.delete_doc("File", file_dict.name)

View file

@ -0,0 +1,11 @@
from __future__ import unicode_literals
import frappe
from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings
def execute():
frappe.reload_doc('desk', 'doctype', 'notification_settings')
frappe.reload_doc('desk', 'doctype', 'notification_subscribed_document')
users = frappe.db.get_all('User', fields=['name'])
for user in users:
create_notification_settings(user.name)

View file

@ -18,6 +18,9 @@
"js/frappe-recorder.min.js": [
"public/js/frappe/recorder/recorder.js"
],
"js/checkout.min.js": [
"public/js/integrations/razorpay.js"
],
"js/frappe-web.min.js": [
"public/js/frappe/class.js",
"public/js/frappe/polyfill.js",

View file

@ -136,11 +136,7 @@ frappe.Application = Class.extend({
method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
callback: function(r) {
if (r.message[0] == __("Inactive")) {
frappe.msgprint({
title: __("Scheduler Inactive"),
indicator: "red",
message: __("Background jobs are not running. Please contact Administrator")
});
frappe.call('frappe.utils.scheduler.activate_scheduler');
}
}
});

View file

@ -367,6 +367,13 @@ export default {
if (this.on_success) {
this.on_success(file_doc, r);
}
} else if (xhr.status === 403) {
let response = JSON.parse(xhr.responseText);
frappe.msgprint({
title: __('Not permitted'),
indicator: 'red',
message: response._error_message
});
} else {
file.failed = true;
let error = null;

View file

@ -1,18 +1,27 @@
import JsBarcode from "jsbarcode";
import JsBarcode from 'jsbarcode';
frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({
make_wrapper() {
// Create the elements for barcode area
this._super();
this.default_svg = '<svg height=80></svg>';
let $input_wrapper = this.$wrapper.find('.control-input-wrapper');
this.barcode_area = $(`<div class="barcode-wrapper border"><svg height=80></svg></div>`);
this.barcode_area = $(
`<div class="barcode-wrapper border">${this.default_svg}</div>`
);
this.barcode_area.appendTo($input_wrapper);
},
parse(value) {
// Parse raw value
return value ? this.get_barcode_html(value) : "";
if (value) {
if (value.startsWith('<svg')) {
return value;
}
return this.get_barcode_html(value);
}
return '';
},
set_formatted_input(value) {
@ -20,47 +29,39 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({
let svg = value;
const barcode_value = $(svg).attr('data-barcode-value');
if(!barcode_value) {
if (!barcode_value && this.doc) {
svg = this.get_barcode_html(value);
this.doc[this.df.fieldname] = svg;
}
this.$input.val(barcode_value || value);
this.barcode_area.html(svg);
this.barcode_area.html(svg || this.default_svg);
},
get_barcode_html(value) {
// Get svg
const svg = this.barcode_area.find('svg')[0];
JsBarcode(svg, value, this.get_options(value));
$(svg).attr('data-barcode-value', value);
return this.barcode_area.html();
if (value) {
// Get svg
const svg = this.barcode_area.find('svg')[0];
JsBarcode(svg, value, this.get_options(value));
$(svg).attr('data-barcode-value', value);
return this.barcode_area.html();
}
},
get_options(value) {
// get JsBarcode options
let options = JSON.parse('{ "height" : 40 }');
if (this.isValidJson(this.df.options)) {
if (frappe.utils.is_json(this.df.options)) {
options = JSON.parse(this.df.options);
if (options.format && options.format === "EAN") {
options.format = value.length == 8 ? "EAN8" : "EAN13";
if (options.format && options.format === 'EAN') {
options.format = value.length == 8 ? 'EAN8' : 'EAN13';
}
if (options.valueField) {
// Set companion field value
this.frm.set_value(options.valueField, value);
this.frm && this.frm.set_value(options.valueField, value);
}
}
return options;
},
isValidJson(jsonData) {
try {
JSON.parse(jsonData);
return true;
} catch (e) {
return false;
}
}
});

View file

@ -28,6 +28,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({
return cint(value);
},
set_input: function(value) {
value = cint(value);
if(this.input) {
this.input.checked = (value ? 1 : 0);
}

View file

@ -1,7 +1,7 @@
import Quill from 'quill';
import Mention from './quill-mention/quill.mention';
Quill.register('modules/mention', Mention);
Quill.register('modules/mention', Mention, true);
frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({
make_wrapper() {

View file

@ -61,6 +61,9 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
});
this.$list_wrapper.on('keydown', e => {
if ($(e.target).is('input')) {
return;
}
if (e.key === 'Backspace') {
this.set_value([]);
}

View file

@ -35,4 +35,4 @@ MentionBlot.blotName = 'mention';
MentionBlot.tagName = 'span';
MentionBlot.className = 'mention';
Quill.register(MentionBlot);
Quill.register(MentionBlot, true);

View file

@ -361,6 +361,6 @@ class Mention {
}
}
Quill.register('modules/mention', Mention);
Quill.register('modules/mention', Mention, true);
export default Mention;

View file

@ -36,7 +36,7 @@ class MyLink extends Link {
}
}
Quill.register(MyLink);
Quill.register(MyLink, true);
// image uploader
const Uploader = Quill.import('modules/uploader');

View file

@ -52,7 +52,7 @@ export default class Grid {
let template = `<div class="form-group">
<div class="clearfix">
<label class="control-label" style="padding-right: 0px;">${__(this.df.label)}</label>
<label class="control-label" style="padding-right: 0px;">${__(this.df.label || '')}</label>
</div>
<div class="form-grid">
<div class="grid-heading-row"></div>

View file

@ -614,7 +614,7 @@ class FilterArea {
let options = df.options;
let condition = '=';
let fieldtype = df.fieldtype;
if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
if (['Text', 'Small Text', 'Text Editor', 'Data', 'Code'].includes(fieldtype)) {
fieldtype = 'Data';
condition = 'like';
}

View file

@ -285,7 +285,8 @@ frappe.views.ListSidebar = class ListSidebar {
args: {
stats: me.stats,
doctype: me.doctype,
filters: me.default_filters || []
// wait for list filter area to be generated before getting filters, or fallback to default filters
filters: (me.list_view.filter_area ? me.list_filter.get_current_filters() : me.default_filters) || []
},
callback: function(r) {
me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]);
@ -385,7 +386,8 @@ frappe.views.ListSidebar = class ListSidebar {
}
reload_stats() {
this.sidebar.find(".sidebar-stat").remove();
this.sidebar.find(".stat-link").remove();
this.sidebar.find(".stat-no-records").remove();
this.get_stats();
}

View file

@ -390,6 +390,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
}
after_render() {
this.list_sidebar.reload_stats();
}
render() {
this.render_list();
this.on_row_checked();
@ -577,7 +581,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
data-filter="${fieldname},=,${value}">
${_value}
</a>`;
} else if (['Text Editor', 'Text', 'Small Text'].includes(df.fieldtype)) {
} else if (['Text Editor', 'Text', 'Small Text', 'HTML Editor'].includes(df.fieldtype)) {
html = `<span class="text-muted ellipsis">
${_value}
</span>`;
@ -589,7 +593,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
return `<span class="ellipsis"
title="${__(label) + ': ' + _value}">
title="${__(label)}: ${escape(_value)}">
${html}
</span>`;
};
@ -1077,10 +1081,21 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
this.toggle_result_area();
this.render_list();
if (this.$checks.length) {
this.set_rows_as_checked();
}
});
});
}
set_rows_as_checked() {
$.each(this.$checks, (i, el) => {
let docname = $(el).attr('data-name');
this.$result.find(`.list-row-checkbox[data-name='${docname}']`).prop('checked', true);
});
this.on_row_checked();
}
on_row_checked() {
this.$list_head_subject = this.$list_head_subject || this.$result.find('header .list-header-subject');
this.$checkbox_actions = this.$checkbox_actions || this.$result.find('header .checkbox-actions');

View file

@ -525,7 +525,13 @@ $.extend(frappe.model, {
},
delete_doc: function(doctype, docname, callback) {
frappe.confirm(__("Permanently delete {0}?", [docname]), function() {
var title = docname;
var title_field = frappe.get_meta(doctype).title_field;
if (frappe.get_meta(doctype).autoname == "hash" && title_field) {
var title = frappe.model.get_value(doctype, docname, title_field);
title += " (" + docname + ")";
}
frappe.confirm(__("Permanently delete {0}?", [title]), function() {
return frappe.call({
method: 'frappe.client.delete',
args: {

View file

@ -367,24 +367,28 @@ frappe.ui.filter_utils = {
get_selected_value(field, condition) {
let val = field.get_value();
if(typeof val==='string') {
if (typeof val === 'string') {
val = strip(val);
}
if(field.df.original_type == 'Check') {
if (condition == 'is' && !val) {
val = field.df.options[0].value;
}
if (field.df.original_type == 'Check') {
val = (val=='Yes' ? 1 :0);
}
if(condition.indexOf('like', 'not like')!==-1) {
if (condition.indexOf('like', 'not like') !== -1) {
// automatically append wildcards
if(val && !(val.startsWith('%') || val.endsWith('%'))) {
if (val && !(val.startsWith('%') || val.endsWith('%'))) {
val = '%' + val + '%';
}
} else if(in_list(["in", "not in"], condition)) {
if(val) {
} else if (in_list(["in", "not in"], condition)) {
if (val) {
val = val.split(',').map(v => strip(v));
}
} if(val === '%') {
} if (val === '%') {
val = "";
}

View file

@ -120,12 +120,6 @@ frappe.msgprint = function(msg, title) {
}
});
// setup and bind an action to the primary button
if (data.primary_action) {
frappe.msg_dialog.set_primary_action(__(data.primary_action.label || "Done"),
data.primary_action.action);
}
// class "msgprint" is used in tests
frappe.msg_dialog.msg_area = $('<div class="msgprint">')
.appendTo(frappe.msg_dialog.body);
@ -137,6 +131,43 @@ frappe.msgprint = function(msg, title) {
frappe.msg_dialog.indicator = frappe.msg_dialog.header.find('.indicator');
}
// setup and bind an action to the primary button
if (data.primary_action) {
if (data.primary_action.server_action && typeof data.primary_action.server_action === 'string') {
data.primary_action.action = () => {
frappe.call({
method: data.primary_action.server_action,
args: {
args: data.primary_action.args
}
});
}
}
if (data.primary_action.client_action && typeof data.primary_action.client_action === 'string') {
let parts = data.primary_action.client_action.split('.');
let obj = window;
for (let part of parts) {
obj = obj[part];
}
data.primary_action.action = () => {
if (typeof obj === 'function') {
obj(data.primary_action.args);
}
}
}
frappe.msg_dialog.set_primary_action(
__(data.primary_action.label || "Done"),
data.primary_action.action
);
} else {
if (frappe.msg_dialog.has_primary_action) {
frappe.msg_dialog.get_primary_btn().addClass('hide');
frappe.msg_dialog.has_primary_action = false;
}
}
if(data.message==null) {
data.message = '';
}

View file

@ -1,3 +1,5 @@
frappe.provide('frappe.search');
frappe.ui.Notifications = class Notifications {
constructor() {
frappe.model
@ -29,16 +31,25 @@ frappe.ui.Notifications = class Notifications {
);
frappe.utils.bind_actions_with_object(this.$dropdown_list, this);
let me = this;
frappe.search.utils.make_function_searchable(
me.route_to_settings,
__('Notification Settings'),
);
this.setup_notifications();
this.bind_events();
}
route_to_settings() {
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
}
setup_notifications() {
this.get_notifications_list(this.max_length).then(list => {
this.dropdown_items = list;
this.render_notifications_dropdown();
if (this.$notifications.find('.unseen').length) {
if (this.notifications_settings.seen == 0) {
this.$notification_indicator.show();
}
});
@ -204,7 +215,7 @@ frappe.ui.Notifications = class Notifications {
change_activity_status() {
if (this.$dropdown_list.find('.activity-status')) {
this.$dropdown_list.find('.activity-status').replaceWith(
`<a class="recent-item text-center text-muted"
`<a class="recent-item text-center text-muted"
href="#List/Notification Log">
<div class="full-log-btn">${__('View Full Log')}</div>
</a>`
@ -212,26 +223,44 @@ frappe.ui.Notifications = class Notifications {
}
}
set_field_as_seen(docname, $el) {
set_field_as_read(docname, $el) {
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.mark_as_seen',
'frappe.desk.doctype.notification_log.notification_log.mark_as_read',
{ docname: docname }
).then(()=> {
$el.removeClass('unseen');
$el.removeClass('unread');
});
}
explicitly_mark_as_seen(e, $target) {
explicitly_mark_as_read(e, $target) {
e.preventDefault();
e.stopImmediatePropagation();
let docname = $target.parents('.unseen').attr('data-name');
this.set_field_as_seen(docname, $target.parents('.unseen'));
let docname = $target.parents('.unread').attr('data-name');
this.set_field_as_read(docname, $target.parents('.unread'));
}
mark_as_seen(e, $target) {
mark_as_read(e, $target) {
let docname = $target.attr('data-name');
let df = this.dropdown_items.filter(f => docname.includes(f.name))[0];
this.set_field_as_seen(df.name, $target);
this.set_field_as_read(df.name, $target);
}
mark_all_as_read(e) {
e.stopImmediatePropagation();
this.$dropdown_list.find('.unread').removeClass('unread');
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.mark_all_as_read',
);
}
toggle_seen(flag) {
frappe.call(
'frappe.desk.doctype.notification_settings.notification_settings.set_seen_value',
{
value: cint(flag),
user: frappe.session.user
}
);
}
get_notifications_list(limit) {
@ -279,8 +308,8 @@ frappe.ui.Notifications = class Notifications {
field.document_type,
field.document_name
);
let seen_class = field.seen ? '' : 'unseen';
let mark_seen_action = field.seen ? '': 'data-action="mark_as_seen"';
let read_class = field.read ? '' : 'unread';
let mark_read_action = field.read ? '': 'data-action="mark_as_read"';
let message = field.subject;
let title = message.match(/<b class="subject-title">(.*?)<\/b>/);
message = title ? message.replace(title[1], frappe.ellipsis(title[1], 100)): message;
@ -288,18 +317,18 @@ frappe.ui.Notifications = class Notifications {
let user = field.from_user;
let user_avatar = frappe.avatar(user, 'avatar-small user-avatar');
let timestamp = frappe.datetime.comment_when(field.creation, true);
let item_html =
`<a class="recent-item ${seen_class}"
let item_html =
`<a class="recent-item ${read_class}"
href="${doc_link}"
data-name="${field.name}"
${mark_seen_action}
${mark_read_action}
>
${user_avatar}
${message_html}
<div class="notification-timestamp text-muted">
${timestamp}
</div>
<span class="mark-read text-muted hidden-xs" data-action="explicitly_mark_as_seen">
<span class="mark-read text-muted hidden-xs" data-action="explicitly_mark_as_read">
${__('Mark as Read')}
</span>
</a>`;
@ -329,18 +358,25 @@ frappe.ui.Notifications = class Notifications {
let category_id = frappe.dom.get_unique_id();
let settings_html =
category.value === 'Notifications'
? `<span class="notification-settings pull-right" data-action="make_and_route_to_settings">
? `<span class="notification-settings pull-right" data-action="go_to_settings">
${__('Settings')}
</span>`
: '';
let mark_all_read_html =
category.value === 'Notifications'
? `<span class="mark-all-read pull-right" data-action="mark_all_as_read">
${__('Mark all as Read')}
</span>`
: '';
let html = `<li class="notifications-category">
<li class="text-muted header"
data-action="${category.action}"
href="#${category_id}"
href="#${category_id}"
data-toggle="collapse">
${category.label}
<span class="octicon octicon-chevron-down collapse-indicator"></span>
${settings_html}
${mark_all_read_html}
</li>
<div id="${category_id}" class="collapse category-list" data-category="${category.value}">
<div class="text-center text-muted notifications-loading">
@ -364,20 +400,11 @@ frappe.ui.Notifications = class Notifications {
);
}
make_and_route_to_settings(e) {
go_to_settings(e) {
e.stopImmediatePropagation();
this.$dropdown.removeClass('open');
this.$dropdown.trigger('hide.bs.dropdown');
let method =
'frappe.desk.doctype.notification_settings.notification_settings.create_notification_settings';
return Promise.resolve()
.then(() => {
if (!this.notifications_settings) return frappe.call(method);
})
.then(() => {
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
});
this.route_to_settings();
}
bind_events() {
@ -418,22 +445,14 @@ frappe.ui.Notifications = class Notifications {
});
this.$dropdown.on('hide.bs.dropdown', e => {
let hide = $(e.currentTarget).data('closable');
if (hide) {
this.$dropdown_list
.find('[data-category="Notifications"]')
.collapse('show');
this.$dropdown_list
.find(
'[data-category="Todays Events"], [data-category="Open Documents"]'
)
.collapse('hide');
}
$(e.currentTarget).data('closable', true);
return hide;
});
this.$dropdown.on('show.bs.dropdown', () => {
this.toggle_seen(true);
if (this.$notification_indicator.is(':visible')) {
this.$notification_indicator.hide();
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide'
);
@ -490,4 +509,4 @@ frappe.ui.notifications = {
}
frappe.set_route('List', doctype);
}
};
};

View file

@ -622,20 +622,21 @@ frappe.search.utils = {
value: this.bolden_match_part(__(item.label), txt),
index: this.fuzzy_search(txt, target),
match: item.label,
onclick: item.action,
onclick: () => item.action.apply(this, item.args)
});
}
});
return results;
},
make_function_searchable(_function, label=null) {
make_function_searchable(_function, label=null, args=null) {
if (typeof _function !== 'function') {
throw new Error('First argument should be a function');
}
this.searchable_functions.push({
'label': label || _function.name,
'action': _function
'action': _function,
'args': args,
});
},
searchable_functions: [],

View file

@ -63,7 +63,7 @@ export default {
}
},
dropdown_links() {
return this.links.length > 0 ? this.links
return this.type === 'module' ? this.links
.filter(link => !link.hidden)
.concat([
{ label: __('Customize'), action: () => this.$emit('customize'), class: 'border-top' }

View file

@ -26,7 +26,8 @@ export default {
},
data() {
return {
dragging: false
dragging: false,
fetched_module_links: {}
}
},
mounted() {
@ -53,6 +54,7 @@ export default {
})
},
show_module_card_customize_dialog(module) {
const me = this;
const d = new frappe.ui.Dialog({
title: __('Customize Shortcuts'),
fields: [
@ -60,11 +62,19 @@ export default {
label: __('Shortcuts'),
fieldname: 'links',
fieldtype: 'MultiSelectPills',
get_data() {
return frappe.call('frappe.desk.moduleview.get_links_for_module', {
app: module.app,
module: module.module_name,
}).then(r => r.message);
get_data: () => {
const module_links = me.fetched_module_links[module.module_name];
if (!module_links) {
return frappe.xcall('frappe.desk.moduleview.get_links_for_module', {
app: module.app,
module: module.module_name,
}).then(links => {
me.fetched_module_links[module.module_name] = links;
return links;
});
} else {
return module_links;
}
},
default: module.links.filter(l => !l.hidden).map(l => l.name)
}
@ -73,7 +83,7 @@ export default {
primary_action: ({ links }) => {
frappe.call('frappe.desk.moduleview.update_links_for_module', {
module_name: module.module_name,
links
links: links || []
}).then(r => {
this.$emit('update-desktop-settings', r.message);
});

View file

@ -975,12 +975,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
return this.data[index];
}
}).filter(Boolean);
let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => {
row[cell.column.id] = cell.content;
return row;
}, {});
rows.push(totalRow);
if (this.raw_data.add_total_row) {
let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => {
row[cell.column.id] = cell.content;
return row;
}, {});
rows.push(totalRow);
}
return rows;
}

View file

@ -500,10 +500,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
axisOptions: {
shortenYAxisNumbers: 1
},
format_tooltip_x: value => value.doc.name,
format_tooltip_y:
value => frappe.format(value, get_df(value.field), { always_show_decimals: true, inline: true }, get_doc(value.doc))
tooltipOptions: {
formatTooltipY: value => frappe.format(value, get_df(this.chart_args.y_axes[0]), { always_show_decimals: true, inline: true }, get_doc(value.doc))
}
});
}
@ -997,7 +996,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
content: d[cdt_field(col.field)],
editable: Boolean(name && this.is_editable(col.docfield, d)),
format: value => {
return frappe.format(value, col.docfield, { always_show_decimals: true });
return frappe.format(value, col.docfield, { always_show_decimals: true }, d);
}
};
}

View file

@ -40,7 +40,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
set_field_values() {
if (this.doc_name) this.set_values(this.doc);
if (this.doc.name) this.set_values(this.doc);
else return;
}
@ -101,7 +101,9 @@ export default class WebForm extends frappe.ui.FieldGroup {
this.validate && this.validate();
// validation hack: get_values will check for missing data
super.get_values(this.allow_incomplete);
let isvalid = super.get_values(this.allow_incomplete);
if (!isvalid) return;
if (window.saving) return;
let for_payment = Boolean(this.accept_payment && !this.doc.paid);

View file

@ -52,12 +52,13 @@ frappe.ready(function() {
const data = setup_fields(r.message);
let web_form_doc = data.web_form;
if (web_form_doc.doc_name && web_form_doc.allow_edit === 0) {
window.location.replace(window.location.pathname + "?new=1");
return;
if (web_form_doc.name && web_form_doc.allow_edit === 0) {
if (!window.location.href.includes("?new=1")) {
window.location.replace(window.location.pathname + "?new=1");
}
}
let doc = r.message.doc || build_doc(r.message);
web_form.prepare(web_form_doc, doc);
web_form.prepare(web_form_doc, r.message.doc && web_form_doc.allow_edit === 1 ? r.message.doc : {});
web_form.make();
web_form.set_default_values();
})

View file

@ -1,26 +1,148 @@
frappe.provide("frappe.integration_service")
/* HOW-TO
frappe.integration_service.razorpay = {
load: function(frm) {
new frappe.integration_service.Razorpay(frm)
},
scheduler_job_helper: function(){
return {
"Every few minutes": "Check and capture new payments"
Razorpay Payment
1. Include checkout script in your code
<script type="text/javascript" src="/assets/js/checkout.min.js"></script>
2. Create the Order controller in your backend
def get_razorpay_order(self):
controller = get_payment_gateway_controller("Razorpay")
payment_details = {
"amount": 300,
...
"reference_doctype": "Conference Participant",
"reference_docname": self.name,
...
"receipt": self.name
}
return controller.create_order(**payment_details)
3. Inititate the payment in client using checkout API
function make_payment(ticket) {
var options = {
"name": "<CHECKOUT MODAL TITLE>",
"description": "<CHECKOUT MODAL DESCRIPTION>",
"image": "<CHECKOUT MODAL LOGO>",
"prefill": {
"name": "<CUSTOMER NAME>",
"email": "<CUSTOMER EMAIL>",
"contact": "<CUSTOMER PHONE>"
},
"theme": {
"color": "<MODAL COLOR>"
},
"doctype": "<REFERENCE DOCTYPE>",
"docname": "<REFERENCE DOCNAME"
};
razorpay = new frappe.checkout.razorpay(options)
razorpay.on_open = () => {
<SCRIPT TO RUN WHEN MODAL OPENS>
}
razorpay.on_success = () => {
<SCRIPT TO RUN ON PAYMENT SUCCESS>
}
razorpay.on_fail = () => {
<SCRIPT TO RUN ON PAYMENT FAILURE>
}
razorpay.init() // Creates the order and opens the modal
}
*/
frappe.provide("frappe.checkout");
frappe.require('https://checkout.razorpay.com/v1/checkout.js').then(() => {
frappe.checkout.razorpay = class RazorpayCheckout {
constructor(opts) {
Object.assign(this, opts);
}
init() {
frappe.run_serially([
() => this.get_key(),
() => this.make_order(),
() => this.prepare_options(),
() => this.setup_handler(),
() => this.show()
]);
}
show() {
this.razorpay = new Razorpay(this.options);
this.razorpay.once('ready', (response) => {
this.on_open && this.on_open(response);
})
this.razorpay.open();
}
get_key() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_api_key").then(res => {
this.key = res.message;
resolve(true);
})
});
}
make_order() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_order", {
doctype: this.doctype,
docname: this.docname
}).then(res => {
this.order = res.message;
resolve(true);
})
});
}
order_success(response) {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_success", {
integration_request: this.order.integration_request,
params: {
razorpay_payment_id: response.razorpay_payment_id,
razorpay_order_id: response.razorpay_order_id,
razorpay_signature: response.razorpay_signature
}
})
}
order_fail(response) {
frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_failure", {
integration_request: this.order.integration_request,
params: response
})
}
prepare_options() {
this.options = {
"key": this.key,
"amount": this.order.amount_due,
"currency": this.order.currency,
"name": this.name,
"description": this.description,
"image": this.image,
"order_id": this.order.id,
"prefill": this.prefill,
"theme": this.theme,
"modal": this.modal
};
}
setup_handler() {
this.options.handler = (response) => {
if (response.error) {
this.order_fail(response);
this.on_fail && this.on_fail(response);
}
else if (response.razorpay_payment_id) {
this.order_success(response);
this.on_success && this.on_success(response);
}
}
}
}
}
frappe.integration_service.Razorpay = Class.extend({
init:function(frm){
this.frm = frm;
this.frm.toggle_display("use_test_account", false);
this.show_logs();
},
show_logs: function(){
this.frm.add_custom_button(__("Show Log"), function(frm){
frappe.route_options = {"integration_request_service": "Razorpay"};
frappe.set_route("List", "Integration Request");
});
}
})
});

View file

@ -7,6 +7,11 @@
cursor: pointer;
}
.mark-all-read {
margin-top: 2px;
margin-right: 15px;
}
.notification-settings {
margin-top: 2px;
}
@ -35,6 +40,10 @@
margin-left: 150px;
}
.navbar .dropdown-notifications .notifications-icon {
padding-top: 9px;
}
.notifications-indicator {
font-size: 7px;
position: absolute;
@ -64,7 +73,7 @@ a.recent-item:hover {
background-color: #f0f4f7;
}
a.unseen:hover .mark-read {
a.unread:hover .mark-read {
display: inline-block;
}
@ -89,7 +98,7 @@ a.unseen:hover .mark-read {
font-weight: 500;
}
.unseen {
.unread {
background: @light-yellow;
}

View file

@ -400,7 +400,9 @@ body[data-route^="Module"] .main-menu {
max-height: 300px;
overflow-y: auto;
max-width: 250px;
z-index: 100;
}
.dropdown-search {
padding: 8px;
}

View file

@ -101,6 +101,10 @@ h4.modal-title {
font-size: 1em;
}
h5.modal-title {
margin: 0px !important;
}
.col-xs-1 { @extend .col-1; }
.col-xs-2 { @extend .col-2; }
.col-xs-3 { @extend .col-3; }

View file

@ -21,14 +21,15 @@ frappe.ui.form.on('Energy Point Log', {
reqd: 1
}],
primary_action: (values) => {
return frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.revert', {
'name': frm.doc.name,
return frm.call('revert', {
'reason': values.reason
}).then(revert_log => {
}).then(res => {
let revert_log = res.message;
revert_dialog.hide();
revert_dialog.clear();
frappe.model.docinfo[frm.doc.reference_doctype][frm.doc.reference_name].energy_point_logs.unshift(revert_log);
}).catch(() => {});
frm.refresh();
});
},
primary_action_label: __('Submit')
});

View file

@ -44,6 +44,36 @@ class EnergyPointLog(Document):
enqueue_create_notification(self.user, notification_doc)
def on_trash(self):
if self.type == 'Revert':
reference_log = frappe.get_doc('Energy Point Log', self.revert_of)
reference_log.reverted = 0
reference_log.save()
def revert(self, reason):
frappe.only_for('System Manager')
if self.type != 'Auto':
frappe.throw(_('This document cannot be reverted'))
if self.get('reverted'):
return
self.reverted = 1
self.save(ignore_permissions=True)
revert_log = frappe.get_doc({
'doctype': 'Energy Point Log',
'points': -(self.points),
'type': 'Revert',
'user': self.user,
'reason': reason,
'reference_doctype': self.reference_doctype,
'reference_name': self.reference_name,
'revert_of': self.name
}).insert(ignore_permissions=True)
return revert_log
def get_notification_message(doc):
owner_name = get_fullname(doc.owner)
points = doc.points
@ -149,7 +179,8 @@ def check_if_log_exists(ref_doctype, ref_name, rule, user=None):
filters = frappe._dict({
'rule': rule,
'reference_doctype': ref_doctype,
'reference_name': ref_name
'reference_name': ref_name,
'reverted': 0
})
if user:
@ -257,32 +288,6 @@ def get_reviews(doctype, docname):
'type': ['in', ('Appreciation', 'Criticism')],
}, fields=['points', 'owner', 'type', 'user', 'reason', 'creation'])
@frappe.whitelist()
def revert(name, reason):
frappe.only_for('System Manager')
doc_to_revert = frappe.get_doc('Energy Point Log', name)
if doc_to_revert.type != 'Auto':
frappe.throw(_('This document cannot be reverted'))
if doc_to_revert.reverted: return
doc_to_revert.reverted = 1
doc_to_revert.save(ignore_permissions=True)
revert_log = frappe.get_doc({
'doctype': 'Energy Point Log',
'points': -(doc_to_revert.points),
'type': 'Revert',
'user': doc_to_revert.user,
'reason': reason,
'reference_doctype': doc_to_revert.reference_doctype,
'reference_name': doc_to_revert.reference_name,
'revert_of': doc_to_revert.name
}).insert(ignore_permissions=True)
return revert_log
def send_weekly_summary():
send_summary('Weekly')
@ -325,5 +330,3 @@ def get_footer_message(timespan):
return _("Stats based on last month's performance (from {0} to {1})")
else:
return _("Stats based on last week's performance (from {0} to {1})")

View file

@ -249,6 +249,27 @@ class TestEnergyPointLog(unittest.TestCase):
# point should not be awarded more than once for same doc (irrespective of user)
self.assertEqual(second_user_points_after_closing_todo, second_user_points)
def test_allow_creation_of_new_log_if_the_previous_log_was_reverted(self):
frappe.set_user('test@example.com')
todo_point_rule = create_energy_point_rule_for_todo()
energy_point_of_user = get_points('test@example.com')
created_todo = create_a_todo()
created_todo.status = 'Closed'
created_todo.save()
points_after_closing_todo = get_points('test@example.com')
log_name = frappe.db.exists('Energy Point Log', {'reference_name': created_todo.name})
frappe.get_doc('Energy Point Log', log_name).revert('Just for test')
points_after_reverting_todo = get_points('test@example.com')
created_todo.save()
points_after_saving_todo_again = get_points('test@example.com')
rule_points = todo_point_rule.points
self.assertEqual(points_after_closing_todo, energy_point_of_user + rule_points)
self.assertEqual(points_after_reverting_todo, points_after_closing_todo - rule_points)
self.assertEqual(points_after_saving_todo_again, points_after_reverting_todo + rule_points)
def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Custom', max_points=None,
for_assigned_users=0, field_to_check=None, apply_once=False, user_field='owner'):

View file

@ -8,7 +8,8 @@ from frappe import _
import frappe.cache_manager
from frappe.model.document import Document
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log, revert
from frappe.social.doctype.energy_point_log.energy_point_log import \
create_energy_points_log
class EnergyPointRule(Document):
def on_update(self):
@ -82,7 +83,8 @@ class EnergyPointRule(Document):
def process_energy_points(doc, state):
if (frappe.flags.in_patch
or frappe.flags.in_install
or frappe.flags.in_migrate):
or frappe.flags.in_migrate
or frappe.flags.in_import):
return
if not is_energy_point_enabled():
@ -106,7 +108,8 @@ def revert_points_for_cancelled_doc(doc):
'type': 'Auto'
})
for log in energy_point_logs:
revert(log.name, _('Reference document has been cancelled'))
reference_log = frappe.get_doc('Energy Point Log', log.name)
reference_log.revert(_('Reference document has been cancelled'))
def get_energy_point_doctypes():

View file

@ -80,7 +80,6 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_
if filter_str:
doc_filter += ' and ' + filter_str if doc_filter else filter_str
month_to_value_dict = get_monthly_results(goal_doctype, goal_field, date_field, doc_filter, aggregation)
frappe.db.set_value(doctype, docname, goal_history_field, json.dumps(month_to_value_dict))
month_to_value_dict[current_month_year] = current_month_value

View file

@ -90,7 +90,7 @@ def as_json():
def as_pdf():
response = Response()
response.mimetype = "application/pdf"
encoded_filename = quote(frappe.response['filename'].replace(' ', '_'), encoding='utf-8')
encoded_filename = quote(frappe.response['filename'].replace(' ', '_'))
response.headers["Content-Disposition"] = ("filename=\"%s\"" % frappe.response['filename'].replace(' ', '_') + ";filename*=utf-8''%s" % encoded_filename).encode("utf-8")
response.data = frappe.response['filecontent']
return response

View file

@ -337,3 +337,10 @@ def get_last_active():
WHERE `user_type` = 'System User' AND `name` NOT IN ({standard_users})"""
.format(standard_users=", ".join(["%s"]*len(STANDARD_USERS))),
STANDARD_USERS)[0][0]
@frappe.whitelist()
def activate_scheduler():
if is_scheduler_disabled():
enable_scheduler()
if frappe.conf.pause_scheduler:
update_site_config('pause_scheduler', 0)

View file

@ -161,7 +161,8 @@ class UserPermissions:
for docname in docs:
if frappe.get_meta(docname, cached=True).allow_import == 1:
self.can_import.append(docname)
frappe.cache().hset("can_import", frappe.session.user, self.can_import)
frappe.cache().hset("can_import", frappe.session.user, self.can_import)
def get_defaults(self):
import frappe.defaults

View file

@ -1,528 +1,158 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 1,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2013-03-28 10:35:30",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"title",
"published_on",
"published",
"column_break_3",
"blog_category",
"blogger",
"route",
"section_break_5",
"blog_intro",
"content_type",
"content",
"content_md",
"content_html",
"email_sent"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "published_on",
"fieldtype": "Date",
"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": "Published On",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Published On"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "published",
"fieldtype": "Check",
"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": "Published",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Published"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"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,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "blog_category",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Blog Category",
"length": 0,
"no_copy": 0,
"options": "Blog Category",
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "blogger",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Blogger",
"length": 0,
"no_copy": 0,
"options": "Blogger",
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "route",
"fieldtype": "Data",
"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": "Route",
"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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"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,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Description for listing page, in plain text, only a couple of lines. (max 140 characters)",
"fieldname": "blog_intro",
"fieldtype": "Small Text",
"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": "Blog Intro",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Blog Intro"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Rich Text",
"fieldname": "content_type",
"fieldtype": "Select",
"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": "Content Type",
"length": 0,
"no_copy": 0,
"options": "Rich Text\nMarkdown\nHTML",
"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,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'Rich Text'",
"fieldname": "content",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 1,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Content",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Content"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "content_md",
"fieldtype": "Markdown Editor",
"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": "Content (Markdown)",
"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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Content (Markdown)"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'HTML'",
"fieldname": "content_html",
"fieldtype": "HTML Editor",
"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": "Content (HTML)",
"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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"ignore_xss_filter": 1,
"label": "Content (HTML)"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "email_sent",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Sent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"translatable": 0,
"unique": 0
"label": "Email Sent"
}
],
"has_web_view": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-quote-left",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_published_field": "published",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2019-02-09 11:27:05.619819",
"modified": "2019-11-18 11:14:56.402471",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"set_user_permissions": 1,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Blogger",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"route": "/blog",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "title",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View file

@ -61,7 +61,7 @@ class BlogPost(WebsiteGenerator):
context.content = get_html_content_based_on_type(self, 'content', self.content_type)
context.description = self.blog_intro or context.content[:140]
context.description = self.blog_intro or strip_html_tags(context.content[:140])
context.metatags = {
"name": self.title,
@ -145,11 +145,12 @@ def get_blog_category(route):
def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
conditions = []
category = filters.blog_category or sanitize_html(frappe.local.form_dict.blog_category or frappe.local.form_dict.category)
if filters:
if filters.blogger:
conditions.append('t1.blogger=%s' % frappe.db.escape(filters.blogger))
if filters.blog_category:
conditions.append('t1.blog_category=%s' % frappe.db.escape(filters.blog_category))
if category:
conditions.append('t1.blog_category=%s' % frappe.db.escape(category))
if txt:
conditions.append('(t1.content like {0} or t1.title like {0}")'.format(frappe.db.escape('%' + txt + '%')))

View file

@ -132,12 +132,12 @@ $.extend(frappe, {
if (data._server_messages) {
var server_messages = JSON.parse(data._server_messages || '[]');
server_messages = $.map(server_messages, function(v) {
server_messages.map((msg) => {
// temp fix for messages sent as dict
try {
return JSON.parse(v).message;
return JSON.parse(msg);
} catch (e) {
return v;
return msg;
}
}).join('<br>');

View file

@ -9,6 +9,12 @@
{% block page_content %}
<!-- {{ for_test }} -->
<div style='min-height: 360px'>
<noscript>
<div class="text-center my-5">
<h4>{{ _("Javascript is disabled on your browser") }}</h4>
<p class="text-muted">{{ _("You need to enable JavaScript for your app to work.") }}<br>{{ _("To enable it follow the instructions in the following link: {0}").format("<a href='https://enable-javascript.com/'>enable-javascript.com</a></p>") }}
</div>
</noscript>
<section class='for-login'>
<div class="login-content page-card" style="margin-top: 30px;">
<form class="form-signin form-login" role="form">

255
yarn.lock
View file

@ -255,12 +255,17 @@ ansi-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
ansi-styles@^3.2.1:
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
@ -628,10 +633,10 @@ camelcase@^3.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-api@^3.0.0:
version "3.0.0"
@ -738,14 +743,14 @@ cliui@^3.2.0:
strip-ansi "^3.0.1"
wrap-ansi "^2.0.0"
cliui@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
dependencies:
string-width "^2.1.1"
strip-ansi "^4.0.0"
wrap-ansi "^2.0.0"
string-width "^3.1.0"
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
clone@^2.1.1, clone@^2.1.2:
version "2.1.2"
@ -953,15 +958,6 @@ cross-spawn@^3.0.0:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
dependencies:
lru-cache "^4.0.1"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -1253,7 +1249,7 @@ debug@~4.1.0:
dependencies:
ms "^2.1.1"
decamelize@^1.1.1, decamelize@^1.1.2:
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@ -1373,6 +1369,11 @@ elegant-spinner@^1.0.1:
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
@ -1516,19 +1517,6 @@ execa@0.10.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
dependencies:
cross-spawn "^5.0.1"
get-stream "^3.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
executable@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
@ -1722,12 +1710,12 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^2.0.0"
locate-path "^3.0.0"
for-in@^1.0.2:
version "1.0.2"
@ -1803,7 +1791,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fstream@^1.0.0, fstream@^1.0.2:
fstream@^1.0.0, fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
@ -1856,6 +1844,11 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-pixels@^3.2.3:
version "3.3.2"
resolved "https://registry.yarnpkg.com/get-pixels/-/get-pixels-3.3.2.tgz#3f62fb8811932c69f262bba07cba72b692b4ff03"
@ -1942,9 +1935,9 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@~7.1.1:
path-is-absolute "^1.0.0"
glob@^7.1.3:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@ -1989,7 +1982,12 @@ globule@^1.0.0:
lodash "~4.17.10"
minimatch "~3.0.2"
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
graceful-fs@^4.1.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graceful-fs@^4.1.6:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
@ -2249,7 +2247,12 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.3, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3:
inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@ -2766,12 +2769,12 @@ loader-utils@^0.2.16:
json5 "^0.5.0"
object-assign "^4.0.1"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
dependencies:
p-locate "^2.0.0"
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash.camelcase@^4.3.0:
@ -2900,13 +2903,6 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
mem@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
dependencies:
mimic-fn "^1.0.0"
meow@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@ -3000,11 +2996,6 @@ mime@^1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -3347,15 +3338,6 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
os-locale@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
dependencies:
execa "^0.7.0"
lcid "^1.0.0"
mem "^1.1.0"
os-tmpdir@^1.0.0, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@ -3374,19 +3356,19 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
p-limit@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
dependencies:
p-try "^1.0.0"
p-try "^2.0.0"
p-locate@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
dependencies:
p-limit "^1.1.0"
p-limit "^2.0.0"
p-map@^1.1.1:
version "1.2.0"
@ -3398,10 +3380,10 @@ p-queue@^2.4.2:
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34"
integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parchment@quilljs/parchment#487850f7eb030a6c4e750ba809e58b09444e0bdb:
version "2.0.0-dev"
@ -4283,6 +4265,11 @@ require-main-filename@^1.0.1:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
reserved-words@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
@ -4337,9 +4324,9 @@ rgba-regex@^1.0.0:
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
rimraf@2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
@ -4572,12 +4559,12 @@ shebang-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
showdown@^1.8.6:
version "1.9.0"
resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.9.0.tgz#d49d2a0b6db21b7c2e96ef855f7b3b2a28ef46f4"
integrity sha512-x7xDCRIaOlicbC57nMhGfKamu+ghwsdVkHMttyn+DelwzuHOx4OHCVL/UW/2QOLH7BxfCcCCVVUix3boKXJKXQ==
showdown@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.9.1.tgz#134e148e75cd4623e09c21b0511977d79b5ad0ef"
integrity sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==
dependencies:
yargs "^10.0.3"
yargs "^14.2"
signal-exit@^3.0.0:
version "3.0.2"
@ -4825,7 +4812,7 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1:
"string-width@^1.0.2 || 2":
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@ -4833,6 +4820,15 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
dependencies:
emoji-regex "^7.0.1"
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@ -4854,6 +4850,13 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@ -4962,12 +4965,12 @@ symbol-observable@1.0.1:
integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=
tar@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
version "2.2.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
dependencies:
block-stream "*"
fstream "^1.0.2"
fstream "^1.0.12"
inherits "2"
terser@^3.14.1:
@ -5296,6 +5299,15 @@ wrap-ansi@^2.0.0:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
dependencies:
ansi-styles "^3.2.0"
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@ -5318,11 +5330,24 @@ y18n@^3.2.1:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yargs-parser@^15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.0.tgz#cdd7a97490ec836195f59f3f4dbe5ea9e8f75f08"
integrity sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
@ -5330,30 +5355,22 @@ yargs-parser@^5.0.0:
dependencies:
camelcase "^3.0.0"
yargs-parser@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==
yargs@^14.2:
version "14.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.0.tgz#f116a9242c4ed8668790b40759b4906c276e76c3"
integrity sha512-/is78VKbKs70bVZH7w4YaZea6xcJWOAwkhbR0CFuZBmYtfTYF0xjGJF43AYd8g2Uii1yJwmS5GR2vBmrc32sbg==
dependencies:
camelcase "^4.1.0"
yargs@^10.0.3:
version "10.1.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==
dependencies:
cliui "^4.0.0"
decamelize "^1.1.1"
find-up "^2.1.0"
get-caller-file "^1.0.1"
os-locale "^2.0.0"
cliui "^5.0.0"
decamelize "^1.2.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^3.2.1"
yargs-parser "^8.1.0"
y18n "^4.0.0"
yargs-parser "^15.0.0"
yargs@^7.0.0:
version "7.1.0"