diff --git a/README.md b/README.md
index 860958087e..7545249610 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
@@ -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).
diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js
index f304abd3d9..edad759216 100644
--- a/cypress/integration/control_duration.js
+++ b/cypress/integration/control_duration.js
@@ -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');
diff --git a/cypress/integration/grid_pagination.js b/cypress/integration/grid_pagination.js
index f03384cb93..b383f30bb8 100644
--- a/cypress/integration/grid_pagination.js
+++ b/cypress/integration/grid_pagination.js
@@ -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);
+ // });
});
\ No newline at end of file
diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py
index 86db7cdc8f..343dc6e2bc 100644
--- a/frappe/commands/utils.py
+++ b/frappe/commands/utils.py
@@ -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,
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index 20e4774add..232d485f36 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -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)
\ No newline at end of file
diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json
index 83d3c18453..aab59a5a0a 100644
--- a/frappe/core/doctype/docfield/docfield.json
+++ b/frappe/core/doctype/docfield/docfield.json
@@ -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",
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index 904deb9990..6ca3cccdba 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -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
diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json
index 77490c8c43..6fa7b29161 100644
--- a/frappe/custom/doctype/custom_field/custom_field.json
+++ b/frappe/custom/doctype/custom_field/custom_field.json
@@ -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",
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index 6a54d9c7e6..d4eeba3f93 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -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'),
diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json
index f422c36e61..267213517c 100644
--- a/frappe/custom/doctype/customize_form_field/customize_form_field.json
+++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json
@@ -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",
diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql
index bd93069a3f..af537e0612 100644
--- a/frappe/database/mariadb/framework_mariadb.sql
+++ b/frappe/database/mariadb/framework_mariadb.sql
@@ -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`),
diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql
index 76309e7347..8f77ed6230 100644
--- a/frappe/database/postgres/framework_postgres.sql
+++ b/frappe/database/postgres/framework_postgres.sql
@@ -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")
) ;
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 200280f6de..f5a8701089 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -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 = [
diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
index 4b595b1abf..f177aa6620 100644
--- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
+++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
@@ -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 = {
diff --git a/frappe/model/meta.py b/frappe/model/meta.py
index 0c5ec75597..1cc3abba5b 100644
--- a/frappe/model/meta.py
+++ b/frappe/model/meta.py
@@ -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)
diff --git a/frappe/modules/full_text_search.py b/frappe/modules/full_text_search.py
new file mode 100644
index 0000000000..fce9983907
--- /dev/null
+++ b/frappe/modules/full_text_search.py
@@ -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)
diff --git a/frappe/patches.txt b/frappe/patches.txt
index fb5bf447b7..582b369343 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -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
diff --git a/frappe/patches/v13_0/update_duration_options.py b/frappe/patches/v13_0/update_duration_options.py
new file mode 100644
index 0000000000..60eef8fc93
--- /dev/null
+++ b/frappe/patches/v13_0/update_duration_options.py
@@ -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')
\ No newline at end of file
diff --git a/frappe/public/css/hljs-night-owl.css b/frappe/public/css/hljs-night-owl.css
new file mode 100644
index 0000000000..932ad2e46f
--- /dev/null
+++ b/frappe/public/css/hljs-night-owl.css
@@ -0,0 +1,183 @@
+/*
+
+Night Owl for highlight.js (c) Carl Baxter
+
+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;
+ }
diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js
index 58df8e15e6..e70afd6e65 100644
--- a/frappe/public/js/frappe/form/controls/duration.js
+++ b/frappe/public/js/frappe/form/controls/duration.js
@@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
`
);
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());
}
}
diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js
index f8f0535b83..a775413d39 100644
--- a/frappe/public/js/frappe/ui/filters/filters.js
+++ b/frappe/public/js/frappe/ui/filters/filters.js
@@ -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);
}
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index f4dde5804f..38c22c9c9f 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -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;
}
diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js
index 51add61f07..60dbc928c9 100644
--- a/frappe/public/js/frappe/views/desktop/desktop.js
+++ b/frappe/public/js/frappe/views/desktop/desktop.js
@@ -26,14 +26,25 @@ export default class Desktop {
}
make_container() {
- this.container = $(`
+ this.container = $(`
+
`);
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 {
``
);
$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);
}
diff --git a/frappe/public/js/frappe/web_form/webform_script.js b/frappe/public/js/frappe/web_form/webform_script.js
index 53d9701774..c3211de99f 100644
--- a/frappe/public/js/frappe/web_form/webform_script.js
+++ b/frappe/public/js/frappe/web_form/webform_script.js
@@ -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;
diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less
index b66a0ad8fc..e68a093d25 100644
--- a/frappe/public/less/desktop.less
+++ b/frappe/public/less/desktop.less
@@ -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;
+ }
}
}
diff --git a/frappe/public/scss/base.scss b/frappe/public/scss/base.scss
index 36a1df55ac..0b01a83b02 100644
--- a/frappe/public/scss/base.scss
+++ b/frappe/public/scss/base.scss
@@ -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;
diff --git a/frappe/public/scss/blog.scss b/frappe/public/scss/blog.scss
new file mode 100644
index 0000000000..046158f23b
--- /dev/null
+++ b/frappe/public/scss/blog.scss
@@ -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;
+ }
+ }
+}
diff --git a/frappe/public/scss/doc.scss b/frappe/public/scss/doc.scss
new file mode 100644
index 0000000000..1eb3422042
--- /dev/null
+++ b/frappe/public/scss/doc.scss
@@ -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;
+}
diff --git a/frappe/public/scss/markdown.scss b/frappe/public/scss/markdown.scss
index 595b7f96a3..a77b8b941e 100644
--- a/frappe/public/scss/markdown.scss
+++ b/frappe/public/scss/markdown.scss
@@ -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;
}
}
diff --git a/frappe/public/scss/page-builder.scss b/frappe/public/scss/page-builder.scss
index a028e34158..defeb19e6e 100644
--- a/frappe/public/scss/page-builder.scss
+++ b/frappe/public/scss/page-builder.scss
@@ -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;
+ }
+ }
+ }
+ }
+}
diff --git a/frappe/public/scss/sidebar.scss b/frappe/public/scss/sidebar.scss
index 72f64a912e..4dc8e64631 100644
--- a/frappe/public/scss/sidebar.scss
+++ b/frappe/public/scss/sidebar.scss
@@ -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;
+ }
}
diff --git a/frappe/public/scss/variables.scss b/frappe/public/scss/variables.scss
index e5f3a47f6f..1339af29a9 100644
--- a/frappe/public/scss/variables.scss
+++ b/frappe/public/scss/variables.scss
@@ -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;
diff --git a/frappe/public/scss/website-image.scss b/frappe/public/scss/website-image.scss
index 8c32e821fe..d416c05650 100644
--- a/frappe/public/scss/website-image.scss
+++ b/frappe/public/scss/website-image.scss
@@ -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;
diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss
index 48bfbe8de0..5164fb2eba 100644
--- a/frappe/public/scss/website.scss
+++ b/frappe/public/scss/website.scss
@@ -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,
diff --git a/frappe/templates/doc.html b/frappe/templates/doc.html
new file mode 100644
index 0000000000..bb3cb6ec77
--- /dev/null
+++ b/frappe/templates/doc.html
@@ -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 %}
+
+{% endblock -%}
+
+{%- block navbar -%}
+
+
+
+
+
+
+
+
+ {%- set items = docs_navbar_items or [] -%}
+ {%- for item in items -%}
+ {{ render_item(item, parent=True) }}
+ {%- endfor -%}
+
+ {% include "templates/includes/web_sidebar.html" %}
+
+
+
+
+
+{%- endblock -%}
+
+{% block content %}
+
+{% macro main_content() %}
+
+ {% block page_container %}
+
+
+ {{ page_content() }}
+
+
+ {% endblock %}
+
+{% 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 %}
+
+
+
+
+
+
+
+ {{ main_content() }}
+
+
+
+
On this page
+ {{ page_toc_html }}
+
+
+
+
+
+{% endblock %}
+
+{%- block script -%}
+
+{%- endblock -%}
diff --git a/frappe/templates/includes/blog/blog.html b/frappe/templates/includes/blog/blog.html
deleted file mode 100644
index 5afaeb6ab8..0000000000
--- a/frappe/templates/includes/blog/blog.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %}{{ blog_title or _("Blog") }}{% endblock %}
-{% block header %}
{{ blog_title or _("Blog") }} {% endblock %}
-{% block hero %}{% endblock %}
-
-{% block page_content %}
-
-
-
-
- {% include "templates/includes/list/list.html" %}
-
-
-{% endblock %}
-
-{% block script %}
-
-{% endblock %}
diff --git a/frappe/templates/includes/blog/blogger.html b/frappe/templates/includes/blog/blogger.html
index 68df22786d..ef8f8257e8 100644
--- a/frappe/templates/includes/blog/blogger.html
+++ b/frappe/templates/includes/blog/blogger.html
@@ -1,7 +1,7 @@
{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %}
{{ item.group_title }}
+ {{ render_sidebar_items(item.group_items) }} + + {%- else -%} + + {% if item.type != 'input' %} + {%- set item_route = item.route[1:] if item.route[0] == '/' else item.route -%} + + {{ _(item.title or item.label) }} + + {% else %} + + {% endif %} + + {%- endif -%} ++ {% for item in items -%} + {{ render_sidebar_item(item) }} + {%- endfor %} +
+{%- endif -%} +{% endmacro %} + +{% macro my_account() %} +{% if frappe.user != 'Guest' %} ++-
+ {{ _("My Account") }}
+
+
+{% endif %} +{% endmacro %} +- {% if sidebar_title %} --
- {{ sidebar_title }}
-
- {% endif %}
- {% for item in sidebar_items -%}
- -
- {% if item.type != 'input' %}
- {%- set item_route = item.route[1:] if item.route[0] == '/' else item.route -%}
-
- {{ _(item.title or item.label) }}
-
- {% else %}
-
- {% endif %}
-
- {%- endfor %}
- {% if frappe.user != 'Guest' %}
- -
- {{ _("My Account") }}
-
- {% endif %}
-
+ {{ render_sidebar_items(sidebar_items) }} + {{ my_account() }}{{ title }}
-- {{ blog_intro }} -
- - -{{ title }}
++ {{ blog_intro }} +
++
@@ -45,7 +64,7 @@ {% endif %} {% if not disable_comments %} -
{{ post.title }}
-{{ post.intro }}
-{{ post.title }}
+ {%- else -%} +{{ post.title }} + {%- endif -%} +
{{ post.intro }}
+