Merge branch 'develop' into fix-none-type-get-workflow
This commit is contained in:
commit
a12c97c4f8
68 changed files with 1838 additions and 397 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<div align="center">
|
||||
<img src=".github/frappe-framework-logo.png" height="150">
|
||||
<h1>
|
||||
<a href="https://frappe.io">
|
||||
<a href="https://frappeframework.com">
|
||||
frappe
|
||||
</a>
|
||||
</h1>
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
|
||||
|
||||
### Table of Contents
|
||||
* [Installation](#installation)
|
||||
* [Documentation](https://frappe.io/docs)
|
||||
* [Installation](https://frappeframework.com/docs/user/en/installation)
|
||||
* [Documentation](https://frappeframework.com/docs)
|
||||
* [License](#license)
|
||||
|
||||
### Installation
|
||||
|
|
@ -49,7 +49,7 @@ Full-stack web application framework that uses Python and MariaDB on the server
|
|||
### Website
|
||||
|
||||
For details and documentation, see the website
|
||||
[https://frappe.io](https://frappe.io)
|
||||
[https://frappeframework.com](https://frappeframework.com)
|
||||
|
||||
### License
|
||||
This repository has been released under the [MIT License](LICENSE).
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ context('Control Duration', () => {
|
|||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
|
||||
function get_dialog_with_duration(show_days=1, show_seconds=1) {
|
||||
function get_dialog_with_duration(hide_days=0, hide_seconds=0) {
|
||||
return cy.dialog({
|
||||
title: 'Duration',
|
||||
fields: [{
|
||||
'fieldname': 'duration',
|
||||
'fieldtype': 'Duration',
|
||||
'show_seconds': show_days,
|
||||
'show_days': show_seconds
|
||||
'hide_days': hide_days,
|
||||
'hide_seconds': hide_seconds
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ context('Control Duration', () => {
|
|||
});
|
||||
|
||||
it('should hide days or seconds according to duration options', () => {
|
||||
get_dialog_with_duration(0, 0).as('dialog');
|
||||
get_dialog_with_duration(1, 1).as('dialog');
|
||||
cy.get('.frappe-control[data-fieldname=duration] input').first().click();
|
||||
cy.get('.duration-input[data-duration=days]').should('not.be.visible');
|
||||
cy.get('.duration-input[data-duration=seconds]').should('not.be.visible');
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ context('Grid Pagination', () => {
|
|||
cy.get('@table').find('.current-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
});
|
||||
it('deletes all rows', ()=> {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
|
||||
cy.get('@table').find('button.grid-remove-all-rows').click();
|
||||
cy.get('.modal-dialog .btn-primary').contains('Yes').click();
|
||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 0);
|
||||
});
|
||||
// it('deletes all rows', ()=> {
|
||||
// cy.visit('/desk#Form/Contact/Test Contact');
|
||||
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
// cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
|
||||
// cy.get('@table').find('button.grid-remove-all-rows').click();
|
||||
// cy.get('.modal-dialog .btn-primary').contains('Yes').click();
|
||||
// cy.get('@table').find('.grid-body .grid-row').should('have.length', 0);
|
||||
// });
|
||||
});
|
||||
|
|
@ -502,7 +502,17 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
|
|||
if coverage:
|
||||
# Generate coverage report only for app that is being tested
|
||||
source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe')
|
||||
cov = Coverage(source=[source_path], omit=['*.html', '*.js', '*.xml', '*.css', '*/doctype/*/*_dashboard.py', '*/patches/*'])
|
||||
cov = Coverage(source=[source_path], omit=[
|
||||
'*.html',
|
||||
'*.js',
|
||||
'*.xml',
|
||||
'*.css',
|
||||
'*.less',
|
||||
'*.scss',
|
||||
'*.vue',
|
||||
'*/doctype/*/*_dashboard.py',
|
||||
'*/patches/*'
|
||||
])
|
||||
cov.start()
|
||||
|
||||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
|
||||
|
|
|
|||
|
|
@ -444,24 +444,48 @@ def update_parent_document_on_communication(doc):
|
|||
|
||||
status_field = parent.meta.get_field("status")
|
||||
if status_field:
|
||||
options = (status_field.options or '').splitlines()
|
||||
options = (status_field.options or "").splitlines()
|
||||
|
||||
# if status has a "Replied" option, then update the status for received communication
|
||||
if ('Replied' in options) and doc.sent_or_received=="Received":
|
||||
if ("Replied" in options) and doc.sent_or_received == "Received":
|
||||
parent.db_set("status", "Open")
|
||||
parent.run_method("handle_hold_time", "Replied")
|
||||
apply_assignment_rule(parent)
|
||||
else:
|
||||
# update the modified date for document
|
||||
parent.update_modified()
|
||||
|
||||
update_mins_to_first_communication(parent, doc)
|
||||
parent.run_method('notify_communication', doc)
|
||||
set_avg_response_time(parent, doc)
|
||||
parent.run_method("notify_communication", doc)
|
||||
parent.notify_update()
|
||||
|
||||
def update_mins_to_first_communication(parent, communication):
|
||||
if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'):
|
||||
if parent.meta.has_field("mins_to_first_response") and not parent.get("mins_to_first_response"):
|
||||
if is_system_user(communication.sender):
|
||||
first_responded_on = communication.creation
|
||||
if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent":
|
||||
parent.db_set('first_responded_on', first_responded_on)
|
||||
parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
|
||||
if parent.meta.has_field("first_responded_on") and communication.sent_or_received == "Sent":
|
||||
parent.db_set("first_responded_on", first_responded_on)
|
||||
parent.db_set("mins_to_first_response", round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
|
||||
|
||||
def set_avg_response_time(parent, communication):
|
||||
if parent.meta.has_field("avg_response_time") and communication.sent_or_received == "Sent":
|
||||
# avg response time for all the responses
|
||||
communications = frappe.get_list("Communication", filters={
|
||||
"reference_doctype": parent.doctype,
|
||||
"reference_name": parent.name
|
||||
},
|
||||
fields=["sent_or_received", "name", "creation"],
|
||||
order_by="creation"
|
||||
)
|
||||
|
||||
if len(communications):
|
||||
response_times = []
|
||||
for i in range(len(communications)):
|
||||
if communications[i].sent_or_received == "Sent" and communications[i-1].sent_or_received == "Received":
|
||||
response_time = round(time_diff_in_seconds(communications[i].creation, communications[i-1].creation), 2)
|
||||
if response_time > 0:
|
||||
response_times.append(response_time)
|
||||
if response_times:
|
||||
avg_response_time = sum(response_times) / len(response_times)
|
||||
parent.db_set("avg_response_time", avg_response_time)
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
"fieldname",
|
||||
"precision",
|
||||
"length",
|
||||
"show_days",
|
||||
"show_seconds",
|
||||
"hide_days",
|
||||
"hide_seconds",
|
||||
"reqd",
|
||||
"search_index",
|
||||
"in_list_view",
|
||||
|
|
@ -453,18 +453,18 @@
|
|||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_days",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Days"
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_seconds",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Seconds"
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -477,7 +477,7 @@
|
|||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-15 09:06:25.224411",
|
||||
"modified": "2020-02-06 09:06:25.224413",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -688,6 +688,9 @@ def validate_fields(meta):
|
|||
|
||||
def check_link_table_options(docname, d):
|
||||
if frappe.flags.in_patch: return
|
||||
|
||||
if frappe.flags.in_fixtures: return
|
||||
|
||||
if d.fieldtype in ("Link",) + table_fields:
|
||||
if not d.options:
|
||||
frappe.throw(_("{0}: Options required for Link or Table type field {1} in row {2}").format(docname, d.label, d.idx), DoctypeLinkError)
|
||||
|
|
@ -908,6 +911,8 @@ 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 frappe.flags.in_fixtures: return
|
||||
if docfield.fieldtype not in ['Table MultiSelect', 'Table']: return
|
||||
|
||||
doctype = docfield.options
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
"column_break_6",
|
||||
"fieldtype",
|
||||
"precision",
|
||||
"show_seconds",
|
||||
"show_days",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"options",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
|
|
@ -383,22 +383,18 @@
|
|||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_seconds",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Seconds",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_days",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Days",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -411,7 +407,7 @@
|
|||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-15 23:43:00.123572",
|
||||
"modified": "2020-02-06 23:43:00.123575",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ docfield_properties = {
|
|||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link',
|
||||
'allow_in_quick_entry': 'Check',
|
||||
'hide_border': 'Check'
|
||||
'hide_border': 'Check',
|
||||
'hide_days': 'Check',
|
||||
'hide_seconds': 'Check'
|
||||
}
|
||||
|
||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
"label",
|
||||
"fieldtype",
|
||||
"fieldname",
|
||||
"show_seconds",
|
||||
"show_days",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"reqd",
|
||||
"unique",
|
||||
"in_list_view",
|
||||
|
|
@ -393,22 +393,18 @@
|
|||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_seconds",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Seconds",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.fieldtype === \"Duration\";",
|
||||
"fieldname": "show_days",
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Days",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -421,7 +417,7 @@
|
|||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-15 23:45:46.810869",
|
||||
"modified": "2020-06-02 23:45:46.810868",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ CREATE TABLE `tabDocField` (
|
|||
`length` int(11) NOT NULL DEFAULT 0,
|
||||
`translatable` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_border` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_days` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_seconds` int(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `label` (`label`),
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ CREATE TABLE "tabDocField" (
|
|||
"length" bigint NOT NULL DEFAULT 0,
|
||||
"translatable" smallint NOT NULL DEFAULT 0,
|
||||
"hide_border" smallint NOT NULL DEFAULT 0,
|
||||
"hide_days" smallint NOT NULL DEFAULT 0,
|
||||
"hide_seconds" smallint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ website_route_rules = [
|
|||
{"from_route": "/profile", "to_route": "me"},
|
||||
]
|
||||
|
||||
base_template = "templates/base.html"
|
||||
|
||||
write_file_keys = ["file_url", "file_name"]
|
||||
|
||||
notification_config = "frappe.core.notifications.get_notification_config"
|
||||
|
|
@ -270,7 +272,10 @@ setup_wizard_exception = [
|
|||
]
|
||||
|
||||
before_migrate = ['frappe.patches.v11_0.sync_user_permission_doctype_before_migrate.execute']
|
||||
after_migrate = ['frappe.website.doctype.website_theme.website_theme.generate_theme_files_if_not_exist']
|
||||
after_migrate = [
|
||||
'frappe.website.doctype.website_theme.website_theme.generate_theme_files_if_not_exist',
|
||||
'frappe.modules.full_text_search.build_index_for_all_routes'
|
||||
]
|
||||
|
||||
otp_methods = ['OTP App','Email','SMS']
|
||||
user_privacy_documents = [
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True):
|
|||
did_not_upload, error_log = backup_to_dropbox(upload_db_backup)
|
||||
if did_not_upload: raise Exception
|
||||
|
||||
send_email(True, "Dropbox", "Dropbox Settings", "send_notifications_to")
|
||||
if cint(frappe.db.get_value("Dropbox Settings", None, "send_email_for_successful_backup")):
|
||||
send_email(True, "Dropbox", "Dropbox Settings", "send_notifications_to")
|
||||
except JobTimeoutException:
|
||||
if retry_count < 2:
|
||||
args = {
|
||||
|
|
|
|||
|
|
@ -483,6 +483,9 @@ class Meta(Document):
|
|||
def get_row_template(self):
|
||||
return self.get_web_template(suffix='_row')
|
||||
|
||||
def get_list_template(self):
|
||||
return self.get_web_template(suffix='_list')
|
||||
|
||||
def get_web_template(self, suffix=''):
|
||||
'''Returns the relative path of the row template for this doctype'''
|
||||
module_name = frappe.scrub(self.module)
|
||||
|
|
|
|||
106
frappe/modules/full_text_search.py
Normal file
106
frappe/modules/full_text_search.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from whoosh.index import create_in, open_dir
|
||||
from whoosh.fields import TEXT, ID, Schema
|
||||
from whoosh.qparser import MultifieldParser, FieldsPlugin, WildcardPlugin
|
||||
from whoosh.query import Prefix
|
||||
from bs4 import BeautifulSoup
|
||||
from frappe.website.render import render_page
|
||||
from frappe.utils import set_request, cint
|
||||
from frappe.utils.global_search import get_routes_to_index
|
||||
|
||||
|
||||
def build_index_for_all_routes():
|
||||
print("Building search index for all web routes...")
|
||||
routes = get_routes_to_index()
|
||||
documents = [get_document_to_index(route) for route in routes]
|
||||
build_index("web_routes", documents)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def web_search(index_name, query, scope=None, limit=20):
|
||||
limit = cint(limit)
|
||||
return search(index_name, query, scope, limit)
|
||||
|
||||
|
||||
def get_document_to_index(route):
|
||||
frappe.set_user("Guest")
|
||||
frappe.local.no_cache = True
|
||||
|
||||
try:
|
||||
set_request(method="GET", path=route)
|
||||
content = render_page(route)
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
page_content = soup.find(class_="page_content")
|
||||
text_content = page_content.text if page_content else ""
|
||||
title = soup.title.text.strip() if soup.title else route
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
return frappe._dict(title=title, content=text_content, path=route)
|
||||
except (
|
||||
frappe.PermissionError,
|
||||
frappe.DoesNotExistError,
|
||||
frappe.ValidationError,
|
||||
Exception,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def build_index(index_name, documents):
|
||||
schema = Schema(
|
||||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)
|
||||
)
|
||||
|
||||
index_dir = get_index_path(index_name)
|
||||
frappe.create_folder(index_dir)
|
||||
|
||||
ix = create_in(index_dir, schema)
|
||||
writer = ix.writer()
|
||||
|
||||
for document in documents:
|
||||
if document:
|
||||
writer.add_document(
|
||||
title=document.title, path=document.path, content=document.content
|
||||
)
|
||||
|
||||
writer.commit()
|
||||
|
||||
|
||||
def search(index_name, text, scope=None, limit=20):
|
||||
index_dir = get_index_path(index_name)
|
||||
ix = open_dir(index_dir)
|
||||
|
||||
results = None
|
||||
out = []
|
||||
with ix.searcher() as searcher:
|
||||
parser = MultifieldParser(["title", "content"], ix.schema)
|
||||
parser.remove_plugin_class(FieldsPlugin)
|
||||
parser.remove_plugin_class(WildcardPlugin)
|
||||
query = parser.parse(text)
|
||||
|
||||
filter_scoped = None
|
||||
if scope:
|
||||
filter_scoped = Prefix("path", scope)
|
||||
results = searcher.search(query, limit=limit, filter=filter_scoped)
|
||||
|
||||
for r in results:
|
||||
title_highlights = r.highlights("title")
|
||||
content_highlights = r.highlights("content")
|
||||
out.append(
|
||||
frappe._dict(
|
||||
title=r["title"],
|
||||
path=r["path"],
|
||||
title_highlights=title_highlights,
|
||||
content_highlights=content_highlights,
|
||||
)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_index_path(index_name):
|
||||
return frappe.get_site_path("indexes", index_name)
|
||||
|
|
@ -288,3 +288,4 @@ execute:frappe.delete_doc("DocType", "Onboarding Slide")
|
|||
execute:frappe.delete_doc("DocType", "Onboarding Slide Field")
|
||||
execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")
|
||||
frappe.patches.v13_0.update_date_filters_in_user_settings
|
||||
frappe.patches.v13_0.update_duration_options
|
||||
|
|
|
|||
28
frappe/patches/v13_0/update_duration_options.py
Normal file
28
frappe/patches/v13_0/update_duration_options.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('core', 'doctype', 'DocField')
|
||||
|
||||
if frappe.db.has_column('DocField', 'show_days'):
|
||||
frappe.db.sql("""
|
||||
UPDATE
|
||||
tabDocField
|
||||
SET
|
||||
hide_days = 1 WHERE show_days = 0
|
||||
""")
|
||||
frappe.db.sql_ddl('alter table tabDocField drop column show_days')
|
||||
|
||||
if frappe.db.has_column('DocField', 'show_seconds'):
|
||||
frappe.db.sql("""
|
||||
UPDATE
|
||||
tabDocField
|
||||
SET
|
||||
hide_seconds = 1 WHERE show_seconds = 0
|
||||
""")
|
||||
frappe.db.sql_ddl('alter table tabDocField drop column show_seconds')
|
||||
|
||||
frappe.clear_cache(doctype='DocField')
|
||||
183
frappe/public/css/hljs-night-owl.css
Normal file
183
frappe/public/css/hljs-night-owl.css
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
|
||||
Night Owl for highlight.js (c) Carl Baxter <carl@cbax.tech>
|
||||
|
||||
An adaptation of Sarah Drasner's Night Owl VS Code Theme
|
||||
https://github.com/sdras/night-owl-vscode-theme
|
||||
|
||||
Copyright (c) 2018 Sarah Drasner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1rem 1.25rem;
|
||||
background: #011627;
|
||||
color: #d6deeb;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* General Purpose */
|
||||
.hljs-keyword {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-built_in {
|
||||
color: #addb67;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-type {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-literal {
|
||||
color: #ff5874;
|
||||
}
|
||||
.hljs-number {
|
||||
color: #F78C6C;
|
||||
}
|
||||
.hljs-regexp {
|
||||
color: #5ca7e4;
|
||||
}
|
||||
.hljs-string {
|
||||
color: #ecc48d;
|
||||
}
|
||||
.hljs-subst {
|
||||
color: #d3423e;
|
||||
}
|
||||
.hljs-symbol {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-class {
|
||||
color: #ffcb8b;
|
||||
}
|
||||
.hljs-function {
|
||||
color: #82AAFF;
|
||||
}
|
||||
.hljs-title {
|
||||
color: #DCDCAA;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-params {
|
||||
color: #7fdbca;
|
||||
}
|
||||
|
||||
/* Meta */
|
||||
.hljs-comment {
|
||||
color: #637777;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-doctag {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-meta {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-meta-keyword {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-meta-string {
|
||||
color: #ecc48d;
|
||||
}
|
||||
|
||||
/* Tags, attributes, config */
|
||||
.hljs-section {
|
||||
color: #82b1ff;
|
||||
}
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-builtin-name {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-attr {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-attribute {
|
||||
color: #80cbc4;
|
||||
}
|
||||
.hljs-variable {
|
||||
color: #addb67;
|
||||
}
|
||||
|
||||
/* Markup */
|
||||
.hljs-bullet {
|
||||
color: #d9f5dd;
|
||||
}
|
||||
.hljs-code {
|
||||
color: #80CBC4;
|
||||
}
|
||||
.hljs-emphasis {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
color: #addb67;
|
||||
font-weight: bold;
|
||||
}
|
||||
.hljs-formula {
|
||||
color: #c792ea;
|
||||
}
|
||||
.hljs-link {
|
||||
color: #ff869a;
|
||||
}
|
||||
.hljs-quote {
|
||||
color: #697098;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* CSS */
|
||||
.hljs-selector-tag {
|
||||
color: #ff6363;
|
||||
}
|
||||
|
||||
.hljs-selector-id {
|
||||
color: #fad430;
|
||||
}
|
||||
|
||||
.hljs-selector-class {
|
||||
color: #addb67;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Templates */
|
||||
.hljs-template-tag {
|
||||
color: #c792ea;
|
||||
}
|
||||
.hljs-template-variable {
|
||||
color: #addb67;
|
||||
}
|
||||
|
||||
/* diff */
|
||||
.hljs-addition {
|
||||
color: #addb67ff;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #EF535090;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
|
|||
</div>`
|
||||
);
|
||||
this.$wrapper.append(this.$picker);
|
||||
this.build_numeric_input("days", !this.duration_options.show_days);
|
||||
this.build_numeric_input("days", this.duration_options.hide_days);
|
||||
this.build_numeric_input("hours", false);
|
||||
this.build_numeric_input("minutes", false);
|
||||
this.build_numeric_input("seconds", !this.duration_options.show_seconds);
|
||||
this.build_numeric_input("seconds", this.duration_options.hide_seconds);
|
||||
this.set_duration_picker_value(this.value);
|
||||
this.$picker.hide();
|
||||
this.bind_events();
|
||||
|
|
@ -130,10 +130,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
|
|||
if (this.inputs) {
|
||||
total_duration.minutes = parseInt(this.inputs.minutes.val());
|
||||
total_duration.hours = parseInt(this.inputs.hours.val());
|
||||
if (this.duration_options.show_days) {
|
||||
if (!this.duration_options.hide_days) {
|
||||
total_duration.days = parseInt(this.inputs.days.val());
|
||||
}
|
||||
if (this.duration_options.show_seconds) {
|
||||
if (!this.duration_options.hide_seconds) {
|
||||
total_duration.seconds = parseInt(this.inputs.seconds.val());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,8 +202,8 @@ frappe.ui.FilterList = Class.extend({
|
|||
value = {0:"No", 1:"Yes"}[cint(value)];
|
||||
} else if (field.df.original_type === "Duration") {
|
||||
let duration_options = {
|
||||
show_days: field.df.show_days,
|
||||
show_seconds: field.df.show_seconds
|
||||
hide_days: field.df.hide_days,
|
||||
hide_seconds: field.df.hide_seconds
|
||||
};
|
||||
value = frappe.utils.get_formatted_duration(value, duration_options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -856,7 +856,7 @@ Object.assign(frappe.utils, {
|
|||
minutes: Math.floor(secs % 3600 / 60),
|
||||
seconds: Math.floor(secs % 60)
|
||||
};
|
||||
if (!duration_options.show_days) {
|
||||
if (duration_options.hide_days) {
|
||||
total_duration.hours = Math.floor(secs / 3600);
|
||||
total_duration.days = 0;
|
||||
}
|
||||
|
|
@ -882,8 +882,8 @@ Object.assign(frappe.utils, {
|
|||
|
||||
get_duration_options: function(docfield) {
|
||||
let duration_options = {
|
||||
show_days: docfield.show_days,
|
||||
show_seconds: docfield.show_seconds
|
||||
hide_days: docfield.hide_days,
|
||||
hide_seconds: docfield.hide_seconds
|
||||
};
|
||||
return duration_options;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,14 +26,25 @@ export default class Desktop {
|
|||
}
|
||||
|
||||
make_container() {
|
||||
this.container = $(`<div class="desk-container row">
|
||||
this.container = $(`
|
||||
<div class="desk-container row">
|
||||
<div class="desk-sidebar"></div>
|
||||
<div class="desk-body"></div>
|
||||
<div class="desk-body">
|
||||
<div class="page-switcher">
|
||||
<div class="current-title"></div>
|
||||
<i class="fa fa-chevron-down text-muted"></i>
|
||||
</div>
|
||||
<div class="mobile-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
this.container.appendTo(this.wrapper);
|
||||
this.sidebar = this.container.find(".desk-sidebar");
|
||||
this.body = this.container.find(".desk-body");
|
||||
this.current_title = this.container.find(".current-title");
|
||||
this.mobile_list = this.container.find(".mobile-list");
|
||||
this.page_switcher = this.container.find(".page-switcher");
|
||||
}
|
||||
|
||||
fetch_desktop_settings() {
|
||||
|
|
@ -73,7 +84,9 @@ export default class Desktop {
|
|||
this.current_page = item.name;
|
||||
}
|
||||
let $item = get_sidebar_item(item);
|
||||
$item.appendTo(this.sidebar);
|
||||
|
||||
$item.appendTo(this.mobile_list);
|
||||
$item.clone().appendTo(this.sidebar);
|
||||
this.sidebar_items[item.name] = $item;
|
||||
};
|
||||
|
||||
|
|
@ -84,6 +97,7 @@ export default class Desktop {
|
|||
`<div class="sidebar-group-title h6 uppercase">${__(name)}</div>`
|
||||
);
|
||||
$title.appendTo(this.sidebar);
|
||||
$title.clone().appendTo(this.mobile_list);
|
||||
};
|
||||
|
||||
this.sidebar_categories.forEach(category => {
|
||||
|
|
@ -94,6 +108,11 @@ export default class Desktop {
|
|||
});
|
||||
}
|
||||
});
|
||||
if (frappe.is_mobile) {
|
||||
this.page_switcher.on('click', () => {
|
||||
this.mobile_list.toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
show_page(page) {
|
||||
|
|
@ -106,6 +125,8 @@ export default class Desktop {
|
|||
this.sidebar_items[page].addClass("selected");
|
||||
}
|
||||
this.current_page = page;
|
||||
this.mobile_list.hide();
|
||||
this.current_title.empty().append(this.current_page);
|
||||
localStorage.current_desk_page = page;
|
||||
this.pages[page] ? this.pages[page].show() : this.make_page(page);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ frappe.ready(function() {
|
|||
};
|
||||
|
||||
df.fields = form_data[df.fieldname];
|
||||
$.each(df.fields || [], function(_i, field) {
|
||||
if (field.fieldtype === "Link") {
|
||||
field.only_select = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (df.fieldtype === "Attach") {
|
||||
df.is_private = true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,40 @@
|
|||
.desk-container {
|
||||
margin-top: 20px;
|
||||
|
||||
.page-switcher {
|
||||
border-radius: 5px;
|
||||
display: none;
|
||||
border: 1px solid @border-color;
|
||||
background-color: @panel-bg;
|
||||
padding: 8px 15px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mobile-list {
|
||||
display: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 15px;
|
||||
border: 1px solid @border-color;
|
||||
|
||||
.sidebar-item {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1px;
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
text-rendering: optimizelegibility;
|
||||
|
||||
&.selected {
|
||||
background-color: @panel-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desk-sidebar {
|
||||
width: 20rem;
|
||||
display: block;
|
||||
|
|
@ -103,6 +137,9 @@
|
|||
.desk-body {
|
||||
padding-left: 15px !important;
|
||||
}
|
||||
.page-switcher {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ html {
|
|||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-size: 16px;
|
||||
color: $body-color;
|
||||
}
|
||||
|
|
@ -18,6 +19,7 @@ h1 {
|
|||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.025em;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
line-height: 2.5rem;
|
||||
|
|
@ -32,6 +34,7 @@ h1 {
|
|||
h2 {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
font-size: $font-size-2xl;
|
||||
|
|
|
|||
94
frappe/public/scss/blog.scss
Normal file
94
frappe/public/scss/blog.scss
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
.blog-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
|
||||
&.result {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 12rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.default-cover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $gray-200;
|
||||
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
.avatar {
|
||||
margin-right: 0.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
font-size: 1rem;
|
||||
max-width: 800px;
|
||||
margin: 0px auto;
|
||||
|
||||
.blog-title {
|
||||
margin-top: 1rem;
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
line-height: 1;
|
||||
font-size: $font-size-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: $text-muted;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.blog-intro {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.blog-content {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.blog-header {
|
||||
margin-bottom: 3rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
278
frappe/public/scss/doc.scss
Normal file
278
frappe/public/scss/doc.scss
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
$navbar-height: 7.625rem;
|
||||
$navbar-height-lg: 4.5rem;
|
||||
|
||||
.doc-layout {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
padding-top: $navbar-height;
|
||||
// border-bottom: 1px solid $gray-200;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding-top: $navbar-height-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-column {
|
||||
display: none;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-container {
|
||||
max-width: 1280px;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.navbar-expand-lg .doc-container {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.doc-navbar {
|
||||
background-color: white;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
.navbar-toggler {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.web-sidebar {
|
||||
display: block;
|
||||
border-top: 1px solid $gray-200;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
height: calc(100vh - #{$navbar-height-lg});
|
||||
overflow: auto;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
height: auto;
|
||||
overflow: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
margin-left: -1rem;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.doc-search-container {
|
||||
display: flex;
|
||||
margin-top: 0.75rem;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-search {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding-left: 4rem;
|
||||
padding-right: 4rem;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 2.5rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
input {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
.dropdown-item {
|
||||
padding: 1rem 0.75rem;
|
||||
}
|
||||
|
||||
.match {
|
||||
background-color: $primary-light;
|
||||
color: $primary;
|
||||
font-weight: 500;
|
||||
padding: 0 0.125rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.doc-sidebar {
|
||||
position: sticky;
|
||||
top: $navbar-height;
|
||||
padding-bottom: 4rem;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
.web-sidebar {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
top: $navbar-height-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-main .page-content-wrapper {
|
||||
padding: 0 0 2rem 0;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding: 0rem 4rem 4rem 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.doc-sidebar-logo {
|
||||
padding-top: 2.5rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-toc {
|
||||
font-size: $font-size-sm;
|
||||
|
||||
h5 {
|
||||
font-size: $font-size-sm;
|
||||
margin-bottom: 0.5rem;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
> div {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 4rem;
|
||||
position: sticky;
|
||||
top: $navbar-height;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
top: $navbar-height-lg;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
li > ul {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
color: $gray-600;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
color: $gray-800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// typography styles for documentation content
|
||||
.doc-content .from-markdown {
|
||||
> :first-child {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: $font-size-3xl;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h1 + p {
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $font-size-2xl;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-xl;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
&::before {
|
||||
height: 6rem;
|
||||
margin-top: -6rem;
|
||||
content: '';
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
table {
|
||||
border-color: $gray-200;
|
||||
}
|
||||
|
||||
table thead {
|
||||
background-color: $light;
|
||||
}
|
||||
|
||||
.table-bordered,
|
||||
.table-bordered th,
|
||||
.table-bordered td {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-color: $gray-200;
|
||||
}
|
||||
|
||||
.table-bordered thead th,
|
||||
.table-bordered thead td {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
// next links
|
||||
.btn-next-wrapper {
|
||||
border-top: 1px solid $gray-200;
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
.from-markdown {
|
||||
color: $gray-700;
|
||||
line-height: 1.625;
|
||||
|
||||
> * + * {
|
||||
|
|
@ -32,12 +33,11 @@
|
|||
}
|
||||
|
||||
> blockquote {
|
||||
padding: 0.75rem 1rem;
|
||||
padding: 1.25rem 1rem;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: 500;
|
||||
color: $gray-900;
|
||||
border-left: 4px solid $yellow;
|
||||
background-color: lighten($yellow, 42%);
|
||||
border: 1px solid $gray-200;
|
||||
border-left: 3px solid $yellow;
|
||||
border-top-left-radius: 0.1rem;
|
||||
border-bottom-left-radius: 0.1rem;
|
||||
border-top-right-radius: 0.375rem;
|
||||
|
|
@ -49,11 +49,17 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
color: $gray-800;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
h1 + p {
|
||||
max-width: 42rem;
|
||||
margin-top: 0.75rem;
|
||||
font-size: $font-size-base;
|
||||
color: $gray-900;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-top: 1.25rem;
|
||||
|
|
@ -104,6 +110,7 @@
|
|||
tr > td,
|
||||
tr > th {
|
||||
font-size: $font-size-sm;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
th:empty {
|
||||
|
|
@ -114,11 +121,10 @@
|
|||
border: 1px solid $gray-400;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
// apply margin on first h1 if container is full width without top margin
|
||||
main:not(.my-5) .from-markdown {
|
||||
h1:first-child {
|
||||
margin-top: 5rem;
|
||||
code:not(.hljs) {
|
||||
padding: 0 0.25rem;
|
||||
background: $light;
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
.hero-subtitle {
|
||||
@extend .lead;
|
||||
font-weight: 400;
|
||||
color: $gray-600;
|
||||
max-width: 42rem;
|
||||
font-size: 1rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-description {
|
||||
max-width: 56rem;
|
||||
margin-top: 0.5rem;
|
||||
font-size: $font-size-base;
|
||||
color: $gray-900;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
font-size: $font-size-lg;
|
||||
|
|
@ -88,16 +94,14 @@
|
|||
}
|
||||
|
||||
.card {
|
||||
.card-title {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: $gray-900;
|
||||
}
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
border-color: $gray-600;
|
||||
border-color: $gray-500;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&.card-sm {
|
||||
|
|
@ -156,12 +160,20 @@
|
|||
}
|
||||
|
||||
.nav-tabs {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
// 1 pixel bottom padding so that the 2px active border is visible
|
||||
padding-bottom: 1px;
|
||||
|
||||
.nav-link {
|
||||
color: $gray-700;
|
||||
color: $gray-800;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
padding: 1rem 0.5rem;
|
||||
margin-right: 2rem;
|
||||
white-space: nowrap;
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
|
|
@ -171,7 +183,7 @@
|
|||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: darken($primary, 5%);
|
||||
background-color: #fff;
|
||||
background-color: transparent;
|
||||
border-bottom: 2px solid $primary;
|
||||
}
|
||||
}
|
||||
|
|
@ -183,7 +195,7 @@
|
|||
.section-cta {
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
background-color: lighten($primary, 42%);
|
||||
background-color: $primary-light;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
|
|
@ -210,7 +222,6 @@
|
|||
margin: 0 auto;
|
||||
margin-top: 0.5rem;
|
||||
font-size: $font-size-base;
|
||||
color: $gray-900;
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
|
@ -220,7 +231,50 @@
|
|||
margin: 0 auto;
|
||||
margin-top: 0.5rem;
|
||||
font-size: $font-size-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.section-small-cta {
|
||||
padding: 1.8rem;
|
||||
background-color: lighten($primary, 42%);
|
||||
border-radius: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
div {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
max-width: 36rem;
|
||||
font-size: $font-size-xl;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: $font-size-2xl;
|
||||
}
|
||||
}
|
||||
.subtitle {
|
||||
max-width: 36rem;
|
||||
font-size: $font-size-base;
|
||||
color: $gray-900;
|
||||
margin-bottom: 1.2rem;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
font-size: $font-size-lg;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,19 +320,77 @@
|
|||
margin-right: auto;
|
||||
margin-top: 2rem;
|
||||
max-width: 52rem;
|
||||
font-size: $font-size-2xl;
|
||||
font-size: $font-size-lg;
|
||||
font-weight: 500;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
font-size: $font-size-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
.testimonial-by {
|
||||
font-size: $font-size-lg;
|
||||
font-size: $font-size-base;
|
||||
margin-top: 2rem;
|
||||
|
||||
&:before {
|
||||
content: '—'
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
}
|
||||
|
||||
.split-section-content {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.section-image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
// Offset for padding
|
||||
margin-right: -2px;
|
||||
margin-left: -2px;
|
||||
|
||||
.image-container {
|
||||
overflow: hidden;
|
||||
border: 2px solid #fff;
|
||||
border-radius: $border-radius;
|
||||
|
||||
width: 100%;
|
||||
max-height: 8rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
&.wide {
|
||||
max-width: 75%;
|
||||
width: 75%;
|
||||
max-height: 15rem;
|
||||
height: 15rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&.narrow {
|
||||
max-width: 25%;
|
||||
width: 25%;
|
||||
max-height: 15rem;
|
||||
height: 15rem;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,41 @@
|
|||
|
||||
.sidebar-item a {
|
||||
display: block;
|
||||
padding: 0.25rem 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: $font-size-sm;
|
||||
color: $gray-700;
|
||||
color: $gray-600;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
@include transition();
|
||||
|
||||
&:hover {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-item a.active {
|
||||
color: $primary;
|
||||
background-color: $primary-light;
|
||||
}
|
||||
|
||||
.sidebar-item-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sidebar-group {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
h6 {
|
||||
font-size: $font-size-sm;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
> ul {
|
||||
padding-left: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
$gray-100: #fafbfc !default;
|
||||
$gray-150: #f5f7fa !default;
|
||||
$gray-200: #ebecf1 !default;
|
||||
$gray-300: #d1d8dd !default;
|
||||
$gray-400: #ced4da !default;
|
||||
$gray-500: #adb5bd !default;
|
||||
$gray-600: #8d99a6 !default;
|
||||
$gray-700: #495057 !default;
|
||||
$gray-800: #36414c !default;
|
||||
$gray-900: #2e3338 !default;
|
||||
$primary: #2490ef !default;
|
||||
$gray-50: #F9FAFA !default;
|
||||
$gray-100: #F4F5F6 !default;
|
||||
$gray-200: #EEF0F2 !default;
|
||||
$gray-300: #E2E6E9 !default;
|
||||
$gray-400: #C8CFD5 !default;
|
||||
$gray-500: #A6B1B9 !default;
|
||||
$gray-600: #74808B !default;
|
||||
$gray-700: #4C5A67 !default;
|
||||
$gray-800: #313B44 !default;
|
||||
$gray-900: #192734 !default;
|
||||
|
||||
$black: #000 !default;
|
||||
$primary: #2490ef !default;
|
||||
$primary-light: lighten($primary, 42%) !default;
|
||||
$light: $gray-50 !default;
|
||||
|
||||
$body-color: $gray-800 !default;
|
||||
$body-color: $gray-700 !default;
|
||||
$text-muted: $gray-600 !default;
|
||||
$border-color: $gray-300 !default;
|
||||
$headings-color: $gray-900 !default;
|
||||
|
||||
$font-size-xs: 0.75rem !default;
|
||||
$font-size-sm: 0.875rem !default;
|
||||
|
|
@ -33,20 +36,32 @@ $btn-font-size-lg: 1.125rem !default;
|
|||
$btn-line-height-lg: 1 !default;
|
||||
$btn-border-radius-lg: 0.5rem !default;
|
||||
$btn-border-radius: 0.375rem !default;
|
||||
$btn-font-size: $font-size-sm;
|
||||
$btn-font-size: $font-size-sm !default;
|
||||
$btn-padding-x: 1rem !default;
|
||||
$btn-padding-y: 0.5rem !default;
|
||||
$btn-font-weight: 500 !default;
|
||||
|
||||
$navbar-nav-link-padding-x: 1rem !default;
|
||||
$navbar-padding-y: 1rem;
|
||||
$navbar-padding-y: 1rem !default;
|
||||
$card-border-radius: 0.75rem !default;
|
||||
$card-spacer-y: 1rem !default;
|
||||
$card-spacer-y: 0.5rem !default;
|
||||
|
||||
$dropdown-font-size: $font-size-sm !default;
|
||||
$dropdown-border-radius: 0.375rem !default;
|
||||
$dropdown-item-padding-y: 0.5rem !default;
|
||||
$dropdown-item-padding-x: 0.5rem !default;
|
||||
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
sm: 576px,
|
||||
md: 768px,
|
||||
lg: 992px,
|
||||
xl: 1200px,
|
||||
2xl: 1440px
|
||||
) !default;
|
||||
|
||||
@import '~bootstrap/scss/functions';
|
||||
@import '~bootstrap/scss/variables';
|
||||
@import "~bootstrap/scss/mixins";
|
||||
|
||||
$code-color: $purple;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@ img:after {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.website-image-extra-small {
|
||||
@include website-image;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.website-image-small {
|
||||
@include website-image;
|
||||
width: 5rem;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
@import 'multilevel-dropdown';
|
||||
@import 'website-image';
|
||||
@import 'page-builder';
|
||||
@import 'blog';
|
||||
@import 'markdown';
|
||||
@import 'sidebar';
|
||||
@import 'doc';
|
||||
|
||||
.container {
|
||||
padding-left: 1.25rem;
|
||||
|
|
@ -15,26 +17,26 @@
|
|||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.container {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.container {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.container {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
.container {
|
||||
padding-left: 5rem;
|
||||
padding-right: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(2xl) {
|
||||
.container {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
|
|
@ -46,7 +48,7 @@
|
|||
}
|
||||
|
||||
.navbar-light .navbar-nav .nav-link {
|
||||
color: $gray-900;
|
||||
color: $gray-700;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: 500;
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ a.card {
|
|||
|
||||
.footer-link, .footer-child-item a {
|
||||
font-weight: 500;
|
||||
color: $gray-900;
|
||||
color: $gray-700;
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
|
|
@ -159,8 +161,9 @@ a.card {
|
|||
}
|
||||
|
||||
.footer-col-left, .footer-col-right {
|
||||
padding-top: 1rem;
|
||||
padding-top: 0.8rem;
|
||||
padding-bottom: 1rem;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.footer-col-right {
|
||||
|
|
@ -281,7 +284,6 @@ h5.modal-title {
|
|||
}
|
||||
|
||||
.btn-primary-light {
|
||||
$primary-light: lighten($primary, 42%);
|
||||
@include button-variant(
|
||||
$background: $primary-light,
|
||||
$border: $primary-light,
|
||||
|
|
|
|||
187
frappe/templates/doc.html
Normal file
187
frappe/templates/doc.html
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
{% extends "templates/base.html" %}
|
||||
{%- from "templates/includes/navbar/navbar_items.html" import render_item -%}
|
||||
|
||||
{% macro page_content() %}
|
||||
{%- block page_content -%}{%- endblock -%}
|
||||
{% endmacro %}
|
||||
|
||||
{%- block head_include %}
|
||||
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
|
||||
{% endblock -%}
|
||||
|
||||
{%- block navbar -%}
|
||||
<nav class="navbar navbar-light navbar-expand-lg doc-navbar fixed-top">
|
||||
<div class="container-fluid doc-container">
|
||||
<div class="row no-gutters w-100">
|
||||
<div class="col-12 col-lg-2">
|
||||
<a class="navbar-brand" href="{{ url_prefix }}{{ home_page or "/" }}">
|
||||
{%- if brand_html -%}
|
||||
{{ brand_html }}
|
||||
{%- elif banner_image -%}
|
||||
<img src='{{ banner_image }}'>
|
||||
{%- else -%}
|
||||
<span>{{ (frappe.get_hooks("brand_html") or [_("Home")])[0] }}</span>
|
||||
{%- endif -%}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="doc-search-container">
|
||||
<div class="doc-search">
|
||||
<div class="dropdown">
|
||||
<div class="search-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-search">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<input type="search" class="form-control" placeholder="Search the docs (Press ? to focus)" />
|
||||
<div class="overflow-hidden shadow dropdown-menu w-100">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button"
|
||||
data-toggle="collapse"
|
||||
data-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-2">
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav">
|
||||
{%- set items = docs_navbar_items or [] -%}
|
||||
{%- for item in items -%}
|
||||
{{ render_item(item, parent=True) }}
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
{% include "templates/includes/web_sidebar.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{%- endblock -%}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% macro main_content() %}
|
||||
<div class="page-content-wrapper">
|
||||
{% block page_container %}
|
||||
<main>
|
||||
<div class="page_content page-content doc-content">
|
||||
{{ page_content() }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro container_attributes() -%}
|
||||
id="page-{{ name or route | e }}" data-path="{{ pathname | e }}"
|
||||
{%- if page_or_generator=="Generator" %}source-type="Generator" data-doctype="{{ doctype }}"{%- endif %}
|
||||
{%- if source_content_type %}source-content-type="{{ source_content_type }}"{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
<div class="container-fluid doc-layout doc-container">
|
||||
<div class="row no-gutters" {{ container_attributes() }}>
|
||||
<div class="sidebar-column col-sm-2">
|
||||
<aside class="doc-sidebar">
|
||||
{% block page_sidebar %}
|
||||
{% include "templates/includes/web_sidebar.html" %}
|
||||
{% endblock %}
|
||||
</aside>
|
||||
</div>
|
||||
<div class="main-column doc-main col-12 col-lg-10 col-xl-8">
|
||||
{{ main_content() }}
|
||||
</div>
|
||||
<div class="page-toc col-sm-2 d-none d-xl-block">
|
||||
<div>
|
||||
<h5>On this page</h5>
|
||||
{{ page_toc_html }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{%- block script -%}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
setup_search();
|
||||
|
||||
$('.web-footer .container')
|
||||
.removeClass('container')
|
||||
.addClass('container-fluid doc-container');
|
||||
});
|
||||
|
||||
function setup_search() {
|
||||
let $dropdown = $('.doc-search .dropdown');
|
||||
let $dropdown_menu = $('.doc-search .dropdown-menu');
|
||||
let $input = $('.doc-search input');
|
||||
|
||||
$(document).on('keypress', e => {
|
||||
if (e.key === '/') {
|
||||
e.preventDefault();
|
||||
$input.focus();
|
||||
}
|
||||
});
|
||||
|
||||
$input.on('input', frappe.utils.debounce(() => {
|
||||
if (!$input.val()) {
|
||||
clear_dropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.modules.full_text_search.web_search',
|
||||
args: {
|
||||
index_name: 'web_routes',
|
||||
scope: '{{ docs_search_scope or "" }}' || null,
|
||||
query: $input.val(),
|
||||
limit: 5
|
||||
}
|
||||
}).then(r => {
|
||||
let results = r.message || [];
|
||||
let dropdown_html;
|
||||
if (results.length == 0) {
|
||||
dropdown_html = `<div class="dropdown-item">No results found</div>`;
|
||||
} else {
|
||||
dropdown_html = results.map(r => {
|
||||
return `<a class="dropdown-item" href="/${r.path}">
|
||||
<h6>${r.title_highlights || r.title}</h6>
|
||||
<div style="white-space: normal;">${r.content_highlights}</div>
|
||||
</a>`
|
||||
}).join('')
|
||||
}
|
||||
$dropdown_menu.html(dropdown_html);
|
||||
$dropdown_menu.addClass('show');
|
||||
});
|
||||
}, 500));
|
||||
|
||||
$input.on('focus', () => {
|
||||
if (!$input.val()) {
|
||||
clear_dropdown();
|
||||
}
|
||||
});
|
||||
|
||||
$input.on('blur', () => {
|
||||
setTimeout(() => {
|
||||
clear_dropdown();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
function clear_dropdown() {
|
||||
$dropdown_menu.html('');
|
||||
$dropdown_menu.removeClass('show');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{%- endblock -%}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %}{{ blog_title or _("Blog") }}{% endblock %}
|
||||
{% block header %}<h1>{{ blog_title or _("Blog") }}</h1>{% endblock %}
|
||||
{% block hero %}{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<!-- no-header -->
|
||||
<!-- no-breadcrumbs -->
|
||||
<div class="blog-list-content">
|
||||
<div id="blog-list">
|
||||
{% include "templates/includes/list/list.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>{% include "templates/includes/list/list.js" %}</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %}
|
||||
|
||||
<div class="media">
|
||||
{{ square_image_with_fallback(src=blogger_info.avatar, size='72px', alt=blogger_info.full_name, class='align-self-start mr-3 rounded') }}
|
||||
{{ square_image_with_fallback(src=blogger_info.avatar, size='small', alt=blogger_info.full_name, class='align-self-start mr-3 rounded') }}
|
||||
<div class="media-body">
|
||||
<h5 class="mt-0">
|
||||
<a href="/blog?blogger={{ blogger_info.name }}" class="text-dark">{{ blogger_info.full_name }}</a>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% if not no_breadcrumbs and parents %}
|
||||
{%- if not no_breadcrumbs and parents -%}
|
||||
<div class="container mt-3">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">
|
||||
|
|
@ -17,4 +17,4 @@
|
|||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %}
|
||||
|
||||
<div class="comment-row media">
|
||||
{{ square_image_with_fallback(src=frappe.get_gravatar(comment.comment_email or comment.sender), size='48px', alt=comment.sender_full_name, class='align-self-start mr-3') }}
|
||||
{{ square_image_with_fallback(src=frappe.get_gravatar(comment.comment_email or comment.sender), size='extra-small', alt=comment.sender_full_name, class='align-self-start mr-3') }}
|
||||
<div class="media-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<span class="font-weight-bold text-muted">
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
{% macro square_image_with_fallback(src=None, size=None, alt=None, class="") %}
|
||||
{% macro square_image_with_fallback(src=None, size='small', alt=None, class="") %}
|
||||
{% if src %}
|
||||
<img
|
||||
{% if size %}
|
||||
width="{{size}}"
|
||||
height="{{size}}"
|
||||
{% endif %}
|
||||
|
||||
{% if src %}
|
||||
src="{{ src }}"
|
||||
{% endif %}
|
||||
|
||||
class="{{ class }} "
|
||||
alt="{{ alt or '' }}"
|
||||
>
|
||||
<img class="rounded-lg website-image-{{ size }} mr-2" src="{{ src }}">
|
||||
{% else %}
|
||||
<div class="no-image bg-light {{ class }} " {% if size %}style="width: {{size}}; height: {{size}};"{% endif %}></div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,82 @@
|
|||
{% macro render_sidebar_item(item) %}
|
||||
<li class="{{ 'sidebar-group' if item.group_title else 'sidebar-item' }}">
|
||||
{%- if item.group_title -%}
|
||||
|
||||
<h6>{{ item.group_title }}</h6>
|
||||
{{ render_sidebar_items(item.group_items) }}
|
||||
|
||||
{%- else -%}
|
||||
|
||||
{% if item.type != 'input' %}
|
||||
{%- set item_route = item.route[1:] if item.route[0] == '/' else item.route -%}
|
||||
<a href="{{ item.route }}" class="{{ 'active' if pathname == item_route else '' }}"
|
||||
{% if item.target %}target="{{ item.target }}" {% endif %}>
|
||||
{{ _(item.title or item.label) }}
|
||||
</a>
|
||||
{% else %}
|
||||
<form action='{{ item.route }}' class="mr-3">
|
||||
<input name='q' class='form-control' type='text' style="outline: none"
|
||||
placeholder="{{ _(item.title or item.label) }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_sidebar_items(items) %}
|
||||
{%- if items | len > 0 -%}
|
||||
<ul class="list-unstyled">
|
||||
{% for item in items -%}
|
||||
{{ render_sidebar_item(item) }}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro my_account() %}
|
||||
{% if frappe.user != 'Guest' %}
|
||||
<ul class="list-unstyled">
|
||||
<li class="sidebar-item">
|
||||
<a href="/me">{{ _("My Account") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="web-sidebar">
|
||||
{% if sidebar_title %}
|
||||
<li class="title">
|
||||
{{ sidebar_title }}
|
||||
</li>
|
||||
{% endif %}
|
||||
<div class="sidebar-items">
|
||||
<ul class="list-unstyled">
|
||||
{% if sidebar_title %}
|
||||
<li class="title">
|
||||
{{ sidebar_title }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for item in sidebar_items -%}
|
||||
<li class="sidebar-item">
|
||||
{% if item.type != 'input' %}
|
||||
{%- set item_route = item.route[1:] if item.route[0] == '/' else item.route -%}
|
||||
<a href="{{ item.route }}" class="{{ 'active' if pathname == item_route else '' }}"
|
||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||
{{ _(item.title or item.label) }}
|
||||
</a>
|
||||
{% else %}
|
||||
<form action='{{ item.route }}' class="mr-3">
|
||||
<input name='q' class='form-control' type='text' style="outline: none"
|
||||
placeholder="{{ _(item.title or item.label) }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
{%- endfor %}
|
||||
{% if frappe.user != 'Guest' %}
|
||||
<li class="sidebar-item">
|
||||
<a href="/me">{{ _("My Account") }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{{ render_sidebar_items(sidebar_items) }}
|
||||
{{ my_account() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
frappe.ready(function() {
|
||||
$('.sidebar-item a').each(function(index) {
|
||||
const active_class = 'active'
|
||||
const non_active_class = ''
|
||||
if(this.href.trim() == window.location) {
|
||||
$(this).removeClass(non_active_class).addClass(active_class);
|
||||
} else {
|
||||
$(this).removeClass(active_class).addClass(non_active_class);
|
||||
}
|
||||
});
|
||||
});
|
||||
frappe.ready(function () {
|
||||
$('.sidebar-item a').each(function (index) {
|
||||
const active_class = 'active'
|
||||
const non_active_class = ''
|
||||
let page_href = window.location.href;
|
||||
if (page_href.indexOf('#') !== -1) {
|
||||
page_href = page_href.slice(0, page_href.indexOf('#'));
|
||||
}
|
||||
if (this.href.trim() == page_href) {
|
||||
$(this).removeClass(non_active_class).addClass(active_class);
|
||||
} else {
|
||||
$(this).removeClass(active_class).addClass(non_active_class);
|
||||
}
|
||||
});
|
||||
|
||||
// scroll the active sidebar item into view
|
||||
let active_sidebar_item = $('.sidebar-item a.active');
|
||||
if (active_sidebar_item.length > 0) {
|
||||
active_sidebar_item.get(0)
|
||||
.scrollIntoView({behavior: "auto", block: "center", inline: "nearest"});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ def format_datetime(datetime_string, format_string=None):
|
|||
formatted_datetime = datetime.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return formatted_datetime
|
||||
|
||||
def format_duration(seconds, show_days=True):
|
||||
def format_duration(seconds, hide_days=False):
|
||||
total_duration = {
|
||||
'days': math.floor(seconds / (3600 * 24)),
|
||||
'hours': math.floor(seconds % (3600 * 24) / 3600),
|
||||
|
|
@ -349,7 +349,7 @@ def format_duration(seconds, show_days=True):
|
|||
'seconds': math.floor(seconds % 60)
|
||||
}
|
||||
|
||||
if not show_days:
|
||||
if hide_days:
|
||||
total_duration['hours'] = math.floor(seconds / 3600)
|
||||
total_duration['days'] = 0
|
||||
|
||||
|
|
@ -776,6 +776,8 @@ def image_to_base64(image, extn):
|
|||
from io import BytesIO
|
||||
|
||||
buffered = BytesIO()
|
||||
if extn.lower() == 'jpg':
|
||||
extn = 'JPEG'
|
||||
image.save(buffered, extn)
|
||||
img_str = base64.b64encode(buffered.getvalue())
|
||||
return img_str
|
||||
|
|
@ -1204,6 +1206,7 @@ def md_to_html(markdown_text):
|
|||
'fenced-code-blocks': None,
|
||||
'tables': None,
|
||||
'header-ids': None,
|
||||
'toc': None,
|
||||
'highlightjs-lang': None,
|
||||
'html-classes': {
|
||||
'table': 'table table-bordered',
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ def format_value(value, df=None, doc=None, currency=None, translated=False):
|
|||
return ', '.join(values)
|
||||
|
||||
elif df.get("fieldtype") == "Duration":
|
||||
show_days = df.show_days
|
||||
return format_duration(value, show_days)
|
||||
hide_days = df.hide_days
|
||||
return format_duration(value, hide_days)
|
||||
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -274,6 +274,10 @@ def update_global_search(doc):
|
|||
sync_value_in_queue(value)
|
||||
|
||||
def update_global_search_for_all_web_pages():
|
||||
if frappe.conf.get('disable_global_search'):
|
||||
return
|
||||
|
||||
print('Update global search for all web pages...')
|
||||
routes_to_index = get_routes_to_index()
|
||||
for route in routes_to_index:
|
||||
add_route_to_global_search(route)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ def build_context(context):
|
|||
# determine templates to be used
|
||||
if not context.base_template_path:
|
||||
app_base = frappe.get_hooks("base_template")
|
||||
context.base_template_path = app_base[0] if app_base else "templates/base.html"
|
||||
context.base_template_path = app_base[-1] if app_base else "templates/base.html"
|
||||
|
||||
if context.title_prefix and context.title and not context.title.startswith(context.title_prefix):
|
||||
context.title = '{0} - {1}'.format(context.title_prefix, context.title)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
frappe.ui.form.on('Blog Post', {
|
||||
refresh: function(frm) {
|
||||
frappe.db.get_single_value('Blog Settings', 'show_cta_in_blog').then(value => {
|
||||
frm.set_df_property("hide_cta", "hidden", !value);
|
||||
});
|
||||
|
||||
generate_google_search_preview(frm);
|
||||
},
|
||||
title: function(frm) {
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"published_on",
|
||||
"published",
|
||||
"read_time",
|
||||
"disable_comments",
|
||||
"column_break_3",
|
||||
"blog_category",
|
||||
"blogger",
|
||||
"route",
|
||||
"read_time",
|
||||
"column_break_3",
|
||||
"published_on",
|
||||
"published",
|
||||
"featured",
|
||||
"hide_cta",
|
||||
"disable_comments",
|
||||
"section_break_5",
|
||||
"blog_intro",
|
||||
"content_type",
|
||||
|
|
@ -83,7 +85,7 @@
|
|||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Description for listing page, in plain text, only a couple of lines. (max 140 characters)",
|
||||
"description": "Description for listing page, in plain text, only a couple of lines. (max 200 characters)",
|
||||
"fieldname": "blog_intro",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Blog Intro"
|
||||
|
|
@ -143,7 +145,8 @@
|
|||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Meta Image"
|
||||
"label": "Meta Image",
|
||||
"mandatory_depends_on": "eval:doc.featured"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_20",
|
||||
|
|
@ -165,8 +168,22 @@
|
|||
"description": "in minutes",
|
||||
"fieldname": "read_time",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "Read Time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "featured",
|
||||
"fieldtype": "Check",
|
||||
"label": "Featured"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_cta",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Hide CTA"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
|
|
@ -175,7 +192,7 @@
|
|||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-04-30 17:32:41.055883",
|
||||
"modified": "2020-06-01 13:37:57.465434",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blog Post",
|
||||
|
|
|
|||
|
|
@ -30,22 +30,32 @@ class BlogPost(WebsiteGenerator):
|
|||
|
||||
if not self.blog_intro:
|
||||
content = get_html_content_based_on_type(self, 'content', self.content_type)
|
||||
self.blog_intro = content[:140]
|
||||
self.blog_intro = content[:200]
|
||||
self.blog_intro = strip_html_tags(self.blog_intro)
|
||||
|
||||
if self.blog_intro:
|
||||
self.blog_intro = self.blog_intro[:140]
|
||||
self.blog_intro = self.blog_intro[:200]
|
||||
|
||||
if not self.meta_description:
|
||||
self.meta_description = self.blog_intro[:140]
|
||||
else:
|
||||
self.meta_description = self.meta_description[:140]
|
||||
|
||||
if self.published and not self.published_on:
|
||||
self.published_on = today()
|
||||
|
||||
# update posts
|
||||
frappe.db.sql("""UPDATE `tabBlogger` SET `posts`=(SELECT COUNT(*) FROM `tabBlog Post`
|
||||
WHERE IFNULL(`blogger`,'')=`tabBlogger`.`name`)
|
||||
WHERE `name`=%s""", (self.blogger,))
|
||||
if self.featured:
|
||||
if not self.meta_image:
|
||||
frappe.throw(_("A featured post must have a cover image"))
|
||||
self.reset_featured_for_other_blogs()
|
||||
|
||||
self.set_read_time()
|
||||
|
||||
def reset_featured_for_other_blogs(self):
|
||||
all_posts = frappe.get_all("Blog Post", {"featured": 1})
|
||||
for post in all_posts:
|
||||
frappe.db.set_value("Blog Post", post.name, "featured", 0)
|
||||
|
||||
def on_update(self):
|
||||
super(BlogPost, self).on_update()
|
||||
clear_cache("writers")
|
||||
|
|
@ -58,10 +68,14 @@ class BlogPost(WebsiteGenerator):
|
|||
if not cint(self.published):
|
||||
raise Exception("This blog has not been published yet!")
|
||||
|
||||
context.no_breadcrumbs = True
|
||||
|
||||
# temp fields
|
||||
context.full_name = get_fullname(self.owner)
|
||||
context.updated = global_date_format(self.published_on)
|
||||
context.social_links = self.fetch_social_links_info()
|
||||
context.cta = self.fetch_cta()
|
||||
context.enable_cta = not self.hide_cta and frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True)
|
||||
|
||||
if self.blogger:
|
||||
context.blogger_info = frappe.get_doc("Blogger", self.blogger).as_dict()
|
||||
|
|
@ -90,27 +104,34 @@ class BlogPost(WebsiteGenerator):
|
|||
{"name": "Blog", "route": "/blog"},
|
||||
{"label": context.category.title, "route":context.category.route}]
|
||||
|
||||
def fetch_cta(self):
|
||||
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True):
|
||||
blog_settings = frappe.get_cached_doc("Blog Settings")
|
||||
|
||||
return {
|
||||
"show_cta_in_blog": 1,
|
||||
"title": blog_settings.title,
|
||||
"subtitle": blog_settings.subtitle,
|
||||
"cta_label": blog_settings.cta_label,
|
||||
"cta_url": blog_settings.cta_url
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
def fetch_social_links_info(self):
|
||||
if not frappe.db.get_single_value("Blog Settings", "enable_social_sharing", cache=True):
|
||||
return []
|
||||
|
||||
url = frappe.local.site + "/" +self.route
|
||||
social_url_map = {
|
||||
"twitter": "https://twitter.com/intent/tweet?text=" +self.title + "&url=" + url,
|
||||
"facebook": "https://www.facebook.com/sharer.php?u=" + url,
|
||||
"linkedin": "https://www.linkedin.com/sharing/share-offsite/?url=" + url,
|
||||
"email": "mailto:?subject=" + self.title + "&body=" + url,
|
||||
}
|
||||
|
||||
social_link = []
|
||||
for link in frappe.get_cached_doc("Blog Settings").social_share_settings:
|
||||
social_media = link.social_link_type
|
||||
social_links = [
|
||||
{ "icon": "twitter", "link": "https://twitter.com/intent/tweet?text=" + self.title + "&url=" + url },
|
||||
{ "icon": "facebook", "link": "https://www.facebook.com/sharer.php?u=" + url },
|
||||
{ "icon": "linkedin", "link": "https://www.linkedin.com/sharing/share-offsite/?url=" + url },
|
||||
{ "icon": "envelope", "link": "mailto:?subject=" + self.title + "&body=" + url }
|
||||
]
|
||||
|
||||
social_link.append({
|
||||
'icon': social_media if not social_media == 'email' else 'envelope',
|
||||
'url': social_url_map.get(social_media),
|
||||
'color': link.color,
|
||||
'background': link.background_color
|
||||
})
|
||||
return social_link
|
||||
return social_links
|
||||
|
||||
def load_comments(self, context):
|
||||
context.comment_list = get_comment_list(self.doctype, self.name)
|
||||
|
|
@ -133,8 +154,8 @@ class BlogPost(WebsiteGenerator):
|
|||
|
||||
def get_list_context(context=None):
|
||||
list_context = frappe._dict(
|
||||
template = "templates/includes/blog/blog.html",
|
||||
get_list = get_blog_list,
|
||||
no_breadcrumbs = True,
|
||||
hide_filters = True,
|
||||
children = get_children(),
|
||||
# show_search = True,
|
||||
|
|
@ -161,7 +182,8 @@ def get_list_context(context=None):
|
|||
else:
|
||||
list_context.parents = [{"name": _("Home"), "route": "/"}]
|
||||
|
||||
list_context.update(frappe.get_doc("Blog Settings", "Blog Settings").as_dict(no_default_fields=True))
|
||||
list_context.update(frappe.get_doc("Blog Settings").as_dict(no_default_fields=True))
|
||||
|
||||
return list_context
|
||||
|
||||
def get_children():
|
||||
|
|
@ -201,6 +223,9 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len
|
|||
select
|
||||
t1.title, t1.name, t1.blog_category, t1.route, t1.published_on, t1.read_time,
|
||||
t1.published_on as creation,
|
||||
t1.read_time as read_time,
|
||||
t1.featured as featured,
|
||||
t1.meta_image as cover_image,
|
||||
t1.content as content,
|
||||
t1.content_type as content_type,
|
||||
t1.content_html as content_html,
|
||||
|
|
@ -216,7 +241,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len
|
|||
where ifnull(t1.published,0)=1
|
||||
and t1.blogger = t2.name
|
||||
%(condition)s
|
||||
order by published_on desc, name asc
|
||||
order by featured desc, published_on desc, name asc
|
||||
limit %(start)s, %(page_len)s""" % {
|
||||
"start": limit_start, "page_len": limit_page_length,
|
||||
"condition": (" and " + " and ".join(conditions)) if conditions else ""
|
||||
|
|
@ -225,9 +250,9 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len
|
|||
posts = frappe.db.sql(query, as_dict=1)
|
||||
|
||||
for post in posts:
|
||||
|
||||
post.content = get_html_content_based_on_type(post, 'content', post.content_type)
|
||||
post.cover_image = find_first_image(post.content)
|
||||
if not post.cover_image:
|
||||
post.cover_image = find_first_image(post.content)
|
||||
post.published = global_date_format(post.creation)
|
||||
post.content = strip_html_tags(post.content)
|
||||
|
||||
|
|
@ -240,7 +265,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len
|
|||
|
||||
post.avatar = post.avatar or ""
|
||||
post.category = frappe.db.get_value('Blog Category', post.blog_category,
|
||||
['route', 'title'], as_dict=True)
|
||||
['name', 'route', 'title'], as_dict=True)
|
||||
|
||||
if post.avatar and (not "http:" in post.avatar and not "https:" in post.avatar) and not post.avatar.startswith("/"):
|
||||
post.avatar = "/" + post.avatar
|
||||
|
|
|
|||
|
|
@ -6,38 +6,57 @@
|
|||
|
||||
{% block page_content %}
|
||||
<div class="blog-container">
|
||||
<article class="blog-content mb-3" itemscope itemtype="http://schema.org/BlogPosting">
|
||||
<article class="blog-content" itemscope itemtype="http://schema.org/BlogPosting">
|
||||
<!-- begin blog content -->
|
||||
<div class="blog-info">
|
||||
<span class="text-center">
|
||||
<h1 itemprop="headline" class="blog-header">{{ title }}</h1>
|
||||
<p class="lead">
|
||||
{{ blog_intro }}
|
||||
</p>
|
||||
</span>
|
||||
|
||||
<div class="text-muted small meta-info">
|
||||
{{ frappe.format_date(published_on) }}
|
||||
{% if read_time %}
|
||||
·
|
||||
{{ read_time }} min read
|
||||
{% endif %}
|
||||
{% if social_links %}
|
||||
<div class="social-links">
|
||||
{% for link in social_links %}
|
||||
<a href="{{ link.url }}" class="fa fa-{{ link.icon }}"
|
||||
style='color:{{ link.color }}; background-color: {{ link.background }};'
|
||||
target="_blank"></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="blog-header">
|
||||
<div>
|
||||
<a class="mr-2" href="/blog">{{ _('Blog') }}</a>
|
||||
<span class="text-muted">/</span>
|
||||
<a class="ml-2" href="/blog/{{ category.title }}">{{ category.title }}</a>
|
||||
</div>
|
||||
<h1 itemprop="headline" class="blog-title">{{ title }}</h1>
|
||||
<p class="blog-intro">
|
||||
{{ blog_intro }}
|
||||
</p>
|
||||
<div class="text-muted">
|
||||
<time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
|
||||
{%- if read_time -%}
|
||||
·
|
||||
<span>{{ read_time }} min read</span>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
<div itemprop="articleBody" class="longform blog-text mt-5">
|
||||
{{ content }}
|
||||
<hr class="my-5">
|
||||
<div itemprop="articleBody" class="from-markdown">
|
||||
{{ content }}
|
||||
</div>
|
||||
<!-- end blog content -->
|
||||
</article>
|
||||
{%- if enable_cta -%}
|
||||
{{ web_blocks([
|
||||
{
|
||||
'template': "Section With Small CTA",
|
||||
'values': cta,
|
||||
'add_container': 0,
|
||||
'add_top_padding': 0,
|
||||
'add_bottom_padding': 0,
|
||||
'css_class': "my-5"
|
||||
}
|
||||
])
|
||||
}}
|
||||
{%- endif -%}
|
||||
<div class="blog-footer">
|
||||
<div>
|
||||
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
|
||||
</div>
|
||||
<div>
|
||||
{% if social_links %}
|
||||
{% for link in social_links %}
|
||||
<a href="{{ link.link }}" class="text-muted ml-2 fa fa-{{ link.icon }}" target="_blank"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if blogger_info %}
|
||||
<hr class="my-5">
|
||||
|
|
@ -45,7 +64,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if not disable_comments %}
|
||||
<div class="blog-comments my-5">
|
||||
<div class="my-5 blog-comments">
|
||||
{% include 'templates/includes/comments/comments.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -55,30 +74,3 @@
|
|||
frappe.ready(() => frappe.set_search_path("/blog"))
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
.blog-container {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.meta-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.social-links {
|
||||
margin-right: 0px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.social-links a {
|
||||
font-size: 1.25rem;
|
||||
margin: 0 5px 0 0;
|
||||
padding: 5px 0;
|
||||
width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "templates/web.html" %}
|
||||
{% block title %}{{ blog_title or _("Blog") }}{% endblock %}
|
||||
{% block hero %}{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
{{ web_blocks([
|
||||
{
|
||||
'template': "Hero",
|
||||
'values': {
|
||||
'title': blog_title or _("Blog"),
|
||||
'subtitle': blog_introduction or '',
|
||||
},
|
||||
'add_container': 0,
|
||||
'add_top_padding': 0,
|
||||
'add_bottom_padding': 0,
|
||||
'css_class': "py-5"
|
||||
}
|
||||
])
|
||||
}}
|
||||
|
||||
<div class="blog-list-content">
|
||||
<div class="website-list" data-doctype="{{ doctype }}" data-txt="{{ txt or '[notxt]' | e }}">
|
||||
<div id="blog-list" class="blog-list result row">
|
||||
{% if not result -%}
|
||||
<div class="text-muted" style="min-height: 300px;">
|
||||
{{ no_result_message or _("Nothing to show") }}
|
||||
</div>
|
||||
{% else %}
|
||||
{% for item in result %}
|
||||
{{ item }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-light btn-more btn {% if not show_more -%} hidden {%- endif %}">{{ _("Load More") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>{% include "templates/includes/list/list.js" %}</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,38 +1,41 @@
|
|||
{%- set post = doc -%}
|
||||
<div class="web-list-item blog-list-item my-5">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="row">
|
||||
<div class="col-9 d-flex flex-column justify-content-between">
|
||||
<div>
|
||||
<div class="text-muted small text-uppercase">{{ post.category.title }}</div>
|
||||
<h4><a href="/{{ post.route }}" class="text-dark">{{ post.title }}</a></h4>
|
||||
<p class="post-description text-muted">{{ post.intro }}</p>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
<a class="text-muted" href="/blog?blogger={{ post.blogger }}">{{ post.full_name }}</a>
|
||||
·
|
||||
{{ frappe.format_date(post.published_on) }}
|
||||
{% if post.comments %}
|
||||
·
|
||||
{% if post.comments == 1 %}
|
||||
{{ _('1 comment') }}
|
||||
{% else %}
|
||||
{{ _('{0} comments').format(post.comments) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if post.read_time %}
|
||||
·
|
||||
{{ _('{0} min read').format(post.read_time) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="blog-card col-sm-12 {{ 'col-md-8' if post.featured else 'col-md-4' }}">
|
||||
<div class="card h-100">
|
||||
<div class="card-img-top">
|
||||
{% if post.cover_image %}
|
||||
<img src="{{ post.cover_image }}" alt="{{post.title}} - Cover Image">
|
||||
{% else %}
|
||||
<div class="default-cover">
|
||||
<span>{{ post.title }}</span>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
{% if post.cover_image %}
|
||||
<img class="website-image-medium object-fit-cover" src="{{ post.cover_image }}" alt="{{post.title}} - Cover Image">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<div class="text-muted small text-uppercase">
|
||||
{%- if post.featured -%}
|
||||
<span class="text-body">{{ _('Featured') }} · </span>
|
||||
{%- endif -%}
|
||||
<span>{{ post.category.title }}</span>
|
||||
</div>
|
||||
{%- if post.featured -%}
|
||||
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
|
||||
{%- else -%}
|
||||
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h3>
|
||||
{%- endif -%}
|
||||
<p class="post-description text-muted">{{ post.intro }}</p>
|
||||
</div>
|
||||
<div class="blog-card-footer">
|
||||
<img class="avatar website-image-extra-small" src="{{ post.avatar }}">
|
||||
<div class="text-muted">
|
||||
<a href="/blog?blogger={{ post.blogger }}">{{ post.full_name }}</a>
|
||||
<div class="small">
|
||||
{{ frappe.format_date(post.published_on) }}
|
||||
{% if post.read_time %} · {{ post.read_time }} min read {% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="stretched-link" href="/{{ post.route }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -19,7 +19,7 @@ class TestBlogPost(unittest.TestCase):
|
|||
self.assertTrue(response.status_code, 200)
|
||||
|
||||
html = response.get_data().decode()
|
||||
self.assertTrue('<article class="blog-content mb-3" itemscope itemtype="http://schema.org/BlogPosting">' in html)
|
||||
self.assertTrue('<article class="blog-content" itemscope itemtype="http://schema.org/BlogPosting">' in html)
|
||||
|
||||
def test_generator_not_found(self):
|
||||
pages = frappe.get_all('Blog Post', fields=['name', 'route'],
|
||||
|
|
|
|||
|
|
@ -7,9 +7,15 @@
|
|||
"field_order": [
|
||||
"blog_title",
|
||||
"blog_introduction",
|
||||
"writers_introduction",
|
||||
"section_break_4",
|
||||
"social_share_settings"
|
||||
"column_break",
|
||||
"enable_social_sharing",
|
||||
"show_cta_in_blog",
|
||||
"cta_section",
|
||||
"title",
|
||||
"subtitle",
|
||||
"column_break_11",
|
||||
"cta_label",
|
||||
"cta_url"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -23,27 +29,62 @@
|
|||
"label": "Blog Introduction"
|
||||
},
|
||||
{
|
||||
"fieldname": "writers_introduction",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Writers Introduction"
|
||||
"default": "0",
|
||||
"fieldname": "enable_social_sharing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Social Sharing"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldname": "column_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "social_share_settings",
|
||||
"fieldtype": "Table",
|
||||
"label": "Social Share Settings",
|
||||
"options": "Social Link Settings"
|
||||
"default": "0",
|
||||
"fieldname": "show_cta_in_blog",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show CTA in Blog"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.show_cta_in_blog",
|
||||
"fieldname": "cta_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "CTA"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
|
||||
},
|
||||
{
|
||||
"fieldname": "subtitle",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subtitle",
|
||||
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
|
||||
},
|
||||
{
|
||||
"fieldname": "cta_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "CTA Label",
|
||||
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
|
||||
},
|
||||
{
|
||||
"fieldname": "cta_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "CTA URL",
|
||||
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-04 09:10:41.815238",
|
||||
"modified": "2020-06-01 15:57:21.564652",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blog Settings",
|
||||
|
|
@ -57,6 +98,13 @@
|
|||
"role": "Website Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Blogger",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
|
|
|
|||
10
frappe/website/doctype/blog_settings/test_blog_settings.py
Normal file
10
frappe/website/doctype/blog_settings/test_blog_settings.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestBlogSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -13,8 +13,7 @@
|
|||
"full_name",
|
||||
"user",
|
||||
"bio",
|
||||
"avatar",
|
||||
"posts"
|
||||
"avatar"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -51,20 +50,13 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "avatar",
|
||||
"fieldtype": "Attach",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Avatar"
|
||||
},
|
||||
{
|
||||
"fieldname": "posts",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Posts",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"image_field": "avatar",
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Blog Post",
|
||||
|
|
@ -72,7 +64,7 @@
|
|||
}
|
||||
],
|
||||
"max_attachments": 1,
|
||||
"modified": "2020-04-19 08:21:09.684300",
|
||||
"modified": "2020-05-28 19:22:40.959895",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blogger",
|
||||
|
|
|
|||
|
|
@ -112,13 +112,6 @@ $.extend(frappe, {
|
|||
opts.args.cmd = opts.method;
|
||||
}
|
||||
|
||||
// stringify
|
||||
$.each(opts.args, function(key, val) {
|
||||
if(typeof val != "string") {
|
||||
opts.args[key] = JSON.stringify(val);
|
||||
}
|
||||
});
|
||||
|
||||
if(!opts.no_spinner) {
|
||||
//NProgress.start();
|
||||
}
|
||||
|
|
@ -329,6 +322,22 @@ $.extend(frappe, {
|
|||
add_switch_to_desk: function() {
|
||||
$('.switch-to-desk').removeClass('hidden');
|
||||
},
|
||||
add_link_to_headings: function() {
|
||||
$('.doc-content .from-markdown').find('h2, h3, h4, h5, h6').each((i, $heading) => {
|
||||
let id = $heading.id;
|
||||
let $a = $('<a class="no-underline">')
|
||||
.prop('href', '#' + id)
|
||||
.attr('aria-hidden', 'true')
|
||||
.html(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 0.8em; height: 0.8em;" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
||||
</svg>
|
||||
`);
|
||||
$($heading).append($a);
|
||||
});
|
||||
},
|
||||
setup_lazy_images: function() {
|
||||
// Use IntersectionObserver to only load images that are visible in the viewport
|
||||
// Fallback for browsers that don't support it
|
||||
|
|
@ -445,6 +454,7 @@ $(document).on("page-change", function() {
|
|||
frappe.trigger_ready();
|
||||
frappe.bind_filters();
|
||||
frappe.highlight_code_blocks();
|
||||
frappe.add_link_to_headings();
|
||||
frappe.make_navbar_active();
|
||||
// scroll to hash
|
||||
if (window.location.hash) {
|
||||
|
|
|
|||
|
|
@ -216,7 +216,6 @@ def build_page(path):
|
|||
|
||||
if context.source:
|
||||
html = frappe.render_template(context.source, context)
|
||||
|
||||
elif context.template:
|
||||
if path.endswith('min.js'):
|
||||
html = frappe.get_jloader().get_source(frappe.get_jenv(), context.template)[0]
|
||||
|
|
|
|||
|
|
@ -270,13 +270,18 @@ def setup_source(page_info):
|
|||
|
||||
if page_info.template.endswith('.md'):
|
||||
source = frappe.utils.md_to_html(source)
|
||||
page_info.page_toc_html = source.toc_html
|
||||
|
||||
if not page_info.show_sidebar:
|
||||
source = '<div class="from-markdown">' + source + '</div>'
|
||||
|
||||
# if only content
|
||||
if page_info.template.endswith('.html') or page_info.template.endswith('.md'):
|
||||
html = extend_from_base_template(page_info, source)
|
||||
if not page_info.base_template:
|
||||
page_info.base_template = get_base_template(page_info.route)
|
||||
|
||||
if page_info.template.endswith(('.html', '.md', )) and \
|
||||
'{%- extends' not in source and '{% extends' not in source:
|
||||
# set the source only if it contains raw content
|
||||
html = source
|
||||
|
||||
# load css/js files
|
||||
js, css = '', ''
|
||||
|
|
@ -300,22 +305,23 @@ def setup_source(page_info):
|
|||
# show table of contents
|
||||
setup_index(page_info)
|
||||
|
||||
def extend_from_base_template(page_info, source):
|
||||
'''Extend the content with appropriate base template if required.
|
||||
|
||||
For easy composition, the users will only add the content of the page,
|
||||
not its template. But if the user has explicitly put Jinja blocks, or <body> tags,
|
||||
or comment tags like <!-- base_template: [path] -->
|
||||
then the system will not try and put it inside the "web.template"
|
||||
def get_base_template(path=None):
|
||||
'''
|
||||
Returns the `base_template` for given `path`.
|
||||
The default `base_template` for any web route is `templates/web.html` defined in `hooks.py`.
|
||||
This can be overridden for certain routes in `custom_app/hooks.py` based on regex pattern.
|
||||
'''
|
||||
if not path:
|
||||
path = frappe.local.request.path
|
||||
|
||||
if (('</body>' not in source) and ('{% block' not in source)
|
||||
and ('<!-- base_template:' not in source)) and 'base_template' not in page_info:
|
||||
page_info.only_content = True
|
||||
source = '''{% extends "templates/web.html" %}
|
||||
{% block page_content %}\n''' + source + '\n{% endblock %}'
|
||||
|
||||
return source
|
||||
base_template_map = frappe.get_hooks("base_template_map") or {}
|
||||
patterns = list(base_template_map.keys())
|
||||
patterns_desc = sorted(patterns, key=lambda x: len(x), reverse=True)
|
||||
for pattern in patterns_desc:
|
||||
if re.match(pattern, path):
|
||||
templates = base_template_map[pattern]
|
||||
base_template = templates[-1]
|
||||
return base_template
|
||||
|
||||
def setup_index(page_info):
|
||||
'''Build page sequence from index.txt'''
|
||||
|
|
@ -335,7 +341,10 @@ def load_properties_from_source(page_info):
|
|||
if base_template:
|
||||
page_info.base_template = base_template
|
||||
|
||||
if page_info.base_template:
|
||||
if (page_info.base_template
|
||||
and "{%- extends" not in page_info.source
|
||||
and "{% extends" not in page_info.source
|
||||
and "</body>" not in page_info.source):
|
||||
page_info.source = '''{{% extends "{0}" %}}
|
||||
{{% block page_content %}}{1}{{% endblock %}}'''.format(page_info.base_template, page_info.source)
|
||||
page_info.no_cache = 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
<div class="section-with-image-grid">
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<p class="section-description">{{ subtitle }}</p>
|
||||
|
||||
<div class="section-image-grid">
|
||||
{%- for index in ['1', '2', '3', '4'] -%}
|
||||
{%- set image = values['image_' + index ] -%}
|
||||
{%- set class = "narrow" if index in ['1', '4'] else "wide" -%}
|
||||
{%- if image -%}
|
||||
<div class="image-container {{ class }}">
|
||||
{{ frappe.render_template('templates/includes/image_with_blur.html', {
|
||||
"src": image
|
||||
}) }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"creation": "2020-06-04 14:43:39.753713",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Template",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subtitle",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subtitle",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "image_1",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image 1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "image_2",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image 2",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "image_3",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image 3",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "image_4",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image 4",
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2020-06-04 16:57:43.097550",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Section with Image Grid",
|
||||
"owner": "Administrator",
|
||||
"standard": 1,
|
||||
"template": ""
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<div class="section-cta-container">
|
||||
<div class="section-small-cta">
|
||||
<div>
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<p class="subtitle">{{ subtitle }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ cta_url }}" class="btn btn-lg btn-primary">{{ cta_label }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"creation": "2020-06-01 15:56:38.002136",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Template",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "subtitle",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Subtitle",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "cta_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "CTA Label",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "cta_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "CTA URL",
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2020-06-01 17:51:23.073342",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Section with Small CTA",
|
||||
"owner": "Administrator",
|
||||
"standard": 1
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
{%- endif -%}
|
||||
|
||||
{%- endfor -%}
|
||||
<ul class="nav nav-tabs flex-nowrap overflow-auto" role="tablist" aria-label="{{ title or '' }}">
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="{{ title or '' }}">
|
||||
{%- for tab in ns.tabs -%}
|
||||
{%- set first_tab = true if loop.index0 == 0 else false -%}
|
||||
<li class="nav-item">
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
"custom_overrides": "",
|
||||
"docstatus": 0,
|
||||
"doctype": "Website Theme",
|
||||
"font_properties": "400,500,600,700,800",
|
||||
"idx": 26,
|
||||
"modified": "2020-05-20 14:47:12.938879",
|
||||
"font_properties": "wght:400;500;600;700;800",
|
||||
"idx": 28,
|
||||
"modified": "2020-06-04 17:47:09.207101",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Standard",
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@ def get_list_context(context, doctype, web_form_name=None):
|
|||
if not meta.custom and not list_context.row_template:
|
||||
list_context.row_template = meta.get_row_template()
|
||||
|
||||
if not meta.custom and not list_context.list_template:
|
||||
list_context.template = meta.get_list_template()
|
||||
|
||||
return list_context
|
||||
|
||||
def get_list(doctype, txt, filters, limit_start, limit_page_length=20, ignore_permissions=False,
|
||||
|
|
|
|||
|
|
@ -66,3 +66,4 @@ watchdog==0.8.0
|
|||
Werkzeug==0.16.1
|
||||
xlrd==1.2.0
|
||||
zxcvbn-python==4.4.24
|
||||
Whoosh==2.7.4
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue