Merge branch 'develop' into patch-4

This commit is contained in:
Suraj Shetty 2020-05-01 11:23:38 +05:30 committed by GitHub
commit 244eadad4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 401 additions and 172 deletions

View file

@ -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",

View file

@ -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):

View file

@ -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)

View file

@ -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."""

View file

@ -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()

View file

@ -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) {

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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=''""")

View file

@ -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() },

View file

@ -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) {

View file

@ -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});
}

View file

@ -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);
});
}

View file

@ -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});
}
});

View file

@ -162,6 +162,6 @@
top: 8px;
height: 15px;
right: 12px;
top: 8px;
pointer-events: none;
}
}

View file

@ -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 {

View file

@ -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 -%}

View file

@ -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") {

View file

@ -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')

View file

@ -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

View file

@ -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>
`);
}

View file

@ -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",

View file

@ -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",

View file

@ -15,7 +15,7 @@
</p>
</div>
<p class="lead">
{{ description }}
{{ blog_intro }}
</p>
<div itemprop="articleBody" class="longform blog-text mt-5">
{{ content }}

View file

@ -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",

View file

@ -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():

View file

@ -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",

View file

@ -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 -%}

View file

@ -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 -%}

View file

@ -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",

View file

@ -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">

View file

@ -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 -%}

View file

@ -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",

View file

@ -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 -%}

View file

@ -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",

View file

@ -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",

View file

@ -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)}`
}),

View file

@ -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"