Merge branch 'develop' into patch-4
This commit is contained in:
commit
244eadad4e
41 changed files with 401 additions and 172 deletions
|
|
@ -16,7 +16,7 @@ global_cache_keys = ("app_hooks", "installed_apps",
|
|||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
|
||||
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts',
|
||||
'sitemap_routes')
|
||||
'sitemap_routes', 'db_tables')
|
||||
|
||||
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
"defaults", "user_permissions", "home_page", "linked_with",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class TestExporter(unittest.TestCase):
|
|||
e = Exporter('Web Page', export_fields='All')
|
||||
csv_array = e.get_csv_array()
|
||||
header = csv_array[0]
|
||||
self.assertEqual(len(header), 35)
|
||||
self.assertEqual(len(header), 36)
|
||||
|
||||
|
||||
def test_exports_selected_fields(self):
|
||||
|
|
|
|||
|
|
@ -712,9 +712,10 @@ def validate_fields(meta):
|
|||
if d.fieldtype == "Currency" and cint(d.width) < 100:
|
||||
frappe.throw(_("Max width for type Currency is 100px in row {0}").format(d.idx))
|
||||
|
||||
def check_in_list_view(d):
|
||||
def check_in_list_view(is_table, d):
|
||||
if d.in_list_view and (d.fieldtype in not_allowed_in_list_view):
|
||||
frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx))
|
||||
property_label = 'In Grid View' if is_table else 'In List View'
|
||||
frappe.throw(_("'{0}' not allowed for type {1} in row {2}").format(property_label, d.fieldtype, d.idx))
|
||||
|
||||
def check_in_global_search(d):
|
||||
if d.in_global_search and d.fieldtype in no_value_fields:
|
||||
|
|
@ -906,6 +907,16 @@ def validate_fields(meta):
|
|||
|
||||
frappe.msgprint(text_str + df_options_str, title="Invalid Data Field", raise_exception=True)
|
||||
|
||||
def check_child_table_option(docfield):
|
||||
if docfield.fieldtype not in ['Table MultiSelect', 'Table']: return
|
||||
|
||||
doctype = docfield.options
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
if not meta.istable:
|
||||
frappe.throw(_('Option {0} for field {1} is not a child table')
|
||||
.format(frappe.bold(doctype), frappe.bold(docfield.fieldname)), title=_("Invalid Option"))
|
||||
|
||||
|
||||
fields = meta.get("fields")
|
||||
fieldname_list = [d.fieldname for d in fields]
|
||||
|
|
@ -929,11 +940,12 @@ def validate_fields(meta):
|
|||
check_link_table_options(meta.get("name"), d)
|
||||
check_dynamic_link_options(d)
|
||||
check_hidden_and_mandatory(meta.get("name"), d)
|
||||
check_in_list_view(d)
|
||||
check_in_list_view(meta.get('istable'), d)
|
||||
check_in_global_search(d)
|
||||
check_illegal_default(d)
|
||||
check_unique_and_text(meta.get("name"), d)
|
||||
check_illegal_depends_on_conditions(d)
|
||||
check_child_table_option(d)
|
||||
check_table_multiselect_option(d)
|
||||
scrub_options_in_select(d)
|
||||
scrub_fetch_from(d)
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ class Database(object):
|
|||
# in transaction validations
|
||||
self.check_transaction_status(query)
|
||||
|
||||
self.clear_db_table_cache(query)
|
||||
|
||||
# autocommit
|
||||
if auto_commit: self.commit()
|
||||
|
||||
|
|
@ -277,6 +279,11 @@ class Database(object):
|
|||
ret.append(frappe._dict(zip(keys, values)))
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def clear_db_table_cache(query):
|
||||
if query and query.strip().split()[0].lower() in {'drop', 'create'}:
|
||||
frappe.cache().delete_key('db_tables')
|
||||
|
||||
@staticmethod
|
||||
def needs_formatting(result, formatted):
|
||||
"""Returns true if the first row in the result has a Date, Datetime, Long Int."""
|
||||
|
|
@ -769,7 +776,16 @@ class Database(object):
|
|||
return ("tab" + doctype) in self.get_tables()
|
||||
|
||||
def get_tables(self):
|
||||
return [d[0] for d in self.sql("select table_name from information_schema.tables where table_schema not in ('pg_catalog', 'information_schema')")]
|
||||
tables = frappe.cache().get_value('db_tables')
|
||||
if not tables:
|
||||
table_rows = self.sql("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
""")
|
||||
tables = {d[0] for d in table_rows}
|
||||
frappe.cache().set_value('db_tables', tables)
|
||||
return tables
|
||||
|
||||
def a_row_exists(self, doctype):
|
||||
"""Returns True if atleast one row exists."""
|
||||
|
|
|
|||
|
|
@ -137,16 +137,14 @@ class DBTable:
|
|||
if frappe.db.is_missing_column(e):
|
||||
# Unknown column 'column_name' in 'field list'
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
raise
|
||||
|
||||
if max_length and max_length[0][0] and max_length[0][0] > new_length:
|
||||
if col.fieldname in self.columns:
|
||||
self.columns[col.fieldname].length = current_length
|
||||
|
||||
frappe.msgprint(_("""Reverting length to {0} for '{1}' in '{2}';
|
||||
Setting the length as {3} will cause truncation of data.""")
|
||||
.format(current_length, col.fieldname, self.doctype, new_length))
|
||||
info_message = _("Reverting length to {0} for '{1}' in '{2}'. Setting the length as {3} will cause truncation of data.") \
|
||||
.format(current_length, col.fieldname, self.doctype, new_length)
|
||||
frappe.msgprint(info_message)
|
||||
|
||||
def is_new(self):
|
||||
return self.table_name not in frappe.db.get_tables()
|
||||
|
|
|
|||
|
|
@ -18,24 +18,29 @@ frappe.ui.form.on('Number Card', {
|
|||
});
|
||||
frm.set_value('filters_json', '[]');
|
||||
frm.set_value('aggregate_function_based_on', '');
|
||||
if (frm.doc.document_type) {
|
||||
frm.trigger('set_options');
|
||||
}
|
||||
frm.trigger('set_options');
|
||||
},
|
||||
|
||||
set_options: function(frm) {
|
||||
let aggregate_based_on_fields = [];
|
||||
const doctype = frm.doc.document_type;
|
||||
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
frappe.get_meta(doctype).fields.map(df => {
|
||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
|
||||
aggregate_based_on_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
});
|
||||
if (doctype) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
frappe.get_meta(doctype).fields.map(df => {
|
||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
|
||||
if (df.fieldtype == 'Currency') {
|
||||
if (!df.options || df.options !== 'Company:company:default_currency') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
aggregate_based_on_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_df_property('aggregate_function_based_on', 'options', aggregate_based_on_fields);
|
||||
});
|
||||
frm.set_df_property('aggregate_function_based_on', 'options', aggregate_based_on_fields);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render_filters_table: function(frm) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
class NumberCard(Document):
|
||||
pass
|
||||
|
|
@ -67,7 +68,7 @@ def get_result(doc, to_date=None):
|
|||
res = frappe.db.get_all(doc.document_type, fields=fields, filters=filters)
|
||||
number = res[0]['result'] if res else 0
|
||||
|
||||
return number
|
||||
return cint(number)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_percentage_difference(doc, result):
|
||||
|
|
@ -141,4 +142,3 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
|
|||
search_conditions=search_conditions,
|
||||
conditions=conditions
|
||||
), values)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import socket
|
|||
import time
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_address, cint, get_datetime, DATE_FORMAT, strip, comma_or, sanitize_html
|
||||
from frappe.utils import validate_email_address, cint, get_datetime, DATE_FORMAT, strip, comma_or, sanitize_html, add_days
|
||||
from frappe.utils.user import is_system_user
|
||||
from frappe.utils.jinja import render_template
|
||||
from frappe.email.smtp import SMTPServer
|
||||
|
|
@ -533,28 +533,37 @@ class EmailAccount(Document):
|
|||
parent = None
|
||||
in_reply_to = (email.mail.get("In-Reply-To") or "").strip(" <>")
|
||||
|
||||
if in_reply_to and "@{0}".format(frappe.local.site) in in_reply_to:
|
||||
# reply to a communication sent from the system
|
||||
email_queue = frappe.db.get_value('Email Queue', dict(message_id=in_reply_to), ['communication','reference_doctype', 'reference_name'])
|
||||
if email_queue:
|
||||
parent_communication, parent_doctype, parent_name = email_queue
|
||||
if parent_communication:
|
||||
communication.in_reply_to = parent_communication
|
||||
if in_reply_to:
|
||||
if "@{0}".format(frappe.local.site) in in_reply_to:
|
||||
# reply to a communication sent from the system
|
||||
email_queue = frappe.db.get_value('Email Queue', dict(message_id=in_reply_to), ['communication','reference_doctype', 'reference_name'])
|
||||
if email_queue:
|
||||
parent_communication, parent_doctype, parent_name = email_queue
|
||||
if parent_communication:
|
||||
communication.in_reply_to = parent_communication
|
||||
else:
|
||||
reference, domain = in_reply_to.split("@", 1)
|
||||
parent_doctype, parent_name = 'Communication', reference
|
||||
|
||||
if frappe.db.exists(parent_doctype, parent_name):
|
||||
parent = frappe._dict(doctype=parent_doctype, name=parent_name)
|
||||
|
||||
# set in_reply_to of current communication
|
||||
if parent_doctype=='Communication':
|
||||
# communication.in_reply_to = email_queue.communication
|
||||
|
||||
if parent.reference_name:
|
||||
# the true parent is the communication parent
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
else:
|
||||
reference, domain = in_reply_to.split("@", 1)
|
||||
parent_doctype, parent_name = 'Communication', reference
|
||||
|
||||
if frappe.db.exists(parent_doctype, parent_name):
|
||||
parent = frappe._dict(doctype=parent_doctype, name=parent_name)
|
||||
|
||||
# set in_reply_to of current communication
|
||||
if parent_doctype=='Communication':
|
||||
# communication.in_reply_to = email_queue.communication
|
||||
|
||||
if parent.reference_name:
|
||||
# the true parent is the communication parent
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
comm = frappe.db.get_value('Communication',
|
||||
dict(
|
||||
message_id=in_reply_to,
|
||||
creation=['>=', add_days(get_datetime(), -30)]),
|
||||
['reference_doctype', 'reference_name'], as_dict=1)
|
||||
if comm:
|
||||
parent = frappe._dict(doctype=comm.reference_doctype, name=comm.reference_name)
|
||||
|
||||
return parent
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ from frappe.core.doctype.server_script.server_script_utils import run_server_scr
|
|||
from werkzeug.wrappers import Response
|
||||
from six import string_types
|
||||
|
||||
ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet')
|
||||
|
||||
|
||||
def handle():
|
||||
"""handle request"""
|
||||
validate_auth()
|
||||
|
|
@ -148,12 +154,14 @@ def uploadfile():
|
|||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def upload_file():
|
||||
user = None
|
||||
if frappe.session.user == 'Guest':
|
||||
if frappe.get_system_settings('allow_guests_to_upload_files'):
|
||||
ignore_permissions = True
|
||||
else:
|
||||
return
|
||||
else:
|
||||
user = frappe.get_doc("User", frappe.session.user)
|
||||
ignore_permissions = False
|
||||
|
||||
files = frappe.request.files
|
||||
|
|
@ -175,11 +183,11 @@ def upload_file():
|
|||
frappe.local.uploaded_file = content
|
||||
frappe.local.uploaded_filename = filename
|
||||
|
||||
if frappe.session.user == 'Guest':
|
||||
if frappe.session.user == 'Guest' or (user and not user.has_desk_access()):
|
||||
import mimetypes
|
||||
filetype = mimetypes.guess_type(filename)[0]
|
||||
if filetype not in ['image/png', 'image/jpeg', 'application/pdf']:
|
||||
frappe.throw("You can only upload JPG, PNG or PDF files.")
|
||||
if filetype not in ALLOWED_MIMETYPES:
|
||||
frappe.throw(_("You can only upload JPG, PNG, PDF, or Microsoft documents."))
|
||||
|
||||
if method:
|
||||
method = frappe.get_attr(method)
|
||||
|
|
|
|||
|
|
@ -274,3 +274,4 @@ frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
|
|||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v13_0.website_theme_custom_scss
|
||||
frappe.patches.v13_0.set_existing_dashboard_charts_as_public
|
||||
frappe.patches.v13_0.set_path_for_homepage_in_web_page_view
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Dashboard Chart')
|
||||
frappe.reload_doc('desk', 'doctype', 'dashboard_chart')
|
||||
|
||||
if not frappe.db.table_exists('Dashboard Chart'):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('website', 'doctype', 'web_page_view', force=True)
|
||||
frappe.db.sql("""UPDATE `tabWeb Page View` set path="/" where path=''""")
|
||||
|
|
@ -2523,7 +2523,7 @@ class extends Component {
|
|||
h("div",{class:"input-group input-group-lg"},
|
||||
!frappe._.is_empty(props.actions) ?
|
||||
h("div",{class:"input-group-btn dropup"},
|
||||
h(frappe.components.Button,{ class: "dropdown-toggle", "data-toggle": "dropdown"},
|
||||
h(frappe.components.Button,{ class: (frappe.session.user === "Guest" ? "disabled" : "dropdown-toggle"), "data-toggle": "dropdown"},
|
||||
h(frappe.components.FontAwesome, { class: "text-muted", type: "paperclip", fixed: true })
|
||||
),
|
||||
h("div",{ class:"dropdown-menu dropdown-menu-left", onclick: e => e.stopPropagation() },
|
||||
|
|
|
|||
|
|
@ -261,26 +261,22 @@ frappe.utils.xss_sanitise = function (string, options) {
|
|||
}
|
||||
|
||||
frappe.utils.sanitise_redirect = (url) => {
|
||||
const is_absolute = ((url) => {
|
||||
// https://github.com/sindresorhus/is-absolute-url
|
||||
// Don't match Windows paths `c:\`
|
||||
if (/^[a-zA-Z]:\\/.test(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
|
||||
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
|
||||
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url);
|
||||
});
|
||||
|
||||
const is_external = (() => {
|
||||
return (url) => {
|
||||
function domain(url) {
|
||||
let base_domain = /https?:\/\/((?:[\w\d]+\.)+[\w\d]{2,})/i.exec(url);
|
||||
let base_domain = /^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?]+)/img.exec(url);
|
||||
return base_domain == null ? "" : base_domain[1];
|
||||
}
|
||||
|
||||
return domain(location.href) !== domain(url);
|
||||
function is_absolute(url) {
|
||||
// returns true for url that have a defined scheme
|
||||
// anything else, eg. internal urls return false
|
||||
return /^(?:[a-z]+:)?\/\//i.test(url);
|
||||
}
|
||||
|
||||
// check for base domain only if the url is absolute
|
||||
// return true for relative url (except protocol-relative urls)
|
||||
return is_absolute(url) ? domain(location.href) !== domain(url) : true;
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
@ -291,11 +287,16 @@ frappe.utils.sanitise_redirect = (url) => {
|
|||
return url.replace(REGEX_SCRIPT, "");
|
||||
});
|
||||
|
||||
if (is_absolute(url) && is_external(url)) {
|
||||
return '';
|
||||
}
|
||||
url = frappe.utils.strip_url(url);
|
||||
|
||||
return sanitise_javascript(frappe.utils.xss_sanitise(url, {strategies: ["js"]}));
|
||||
return is_external(url) ? "" : sanitise_javascript(frappe.utils.xss_sanitise(url, {strategies: ["js"]}));
|
||||
};
|
||||
|
||||
frappe.utils.strip_url = (url) => {
|
||||
// strips invalid characters from the beginning of the URL
|
||||
// in our case, the url can start with either a protocol, //, or even #
|
||||
// so anything except those characters can be considered invalid
|
||||
return url.replace(/^[^A-Za-z0-9(//)#]+/g, '');
|
||||
}
|
||||
|
||||
frappe.utils.new_auto_repeat_prompt = function(frm) {
|
||||
|
|
|
|||
|
|
@ -71,12 +71,14 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView {
|
|||
{
|
||||
chart_type: ['in', ['Count', 'Sum', 'Group By']],
|
||||
document_type: this.doctype,
|
||||
is_public: 1,
|
||||
},
|
||||
'charts'
|
||||
),
|
||||
() => this.fetch_dashboard_items('Number Card',
|
||||
{
|
||||
document_type: this.doctype,
|
||||
is_public: 1,
|
||||
},
|
||||
'number_cards'
|
||||
),
|
||||
|
|
@ -427,6 +429,11 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView {
|
|||
date_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
|
||||
if (df.fieldtype == 'Currency') {
|
||||
if (!df.options || df.options !== 'Company:company:default_currency') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
value_fields.push({label: df.label, value: df.fieldname});
|
||||
aggregate_function_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ export default class NumberCardWidget extends Widget {
|
|||
frappe.model.with_doc('Number Card', this.name).then(card => {
|
||||
if (!card) {
|
||||
if (this.document_type) {
|
||||
this.create_number_card();
|
||||
this.render_card();
|
||||
frappe.run_serially([
|
||||
() => this.create_number_card(),
|
||||
() => this.render_card(),
|
||||
]);
|
||||
} else {
|
||||
// widget doesn't exist so delete
|
||||
this.delete(false);
|
||||
|
|
@ -50,13 +52,14 @@ export default class NumberCardWidget extends Widget {
|
|||
|
||||
create_number_card() {
|
||||
this.set_doc_args();
|
||||
frappe.xcall(
|
||||
return frappe.xcall(
|
||||
'frappe.desk.doctype.number_card.number_card.create_number_card',
|
||||
{
|
||||
'args': this.card_doc
|
||||
}
|
||||
).then(doc => {
|
||||
this.name = doc.name;
|
||||
this.card_doc.name = this.name;
|
||||
this.widget.attr('data-widget-name', this.name);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -372,6 +372,11 @@ class NumberCardDialog extends WidgetDialog {
|
|||
if (this.document_type) {
|
||||
frappe.get_meta(this.document_type).fields.map(df => {
|
||||
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
|
||||
if (df.fieldtype == 'Currency') {
|
||||
if (!df.options || df.options !== 'Company:company:default_currency') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
aggregate_function_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -162,6 +162,6 @@
|
|||
top: 8px;
|
||||
height: 15px;
|
||||
right: 12px;
|
||||
top: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,10 @@ details.hide-summary-arrow summary::-webkit-details-marker {
|
|||
}
|
||||
|
||||
.from-markdown {
|
||||
@apply text-gray-900;
|
||||
@apply leading-relaxed;
|
||||
|
||||
> * + * {
|
||||
@apply mt-6;
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
|
|
@ -50,28 +49,45 @@ details.hide-summary-arrow summary::-webkit-details-marker {
|
|||
@apply px-4 py-3 text-sm font-medium text-gray-900 border border-gray-400 rounded-md bg-gray-50;
|
||||
}
|
||||
|
||||
> h1 {
|
||||
@apply text-4xl;
|
||||
@apply mt-16;
|
||||
@apply mb-4;
|
||||
@apply leading-none;
|
||||
@apply font-bold;
|
||||
h1 {
|
||||
@apply mt-16 mb-4 text-3xl font-extrabold leading-tight tracking-tight;
|
||||
@screen sm {
|
||||
@apply text-4xl leading-10;
|
||||
}
|
||||
@screen xl {
|
||||
@apply text-5xl leading-none;
|
||||
}
|
||||
}
|
||||
|
||||
> h2 {
|
||||
@apply mt-16;
|
||||
@apply mb-4;
|
||||
@apply leading-none;
|
||||
@apply font-bold;
|
||||
@apply text-3xl;
|
||||
h1 + p {
|
||||
@apply max-w-2xl mt-3 text-base text-gray-900;
|
||||
|
||||
@screen sm {
|
||||
@apply mt-5 text-lg;
|
||||
}
|
||||
@screen md {
|
||||
@apply mt-5 text-xl;
|
||||
}
|
||||
}
|
||||
|
||||
> h3 {
|
||||
@apply mt-16;
|
||||
@apply mb-4;
|
||||
@apply leading-none;
|
||||
@apply font-bold;
|
||||
@apply text-2xl;
|
||||
h2 {
|
||||
@apply mb-4 text-2xl font-bold leading-tight mt-14;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply mt-12 mb-4 text-xl font-semibold leading-tight;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply mt-10 mb-4 text-lg font-semibold leading-tight;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@apply mt-8 mb-4 text-base font-semibold leading-tight;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@apply mt-6 mb-4 text-sm font-semibold leading-tight;
|
||||
}
|
||||
|
||||
> a,
|
||||
|
|
@ -84,9 +100,17 @@ details.hide-summary-arrow summary::-webkit-details-marker {
|
|||
}
|
||||
}
|
||||
|
||||
table {
|
||||
@apply w-full my-8 border-t;
|
||||
}
|
||||
|
||||
tbody {
|
||||
@apply border-t;
|
||||
}
|
||||
|
||||
tr > td,
|
||||
tr > th {
|
||||
@apply px-4 py-2 border border-gray-400;
|
||||
@apply py-4 pr-6 text-sm leading-6 text-left border-b;
|
||||
}
|
||||
|
||||
th:empty {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
web_block.css_class
|
||||
]) -%}
|
||||
|
||||
{%- if not web_block.hide_block -%}
|
||||
<{{htmltag}} class="{{ classes }}" data-section-idx="{{ web_block.idx | e }}"
|
||||
data-section-template="{{ web_block.web_template | e }}">
|
||||
{%- if web_block.add_container -%}
|
||||
|
|
@ -17,3 +18,4 @@
|
|||
</div>
|
||||
{%- endif -%}
|
||||
</{{htmltag}}>
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ login.login_handlers = (function() {
|
|||
login.set_indicator('{{ _("Success") }}', 'green');
|
||||
window.location.href = frappe.utils.sanitise_redirect(frappe.utils.get_url_arg("redirect-to")) || data.home_page;
|
||||
} else if(data.message == 'Password Reset'){
|
||||
window.location.href = data.redirect_to;
|
||||
window.location.href = frappe.utils.sanitise_redirect(data.redirect_to);
|
||||
} else if(data.message=="No App") {
|
||||
login.set_indicator("{{ _("Success") }}", 'green');
|
||||
if(localStorage) {
|
||||
|
|
@ -194,7 +194,7 @@ login.login_handlers = (function() {
|
|||
}
|
||||
|
||||
if(data.redirect_to) {
|
||||
window.location.href = data.redirect_to;
|
||||
window.location.href = frappe.utils.sanitise_redirect(data.redirect_to);
|
||||
}
|
||||
|
||||
if(last_visited && last_visited != "/login") {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class TestPermissions(unittest.TestCase):
|
|||
doc = frappe.get_doc("DocType", "Blog Post")
|
||||
|
||||
# change one property from the child table
|
||||
doc.fields[-1].fieldtype = 'HTML'
|
||||
doc.fields[-1].fieldtype = 'Check'
|
||||
self.assertRaises(frappe.CannotChangeConstantError, doc.save)
|
||||
frappe.clear_cache(doctype='DocType')
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def two_factor_is_enabled(user=None):
|
|||
if bypass_two_factor_auth and user:
|
||||
user_doc = frappe.get_doc("User", user)
|
||||
restrict_ip_list = user_doc.get_restricted_ip_list() #can be None or one or more than one ip address
|
||||
if restrict_ip_list:
|
||||
if restrict_ip_list and frappe.local.request_ip:
|
||||
for ip in restrict_ip_list:
|
||||
if frappe.local.request_ip.startswith(ip):
|
||||
enabled = False
|
||||
|
|
|
|||
|
|
@ -3,6 +3,40 @@
|
|||
|
||||
frappe.ui.form.on('Blog Post', {
|
||||
refresh: function(frm) {
|
||||
|
||||
generate_google_search_preview(frm);
|
||||
},
|
||||
title: function(frm) {
|
||||
generate_google_search_preview(frm);
|
||||
},
|
||||
meta_description: function(frm) {
|
||||
generate_google_search_preview(frm);
|
||||
},
|
||||
blog_intro: function(frm) {
|
||||
generate_google_search_preview(frm);
|
||||
}
|
||||
});
|
||||
|
||||
function generate_google_search_preview(frm) {
|
||||
let google_preview = frm.get_field("google_preview");
|
||||
let seo_title = (frm.doc.title).slice(0, 60);
|
||||
let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160);
|
||||
let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : '';
|
||||
let route_array = frm.doc.route.split('/');
|
||||
route_array.pop();
|
||||
|
||||
google_preview.html(`
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=swap" rel="stylesheet">
|
||||
<div style="font-family: Open Sans; padding: 15px; border: 1px solid #d1d8dd !important; border-radius: 6px;">
|
||||
<cite style="font-size: 14px; padding-top: 1px; line-height: 1.3; color: #202124; font-style: normal;">
|
||||
${frappe.boot.sitename}
|
||||
<span style="color: #5f6368;"> › ${route_array.join(' › ')}</span>
|
||||
</cite>
|
||||
<div style="font-size: 20px; line-height: 1.3; color: #1a0dab; padding-top: 4px; margin-bottom: 3px;">
|
||||
${ seo_title }
|
||||
</div>
|
||||
<p style="color: #545454; max-width: 48em; line-height: 1.58; font-size:14px;">
|
||||
<span style="color: #70757a;">${ date }</span> ${ seo_description }
|
||||
</p>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,13 @@
|
|||
"content",
|
||||
"content_md",
|
||||
"content_html",
|
||||
"email_sent"
|
||||
"email_sent",
|
||||
"meta_tags",
|
||||
"meta_description",
|
||||
"column_break_18",
|
||||
"meta_image",
|
||||
"section_break_20",
|
||||
"google_preview"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -123,6 +129,36 @@
|
|||
"fieldname": "disable_comments",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Comments"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Meta Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Meta Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_20",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "This is an example Google SERP Preview.",
|
||||
"fieldname": "google_preview",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Google Snippet Preview",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_tags",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Meta Tags"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
|
|
@ -131,7 +167,7 @@
|
|||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-04-08 19:58:13.672332",
|
||||
"modified": "2020-04-29 17:32:41.055883",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blog Post",
|
||||
|
|
|
|||
|
|
@ -65,16 +65,18 @@ class BlogPost(WebsiteGenerator):
|
|||
|
||||
|
||||
context.content = get_html_content_based_on_type(self, 'content', self.content_type)
|
||||
context.description = self.blog_intro or strip_html_tags(context.content[:140])
|
||||
|
||||
#if meta description is not present, then blog intro or first 140 characters of the blog will be set as description
|
||||
context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140])
|
||||
|
||||
context.metatags = {
|
||||
"name": self.title,
|
||||
"description": context.description,
|
||||
}
|
||||
|
||||
#if meta image is not present, then first image inside the blog will be set as the meta image
|
||||
image = find_first_image(context.content)
|
||||
if image:
|
||||
context.metatags["image"] = image
|
||||
context.metatags["image"] = self.meta_image or image or None
|
||||
|
||||
self.load_comments(context)
|
||||
|
||||
|
|
@ -95,7 +97,6 @@ class BlogPost(WebsiteGenerator):
|
|||
else:
|
||||
context.comment_text = _('{0} comments').format(len(context.comment_list))
|
||||
|
||||
|
||||
def get_list_context(context=None):
|
||||
list_context = frappe._dict(
|
||||
template = "templates/includes/blog/blog.html",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<p class="lead">
|
||||
{{ description }}
|
||||
{{ blog_intro }}
|
||||
</p>
|
||||
<div itemprop="articleBody" class="longform blog-text mt-5">
|
||||
{{ content }}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"column_break_5",
|
||||
"add_container",
|
||||
"add_padding",
|
||||
"add_shade"
|
||||
"add_shade",
|
||||
"hide_block"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -54,18 +55,24 @@
|
|||
"default": "0",
|
||||
"fieldname": "add_shade",
|
||||
"fieldtype": "Check",
|
||||
"label": "Shaded Section"
|
||||
"label": "Add Gray Background"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "add_container",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add Container"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_block",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Block"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-19 16:16:44.524042",
|
||||
"modified": "2020-04-29 15:08:25.976179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Page Block",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ def make_view_log(path, referrer=None, browser=None, version=None, url=None, use
|
|||
if referrer.startswith(url):
|
||||
is_unique = False
|
||||
|
||||
if path.startswith('/'):
|
||||
if path != "/" and path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
if is_tracking_enabled():
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Attach Image\nCheck\nData\nInt\nSelect\nSmall Text\nText\nMarkdown Editor",
|
||||
"options": "Attach Image\nCheck\nData\nInt\nSelect\nSmall Text\nText\nMarkdown Editor\nSection Break\nColumn Break",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-24 17:05:25.322767",
|
||||
"modified": "2020-04-29 14:53:23.192395",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Template Field",
|
||||
|
|
|
|||
|
|
@ -9,22 +9,12 @@
|
|||
</p>
|
||||
{%- endif -%}
|
||||
{%- if primary_action or secondary_action -%}
|
||||
<div class="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start">
|
||||
<div class="mt-5 sm:mt-8">
|
||||
{%- if primary_action -%}
|
||||
<div class="rounded-md shadow">
|
||||
<a href="{{ primary_action }}"
|
||||
class="flex items-center justify-center w-full px-8 py-3 text-base font-medium leading-6 text-white transition duration-150 ease-in-out border border-transparent rounded-md bg-primary-500 hover:bg-primary-400 focus:outline-none focus:shadow-outline md:py-4 md:text-lg md:px-10">
|
||||
{{ primary_action_label }}
|
||||
</a>
|
||||
</div>
|
||||
{{ c('button', label=primary_action_label, url=primary_action, variant="primary", size="large") }}
|
||||
{%- endif -%}
|
||||
{%- if secondary_action -%}
|
||||
<div class="mt-3 sm:mt-0 sm:ml-3">
|
||||
<a href="{{ secondary_action }}"
|
||||
class="flex items-center justify-center w-full px-8 py-3 text-base font-medium leading-6 transition duration-150 ease-in-out border border-transparent rounded-md text-primary-700 bg-primary-100 hover:text-primary-600 hover:bg-primary-50 focus:outline-none focus:shadow-outline focus:border-primary-300 md:py-4 md:text-lg md:px-10">
|
||||
{{ secondary_action_label }}
|
||||
</a>
|
||||
</div>
|
||||
{{ c('button', label=secondary_action_label, url=secondary_action, variant="secondary", size="large", class="ml-4") }}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="relative flex">
|
||||
<div class="relative flex items-center flex-shrink-0 w-full sm:w-7/12">
|
||||
<div class="relative flex items-center flex-shrink-0 w-full md:w-6/12">
|
||||
<div>
|
||||
<h1
|
||||
class="text-3xl font-extrabold leading-tight tracking-tight sm:leading-10 sm:text-4xl xl:leading-none xl:text-5xl">
|
||||
|
|
@ -22,8 +22,16 @@
|
|||
</div>
|
||||
</div>
|
||||
{%- if image -%}
|
||||
<div class="hidden sm:block lg:w-5/12">
|
||||
<img class="object-cover w-full h-56 lg:h-full lg:w-full md:h-96 sm:h-72" src="{{ image }}" alt="">
|
||||
</div>
|
||||
{{ c('image_with_blur',
|
||||
class=["hidden md:block max-h-144", "w-full md:w-6/12" if contain_image else "md:max-w-md lg:max-w-lg xl:max-w-xl xxl:max-w-2xl"],
|
||||
src=image,
|
||||
alt="")
|
||||
}}
|
||||
{%- endif -%}
|
||||
</div>
|
||||
|
||||
{%- if not contain_image -%}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => document.body.classList.add('overflow-x-hidden'));
|
||||
</script>
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@
|
|||
"label": "Image",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "contain_image",
|
||||
"fieldtype": "Check",
|
||||
"label": "Restrict Image inside Container",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "primary_action_label",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -47,7 +53,7 @@
|
|||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2020-04-26 15:08:26.937576",
|
||||
"modified": "2020-04-29 14:12:31.613545",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Hero with Right Image",
|
||||
"owner": "Administrator",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
</a>
|
||||
</div>
|
||||
<details class="z-10 flex items-center sm:hidden hide-summary-arrow">
|
||||
<summary>
|
||||
<button>
|
||||
<summary class="block h-6 list-none cursor-pointer focus:outline-none focus:shadow-outline">
|
||||
<button class="pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-menu">
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
'p-8': card_size == 'Large'
|
||||
}) -%}
|
||||
{%- set title_classes = resolve_class({
|
||||
'text-base': card_size == 'Small',
|
||||
'text-lg md:text-xl': card_size == 'Medium',
|
||||
'text-xl md:text-2xl': card_size == 'Large'
|
||||
'text-base font-semibold': card_size == 'Small',
|
||||
'text-lg md:text-xl font-semibold': card_size == 'Medium',
|
||||
'text-xl md:text-2xl font-bold': card_size == 'Large'
|
||||
}) -%}
|
||||
{%- set content_classes = resolve_class({
|
||||
'text-sm': card_size == 'Small',
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
}) -%}
|
||||
|
||||
<a href="{{ url or '#' }}"
|
||||
class="block bg-white border rounded-xl hover:shadow-lg {{ card_classes }}">
|
||||
<h3 class="font-semibold {{ title_classes }}">{{ title }}</h3>
|
||||
class="block bg-white border rounded-xl hover:border-gray-600 {{ card_classes }}">
|
||||
<h3 class="leading-none {{ title_classes }}">{{ title }}</h3>
|
||||
<p class="mt-4 text-gray-900 {{ content_classes }}">{{ content or '' }}</p>
|
||||
</a>
|
||||
{%- endmacro -%}
|
||||
|
|
|
|||
|
|
@ -22,153 +22,201 @@
|
|||
"options": "Small\nMedium\nLarge",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 1",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_1_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 1 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_1_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 1 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_1_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 1 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 2",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_2_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 2 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_2_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 2 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_2_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 2 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 3",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_3_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 3 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_3_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 3 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_3_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 3 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_4",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 4",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_4_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 4 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_4_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 4 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_4_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 4 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 5",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_5_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 5 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_5_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 5 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_5_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 5 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 6",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_6_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 6 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_6_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 6 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_6_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 6 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 7",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_7_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 7 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_7_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 7 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_7_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 7 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Card 8",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_8_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 8 Title",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_8_content",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Card 8 Content",
|
||||
"label": "Content",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "card_8_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Card 8 URL",
|
||||
"label": "URL",
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2020-04-21 21:24:04.192839",
|
||||
"modified": "2020-04-29 22:40:03.362229",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Section with Cards",
|
||||
"owner": "Administrator",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<div class="relative">
|
||||
<div class="px-8 py-12 text-center sm:px-12 md:py-20 bg-primary-100 rounded-xl">
|
||||
<h2 class="max-w-xl mx-auto text-2xl font-bold leading-tight md:text-3xl xl:text-4xl">{{ title }}</h2>
|
||||
<p class="max-w-xl mx-auto mt-2 text-base text-gray-900 sm:text-lg">{{ subtitle }}</p>
|
||||
<h2 class="max-w-xl mx-auto text-2xl font-extrabold leading-tight md:text-4xl">{{ title }}</h2>
|
||||
<p class="max-w-xl mx-auto mt-2 text-base text-gray-900 md:text-lg">{{ subtitle }}</p>
|
||||
<p class="mt-8">
|
||||
{{ c('button', label=cta_label, url=cta_url, variant="primary", size="large") }}
|
||||
</p>
|
||||
{%- if cta_description -%}
|
||||
<div class="max-w-xl mx-auto mt-2 text-sm text-gray-900">
|
||||
<div class="max-w-xl mx-auto mt-2 text-xs text-gray-900">
|
||||
{{ cta_description }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"font_properties": "300,600",
|
||||
"footer": [],
|
||||
"idx": 26,
|
||||
"modified": "2020-04-24 23:52:27.211811",
|
||||
"modified": "2020-04-29 12:26:48.399125",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Standard",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
"cssnano": "^4.1.10",
|
||||
"express": "^4.17.1",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"frappe-charts": "^1.3.0",
|
||||
"frappe-charts": "^1.3.2",
|
||||
"frappe-datatable": "^1.15.1",
|
||||
"frappe-gantt": "^0.1.0",
|
||||
"fuse.js": "^3.4.6",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ module.exports = {
|
|||
borderRadius: {
|
||||
xl: '0.75rem'
|
||||
},
|
||||
maxHeight: {
|
||||
'144': '36rem'
|
||||
},
|
||||
boxShadow: theme => ({
|
||||
'outline-primary': `0 0 0 3px ${rgba(theme('colors.blue.300'), 0.45)}`
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2304,10 +2304,10 @@ fragment-cache@^0.2.1:
|
|||
dependencies:
|
||||
map-cache "^0.2.2"
|
||||
|
||||
frappe-charts@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.3.0.tgz#9ed033fa64833906bba16554187fa2f8a3a54ef6"
|
||||
integrity sha512-hdLv4fOIVgIL5eV9KYlsQaEpxkcJvuEVVDJewJL8PG0ySPy5EEiG5KZGL2uj7YegVWbtsqJ4Oq/74mjgQoMdag==
|
||||
frappe-charts@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.3.2.tgz#28b26637f03efeb86a07a2a9180c0ef2d5f52c56"
|
||||
integrity sha512-9VscidL7TUxgnI8dM+s0WIphnthUsDwnknHFnUH1zsISWzuw1FEUd2v29cn+E1+eNlD1a0bNBd+rJPtMdMnnvQ==
|
||||
|
||||
frappe-datatable@^1.15.1:
|
||||
version "1.15.1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue