From e323441c1538b5e9bcfa964acefbd5d14524c462 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 17 Mar 2020 17:12:10 +0530 Subject: [PATCH 1/9] feat: Add Web View to compose webpages with components --- frappe/core/doctype/doctype/doctype.py | 4 +- frappe/utils/jinja.py | 15 +- frappe/website/doctype/css_class/__init__.py | 0 frappe/website/doctype/css_class/css_class.js | 8 + .../website/doctype/css_class/css_class.json | 60 ++++++ frappe/website/doctype/css_class/css_class.py | 10 + .../doctype/css_class/test_css_class.py | 10 + frappe/website/doctype/web_view/__init__.py | 0 .../doctype/web_view/templates/web_view.html | 65 +++++++ .../web_view/templates/web_view_row.html | 4 + .../website/doctype/web_view/test_web_view.py | 10 + frappe/website/doctype/web_view/web_view.js | 8 + frappe/website/doctype/web_view/web_view.json | 89 +++++++++ frappe/website/doctype/web_view/web_view.py | 59 ++++++ .../website/doctype/web_view_item/__init__.py | 0 .../doctype/web_view_item/web_view_item.json | 115 +++++++++++ .../doctype/web_view_item/web_view_item.py | 10 + .../doctype/website_theme/website_theme.js | 178 ------------------ .../doctype/website_theme/website_theme.json | 72 ++++++- .../doctype/website_theme/website_theme.py | 19 +- .../website_theme/website_theme_template.scss | 23 +++ 21 files changed, 568 insertions(+), 191 deletions(-) create mode 100644 frappe/website/doctype/css_class/__init__.py create mode 100644 frappe/website/doctype/css_class/css_class.js create mode 100644 frappe/website/doctype/css_class/css_class.json create mode 100644 frappe/website/doctype/css_class/css_class.py create mode 100644 frappe/website/doctype/css_class/test_css_class.py create mode 100644 frappe/website/doctype/web_view/__init__.py create mode 100644 frappe/website/doctype/web_view/templates/web_view.html create mode 100644 frappe/website/doctype/web_view/templates/web_view_row.html create mode 100644 frappe/website/doctype/web_view/test_web_view.py create mode 100644 frappe/website/doctype/web_view/web_view.js create mode 100644 frappe/website/doctype/web_view/web_view.json create mode 100644 frappe/website/doctype/web_view/web_view.py create mode 100644 frappe/website/doctype/web_view_item/__init__.py create mode 100644 frappe/website/doctype/web_view_item/web_view_item.json create mode 100644 frappe/website/doctype/web_view_item/web_view_item.py create mode 100644 frappe/website/doctype/website_theme/website_theme_template.scss diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index f970f51419..f7c9cbe28a 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -206,7 +206,7 @@ class DocType(Document): if d.fieldtype: if (not getattr(d, "fieldname", None)): if d.label: - d.fieldname = d.label.strip().lower().replace(' ','_') + d.fieldname = d.label.strip().lower().replace(' ','_').strip('?') if d.fieldname in restricted: d.fieldname = d.fieldname + '1' if d.fieldtype=='Section Break': @@ -914,7 +914,7 @@ def validate_fields(meta): if not d.permlevel: d.permlevel = 0 if d.fieldtype not in table_fields: d.allow_bulk_edit = 0 if not d.fieldname: - d.fieldname = d.fieldname.lower() + d.fieldname = d.fieldname.lower().strip('?') check_illegal_characters(d.fieldname) check_invalid_fieldnames(meta.get("name"), d.fieldname) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 7c3b9b0482..bc26490422 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -68,10 +68,7 @@ def render_template(template, context, is_path=None, safe_render=True): if not template: return "" - # if it ends with .html then its a freaking path, not html - if (is_path - or template.startswith("templates/") - or (template.endswith('.html') and '\n' not in template)): + if (is_path or guess_is_path(template)): return get_jenv().get_template(template).render(context) else: if safe_render and ".__" in template: @@ -81,6 +78,16 @@ def render_template(template, context, is_path=None, safe_render=True): except TemplateError: throw(title="Jinja Template Error", msg="
{template}
{tb}
".format(template=template, tb=get_traceback())) +def guess_is_path(template): + # template can be passed as a path or content + # if its single line and ends with a html, then its probably a path + if not '\n' in template and '.' in template: + extn = template.rsplit('.')[-1] + if extn in ('html', 'css', 'scss', 'py'): + return True + + return False + def get_jloader(): import frappe diff --git a/frappe/website/doctype/css_class/__init__.py b/frappe/website/doctype/css_class/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/css_class/css_class.js b/frappe/website/doctype/css_class/css_class.js new file mode 100644 index 0000000000..4544e249bf --- /dev/null +++ b/frappe/website/doctype/css_class/css_class.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('CSS Class', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/website/doctype/css_class/css_class.json b/frappe/website/doctype/css_class/css_class.json new file mode 100644 index 0000000000..2a7e1e010e --- /dev/null +++ b/frappe/website/doctype/css_class/css_class.json @@ -0,0 +1,60 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2020-03-17 15:03:31.431344", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "is_global", + "is_dynamic", + "css" + ], + "fields": [ + { + "default": "0", + "fieldname": "is_global", + "fieldtype": "Check", + "label": "Is Global?" + }, + { + "fieldname": "css", + "fieldtype": "Code", + "in_list_view": 1, + "label": "CSS", + "reqd": 1 + }, + { + "default": "0", + "description": "Website Theme elements are accessible as Jinja variables. Example: \"{{ primary_color }}\"", + "fieldname": "is_dynamic", + "fieldtype": "Check", + "label": "Is Dynamic?" + } + ], + "links": [], + "modified": "2020-03-17 17:01:14.874631", + "modified_by": "Administrator", + "module": "Website", + "name": "CSS Class", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Website Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/css_class/css_class.py b/frappe/website/doctype/css_class/css_class.py new file mode 100644 index 0000000000..cb9e7483d4 --- /dev/null +++ b/frappe/website/doctype/css_class/css_class.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class CSSClass(Document): + pass diff --git a/frappe/website/doctype/css_class/test_css_class.py b/frappe/website/doctype/css_class/test_css_class.py new file mode 100644 index 0000000000..551b44e3f2 --- /dev/null +++ b/frappe/website/doctype/css_class/test_css_class.py @@ -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 TestCSSClass(unittest.TestCase): + pass diff --git a/frappe/website/doctype/web_view/__init__.py b/frappe/website/doctype/web_view/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/web_view/templates/web_view.html b/frappe/website/doctype/web_view/templates/web_view.html new file mode 100644 index 0000000000..8515432f5b --- /dev/null +++ b/frappe/website/doctype/web_view/templates/web_view.html @@ -0,0 +1,65 @@ +{% extends "templates/web.html" %} + +{% block page_content %} + +{% if css_rules or css %} + +{% endif %} + +{% macro render_element(element) %} + {% if element.element_type=='Content' %} + {{ element.web_content_html }} + {% elif element.element_type=='Image' %} + + {% endif %} +{% endmacro %} + + +{% for section in sections %} +
+
+ {% if section.section_intro %} +
{{ section.section_intro }}
+ {% endif %} + {% if section.section_type == 'List' %} + {% for element in section.elements %} + {{ render_element(element) }} + {% endfor %} + {% elif section.section_type == 'Grid' %} +
+ {% for element in section.elements %} +
+ {{ render_element(element) }} +
+ {% endfor %} +
+ {% elif section.section_type == 'Tabbed' %} + +
+ {% for element in section.elements %} +
+ {{ render_element(element) }} +
+ {% endfor %} +
+ + {% endif %} +
+
+{% endfor %} +{% endblock %} + + \ No newline at end of file diff --git a/frappe/website/doctype/web_view/templates/web_view_row.html b/frappe/website/doctype/web_view/templates/web_view_row.html new file mode 100644 index 0000000000..2b999819cb --- /dev/null +++ b/frappe/website/doctype/web_view/templates/web_view_row.html @@ -0,0 +1,4 @@ +
+ {{ title }} +
+ \ No newline at end of file diff --git a/frappe/website/doctype/web_view/test_web_view.py b/frappe/website/doctype/web_view/test_web_view.py new file mode 100644 index 0000000000..70dc1ca906 --- /dev/null +++ b/frappe/website/doctype/web_view/test_web_view.py @@ -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 TestWebView(unittest.TestCase): + pass diff --git a/frappe/website/doctype/web_view/web_view.js b/frappe/website/doctype/web_view/web_view.js new file mode 100644 index 0000000000..449c0949b6 --- /dev/null +++ b/frappe/website/doctype/web_view/web_view.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Web View', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/website/doctype/web_view/web_view.json b/frappe/website/doctype/web_view/web_view.json new file mode 100644 index 0000000000..964cc1fbae --- /dev/null +++ b/frappe/website/doctype/web_view/web_view.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "allow_guest_to_view": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:title", + "creation": "2020-03-16 15:28:03.828741", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "route", + "published", + "items", + "css" + ], + "fields": [ + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Web View Item", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "route", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Route", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Published" + }, + { + "fieldname": "css", + "fieldtype": "Code", + "label": "CSS" + } + ], + "has_web_view": 1, + "links": [], + "modified": "2020-03-16 18:06:47.024221", + "modified_by": "Administrator", + "module": "Website", + "name": "Web View", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Website Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/web_view/web_view.py b/frappe/website/doctype/web_view/web_view.py new file mode 100644 index 0000000000..7c8c9d76eb --- /dev/null +++ b/frappe/website/doctype/web_view/web_view.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.website.website_generator import WebsiteGenerator +from frappe.utils import markdown +import frappe + +class WebView(WebsiteGenerator): + def get_context(self, context): + # group items into sections + context.sections = [] + context.css_rules = [] + for item in self.items: + if not context.sections and item.element_type!='Section': + self.add_default_section(context) + + if item.element_type=='Section': + item.elements = [] + context.sections.append(item) + + if item.section_intro: + item.section_intro = markdown(item.section_intro) + + else: + if item.hide: + continue + + if item.web_content_type == 'Markdown': + item.web_content_html = markdown(item.web_content_markdown) + + if item.title: + item.element_id = frappe.scrub(item.title) + + context.sections[-1].elements.append(item) + + if item.element_class: + css, is_dynamic = frappe.db.get_value('CSS Class', item.element_class, ['css', 'is_dynamic']) + if is_dynamic: + css = frappe.render_template(css, self.get_theme()) + context.css_rules.append(css) + + def get_theme(self): + # get theme properties + if not hasattr(self, '_theme'): + default_theme = frappe.db.get_value("Website Settings", "Website Settings", "website_theme") + self._theme = frappe.get_value('Website Theme', default_theme, '*') + return self._theme + + def add_default_section(self, context): + # add a default section if not added + context.section.append(dict( + element_type='Section', + section_type='List', + title='Default Section', + elements=[] + )) diff --git a/frappe/website/doctype/web_view_item/__init__.py b/frappe/website/doctype/web_view_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/web_view_item/web_view_item.json b/frappe/website/doctype/web_view_item/web_view_item.json new file mode 100644 index 0000000000..7be1c35415 --- /dev/null +++ b/frappe/website/doctype/web_view_item/web_view_item.json @@ -0,0 +1,115 @@ +{ + "actions": [], + "creation": "2020-03-16 15:25:17.530296", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "element_type", + "title", + "hide", + "column_break_3", + "columns", + "element_class", + "section_break_5", + "section_type", + "web_content_type", + "web_content_html", + "web_content_markdown", + "image_url", + "section_intro" + ], + "fields": [ + { + "fieldname": "element_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Element Type", + "options": "Section\nContent\nHero\nParagraph\nImage\nQuote", + "reqd": 1 + }, + { + "depends_on": "eval:doc.element_type==='Section'", + "fieldname": "section_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Section Type", + "options": "\nList\nTabbed\nGrid" + }, + { + "depends_on": "eval:doc.element_type==='Content'", + "fieldname": "web_content_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Web Content Type", + "options": "\nHTML\nMarkdown" + }, + { + "depends_on": "eval:doc.web_content_type==='HTML'", + "fieldname": "web_content_html", + "fieldtype": "HTML Editor", + "label": "Web Content HTML" + }, + { + "depends_on": "eval:doc.web_content_type==='Markdown'", + "fieldname": "web_content_markdown", + "fieldtype": "Markdown Editor", + "label": "Web Content Markdown" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title" + }, + { + "fieldname": "columns", + "fieldtype": "Int", + "label": "Columns" + }, + { + "fieldname": "element_class", + "fieldtype": "Link", + "label": "Element Class", + "options": "CSS Class" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.element_type==='Image'", + "fieldname": "image_url", + "fieldtype": "Small Text", + "label": "Image URL" + }, + { + "depends_on": "eval:doc.element_type==='Section'", + "fieldname": "section_intro", + "fieldtype": "Markdown Editor", + "label": "Section Intro" + }, + { + "default": "0", + "fieldname": "hide", + "fieldtype": "Check", + "label": "Hide" + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-17 17:06:37.157763", + "modified_by": "Administrator", + "module": "Website", + "name": "Web View Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/web_view_item/web_view_item.py b/frappe/website/doctype/web_view_item/web_view_item.py new file mode 100644 index 0000000000..cc440305c0 --- /dev/null +++ b/frappe/website/doctype/web_view_item/web_view_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WebViewItem(Document): + pass diff --git a/frappe/website/doctype/website_theme/website_theme.js b/frappe/website/doctype/website_theme/website_theme.js index b39bd5defe..75ecbe15e3 100644 --- a/frappe/website/doctype/website_theme/website_theme.js +++ b/frappe/website/doctype/website_theme/website_theme.js @@ -6,7 +6,6 @@ frappe.ui.form.on('Website Theme', { frm.clear_custom_buttons(); frm.toggle_display(["module", "custom"], !frappe.boot.developer_mode); - frm.trigger('setup_configure_theme'); frm.trigger('set_default_theme_button_and_indicator'); if (!frm.doc.custom && !frappe.boot.developer_mode) { @@ -17,96 +16,6 @@ frappe.ui.form.on('Website Theme', { } }, - setup_configure_theme(frm) { - frm.add_custom_button(__('Configure Theme'), () => { - const d = new frappe.ui.Dialog({ - title: __('Configure Theme'), - fields: [ - { - label: __('Font Styles'), - fieldtype: 'Section Break' - }, - { - label: __('Google Font'), - fieldtype: 'Data', - fieldname: 'google_font', - description: __('Add the name of a "Google Web Font" e.g. "Open Sans"') - }, - { - label: __('Font Size (px)'), - fieldtype: 'Int', - fieldname: 'font_size', - default: 16 - }, - { - label: __('Theme Colors'), - fieldtype: 'Section Break', - }, - { - label: __('Primary Color'), - fieldtype: 'Color', - fieldname: 'primary_color' - }, - { - label: __('Dark Color'), - fieldtype: 'Color', - fieldname: 'dark_color' - }, - { - label: __('Text Color'), - fieldtype: 'Color', - fieldname: 'text_color' - }, - { - label: __('Background Color'), - fieldtype: 'Color', - fieldname: 'background_color' - }, - { - label: __('Misc'), - fieldtype: 'Section Break', - }, - { - label: __('Navbar Style'), - fieldtype: 'Select', - fieldname: 'navbar_style', - options: [ - 'Light', - 'Dark' - ], - default: 'Light' - }, - { - label: __('Enable Shadows'), - fieldtype: 'Check', - fieldname: 'enable_shadows' - }, - { - label: __('Enable Gradients'), - fieldtype: 'Check', - fieldname: 'enable_gradients' - }, - { - label: __('Rounded Corners'), - fieldtype: 'Check', - fieldname: 'enable_rounded', - default: 1 - }, - ], - primary_action: (values) => { - frm.set_value('theme_json', JSON.stringify(values)); - frm.events.set_theme_from_config(frm, values); - d.hide(); - } - }); - - if (frm.doc.theme_json) { - d.set_values(JSON.parse(frm.doc.theme_json)); - } - d.show(); - }); - }, - set_default_theme_button_and_indicator(frm) { frappe.db.get_single_value('Website Settings', 'website_theme') .then(value => { @@ -122,92 +31,5 @@ frappe.ui.form.on('Website Theme', { } } }); - }, - - set_theme_from_config(frm, config) { - const { - google_font, - font_size, - primary_color, - dark_color, - text_color, - background_color, - navbar_style, - enable_shadows, - enable_gradients, - enable_rounded - } = config; - - let scss_lines = []; - let js_lines = []; - if (google_font) { - const google_font_slug = google_font.split(' ').join('+'); - const font_family_default = `'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'`; - scss_lines.push( - `@import url('https://fonts.googleapis.com/css?family=${google_font_slug}:400,300,400italic,700&subset=latin,latin-ext');`, - `$font-family-sans-serif: "${google_font}", ${font_family_default};` - ); - } - if (primary_color) { - scss_lines.push( - `$primary: ${primary_color};` - ); - } - if (dark_color) { - scss_lines.push( - `$dark: ${dark_color};` - ); - } - if (text_color) { - scss_lines.push( - `$body-color: ${text_color};` - ); - } - if (background_color) { - scss_lines.push( - `$body-bg: ${background_color};` - ); - } - - scss_lines.push( - `$enable-shadows: ${Boolean(enable_shadows)};` - ); - - scss_lines.push( - `$enable-gradients: ${Boolean(enable_gradients)};` - ); - - scss_lines.push( - `$enable-rounded: ${Boolean(enable_rounded)};` - ); - - if (font_size) { - scss_lines.push( - '\n', - `body {\n\tfont-size: ${font_size}px;\n}` - ); - } - - if (navbar_style === 'Dark') { - if (!(frm.doc.js || '').includes(`.addClass('navbar-dark bg-dark')`)) { - js_lines.push( - `frappe.ready(() => {`, - `\t$('.navbar').removeClass('navbar-light bg-white').addClass('navbar-dark bg-dark')`, - `})` - ); - } - } - - scss_lines.push( - `@import "frappe/public/scss/website";`, - '\n' - ); - - // set scss - frm.set_value('theme_scss', scss_lines.join('\n')); - - // set js - const js = frm.doc.js || ''; - frm.set_value('js', js_lines.join('\n') + js); } }); diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json index 0d59384f3c..63e70f9668 100644 --- a/frappe/website/doctype/website_theme/website_theme.json +++ b/frappe/website/doctype/website_theme/website_theme.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "field:theme", "creation": "2015-02-18 12:46:38.168929", @@ -9,7 +10,18 @@ "theme", "module", "custom", + "configuration_section", + "google_font", + "font_size", + "column_break_7", + "primary_color", + "text_color", + "light_color", + "dark_color", + "background_color", + "stylesheet_section", "theme_scss", + "custom_scss", "theme_json", "theme_url", "custom_js_section", @@ -43,7 +55,8 @@ "fieldname": "theme_scss", "fieldtype": "Code", "label": "Theme", - "options": "SCSS" + "options": "SCSS", + "read_only": 1 }, { "fieldname": "theme_url", @@ -68,9 +81,64 @@ "hidden": 1, "label": "Theme JSON", "options": "JSON" + }, + { + "fieldname": "configuration_section", + "fieldtype": "Section Break", + "label": "Configuration" + }, + { + "fieldname": "google_font", + "fieldtype": "Data", + "label": "Google Font" + }, + { + "fieldname": "font_size", + "fieldtype": "Data", + "label": "Font Size" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "primary_color", + "fieldtype": "Color", + "label": "Primary Color" + }, + { + "fieldname": "text_color", + "fieldtype": "Color", + "label": "Text Color" + }, + { + "fieldname": "dark_color", + "fieldtype": "Color", + "label": "Dark Color" + }, + { + "fieldname": "background_color", + "fieldtype": "Color", + "label": "Background Color" + }, + { + "fieldname": "stylesheet_section", + "fieldtype": "Section Break", + "label": "Stylesheet" + }, + { + "fieldname": "custom_scss", + "fieldtype": "Code", + "label": "Custom SCSS" + }, + { + "fieldname": "light_color", + "fieldtype": "Color", + "label": "Light Color" } ], - "modified": "2019-06-14 18:36:21.283390", + "links": [], + "modified": "2020-03-17 16:52:18.541152", "modified_by": "Administrator", "module": "Website", "name": "Website Theme", diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index f16fc90d79..9a20bab5f7 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -10,6 +10,7 @@ from os.path import join as join_path, exists as path_exists class WebsiteTheme(Document): def validate(self): self.validate_if_customizable() + self.render_theme() self.validate_theme() def on_update(self): @@ -35,12 +36,16 @@ class WebsiteTheme(Document): if self.is_standard_and_not_valid_user(): frappe.throw(_("Please Duplicate this Website Theme to customize.")) + def render_theme(self): + if self.google_font: + self.google_font = self.google_font.replace(' ', '+') + self.theme_scss = frappe.render_template('frappe/website/doctype/website_theme/website_theme_template.scss', self.as_dict()) + def validate_theme(self): '''Generate theme css if theme_scss has changed''' - if self.theme_scss: - doc_before_save = self.get_doc_before_save() - if doc_before_save is None or self.theme_scss != doc_before_save.theme_scss: - self.generate_bootstrap_theme() + doc_before_save = self.get_doc_before_save() + if doc_before_save is None or get_scss(self) != get_scss(doc_before_save): + self.generate_bootstrap_theme() def export_doc(self): """Export to standard folder `[module]/website_theme/[name]/[name].json`.""" @@ -59,7 +64,7 @@ class WebsiteTheme(Document): file_name = frappe.scrub(self.name) + '_' + frappe.generate_hash('Website Theme', 8) + '.css' output_path = join_path(frappe.utils.get_bench_path(), 'sites', 'assets', 'css', file_name) - content = self.theme_scss + content = get_scss(self) content = content.replace('\n', '\\n') command = ['node', 'generate_bootstrap_theme.js', output_path, content] @@ -116,3 +121,7 @@ def generate_theme_files_if_not_exist(): doc.save() except Exception: frappe.log_error(frappe.get_traceback(), "Theme File Generation Failed") + +def get_scss(doc): + return doc.theme_scss + '\n' + doc.custom_scss + diff --git a/frappe/website/doctype/website_theme/website_theme_template.scss b/frappe/website/doctype/website_theme/website_theme_template.scss new file mode 100644 index 0000000000..6267eb531d --- /dev/null +++ b/frappe/website/doctype/website_theme/website_theme_template.scss @@ -0,0 +1,23 @@ +{% if google_font %} +@import url('https://fonts.googleapis.com/css?family={{ google_font }}:400,400italic,600&subset=latin,latin-ext'); +$font-family-sans-serif: "{{ google_font }}", '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'; +{% endif %} + +{% if primary_color %}$primary: {{ primary_color }};{% endif %} +{% if dark_color %}$dark: {{ dark_color }};{% endif %} +{% if text_color %}$body-color: {{ text_color }};{% endif %} +{% if background_color %}$body-bg: {{ background_color }};{% endif %} + +$enable-shadows: {{ enable_shadows and "true" or "false" }}; +$enable-gradients: {{ enable_gradients and "true" or "false" }}; +$enable-rounded: {{ enable_rounded and "true" or "false" }}; + +@import "frappe/public/scss/website"; + +body { + {% if font_size %} + font-size: {{ font_size }}; + {% endif %} + font-smoothing: antialiased; + -webkit-font-smoothing: antialiased; +} From 0322ab198d3f1fbf63ee8427a30615ce50eaec0d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 18 Mar 2020 18:51:03 +0530 Subject: [PATCH 2/9] feat: make Web View embeddable and allow navbar and footer as Web Views --- frappe/templates/base.html | 12 +++- .../doctype/web_view/templates/web_view.html | 60 +----------------- .../web_view/templates/web_view_content.html | 62 +++++++++++++++++++ frappe/website/doctype/web_view/web_view.py | 51 +++++++++------ .../website_settings/test_website_settings.py | 10 +++ .../website_settings/website_settings.json | 38 ++++++++++++ .../website_settings/website_settings.py | 12 ++++ .../doctype/website_theme/website_theme.json | 9 ++- .../website_theme/website_theme_template.scss | 4 +- 9 files changed, 176 insertions(+), 82 deletions(-) create mode 100644 frappe/website/doctype/web_view/templates/web_view_content.html create mode 100644 frappe/website/doctype/website_settings/test_website_settings.py diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 1c5f286442..2a241c4843 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -62,7 +62,11 @@ {%- endblock -%} {%- block navbar -%} - {% include "templates/includes/navbar/navbar.html" %} + {%- if navbar_content -%} + {{ navbar_content }} + {%- else -%} + {% include "templates/includes/navbar/navbar.html" %} + {%- endif -%} {%- endblock -%} {% block content %} @@ -70,7 +74,11 @@ {% endblock %} {%- block footer -%} - {% include "templates/includes/footer/footer.html" %} + {%- if footer_content -%} + {{ footer_content }} + {%- else -%} + {% include "templates/includes/footer/footer.html" %} + {%- endif -%} {%- endblock -%} {% block base_scripts %} diff --git a/frappe/website/doctype/web_view/templates/web_view.html b/frappe/website/doctype/web_view/templates/web_view.html index 8515432f5b..bf993c05fb 100644 --- a/frappe/website/doctype/web_view/templates/web_view.html +++ b/frappe/website/doctype/web_view/templates/web_view.html @@ -1,65 +1,7 @@ {% extends "templates/web.html" %} {% block page_content %} - -{% if css_rules or css %} - -{% endif %} - -{% macro render_element(element) %} - {% if element.element_type=='Content' %} - {{ element.web_content_html }} - {% elif element.element_type=='Image' %} - - {% endif %} -{% endmacro %} - - -{% for section in sections %} -
-
- {% if section.section_intro %} -
{{ section.section_intro }}
- {% endif %} - {% if section.section_type == 'List' %} - {% for element in section.elements %} - {{ render_element(element) }} - {% endfor %} - {% elif section.section_type == 'Grid' %} -
- {% for element in section.elements %} -
- {{ render_element(element) }} -
- {% endfor %} -
- {% elif section.section_type == 'Tabbed' %} - -
- {% for element in section.elements %} -
- {{ render_element(element) }} -
- {% endfor %} -
- - {% endif %} -
-
-{% endfor %} + {% include "frappe/website/doctype/web_view/templates/web_view_content.html" %} {% endblock %} \ No newline at end of file diff --git a/frappe/website/doctype/web_view/templates/web_view_content.html b/frappe/website/doctype/web_view/templates/web_view_content.html new file mode 100644 index 0000000000..dd14e80d39 --- /dev/null +++ b/frappe/website/doctype/web_view/templates/web_view_content.html @@ -0,0 +1,62 @@ +{%- if css_rules or css -%} + + +{%- endif -%} + +{%- macro render_element(element) -%} + {%- if element.element_type=='Content' -%} + {{ element.web_content_html }} + {%- elif element.element_type=='Image' -%} + + {%- endif -%} +{%- endmacro -%} + +{%- for section in sections -%} +
+
+ {%- if section.section_intro -%} + +
{{ section.section_intro }}
+ {%- endif -%} + + {%- if section.section_type == 'List' -%} + {%- for element in section.elements -%} + {{ render_element(element) }} + {%- endfor -%} + + {%- elif section.section_type == 'Grid' -%} +
+ {%- for element in section.elements -%} +
+ {{ render_element(element) }} +
+ {%- endfor -%} +
+ + {%- elif section.section_type == 'Tabbed' -%} + +
+ {%- for element in section.elements -%} +
+ {{ render_element(element) }} +
+ {%- endfor -%} +
+ + {%- endif -%} +
+
+{%- endfor -%} \ No newline at end of file diff --git a/frappe/website/doctype/web_view/web_view.py b/frappe/website/doctype/web_view/web_view.py index 7c8c9d76eb..92f564553c 100644 --- a/frappe/website/doctype/web_view/web_view.py +++ b/frappe/website/doctype/web_view/web_view.py @@ -18,29 +18,44 @@ class WebView(WebsiteGenerator): self.add_default_section(context) if item.element_type=='Section': - item.elements = [] - context.sections.append(item) - - if item.section_intro: - item.section_intro = markdown(item.section_intro) - + self.add_section(context, item) else: - if item.hide: - continue + self.add_item(context, item) - if item.web_content_type == 'Markdown': - item.web_content_html = markdown(item.web_content_markdown) + self.add_css_class(context, item) - if item.title: - item.element_id = frappe.scrub(item.title) + return context - context.sections[-1].elements.append(item) + def add_section(self, context, item): + item.elements = [] + context.sections.append(item) - if item.element_class: - css, is_dynamic = frappe.db.get_value('CSS Class', item.element_class, ['css', 'is_dynamic']) - if is_dynamic: - css = frappe.render_template(css, self.get_theme()) - context.css_rules.append(css) + if item.section_intro: + item.section_intro = markdown(item.section_intro) + + def add_item(self, context, item): + if item.hide: + return + + if item.web_content_type == 'Markdown': + item.web_content_html = markdown(item.web_content_markdown) + + if item.title: + item.element_id = frappe.scrub(item.title) + + context.sections[-1].elements.append(item) + + def add_css_class(self, context, item): + # add css class definitions selected by the user + if item.element_class and not item.hide: + css, is_dynamic = frappe.db.get_value('CSS Class', item.element_class, ['css', 'is_dynamic']) + if is_dynamic: + css = frappe.render_template(css, self.get_theme()) + context.css_rules.append(css) + + def render_content(self): + # webview can be rendered as an object (see footer) + return frappe.render_template("frappe/website/doctype/web_view/templates/web_view_content.html", self.get_context(self.as_dict())) def get_theme(self): # get theme properties diff --git a/frappe/website/doctype/website_settings/test_website_settings.py b/frappe/website/doctype/website_settings/test_website_settings.py new file mode 100644 index 0000000000..9eca957713 --- /dev/null +++ b/frappe/website/doctype/website_settings/test_website_settings.py @@ -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 TestWebsiteSettings(unittest.TestCase): + pass diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 39ffa2329f..a730f4746f 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -19,11 +19,15 @@ "set_banner_from_image", "favicon", "top_bar", + "top_bar_type", + "top_bar_web_view", "navbar_search", "top_bar_items", "banner", "banner_html", "footer", + "footer_type", + "footer_web_view", "copyright", "address", "footer_items", @@ -130,11 +134,13 @@ }, { "default": "0", + "depends_on": "eval:doc.top_bar_type==='Standard'", "fieldname": "navbar_search", "fieldtype": "Check", "label": "Include Search in Top Bar" }, { + "depends_on": "eval:doc.top_bar_type==='Standard'", "fieldname": "top_bar_items", "fieldtype": "Table", "label": "Top Bar Items", @@ -160,17 +166,20 @@ "label": "Footer" }, { + "depends_on": "eval:doc.footer_type==='Standard'", "fieldname": "copyright", "fieldtype": "Data", "label": "Copyright" }, { + "depends_on": "eval:doc.footer_type==='Standard'", "description": "Address and other legal information you may want to put in the footer.", "fieldname": "address", "fieldtype": "Text Editor", "label": "Address" }, { + "depends_on": "eval:doc.footer_type==='Standard'", "fieldname": "footer_items", "fieldtype": "Table", "label": "Footer Items", @@ -178,6 +187,7 @@ }, { "default": "0", + "depends_on": "eval:doc.footer_type==='Standard'", "fieldname": "hide_footer_signup", "fieldtype": "Check", "label": "Hide Footer Signup" @@ -319,6 +329,34 @@ "fieldname": "authorize_api_indexing_access", "fieldtype": "Button", "label": "Authorize API Indexing Access" + }, + { + "default": "Standard", + "fieldname": "footer_type", + "fieldtype": "Select", + "label": "Footer Type", + "options": "Standard\nWeb View" + }, + { + "depends_on": "eval:doc.footer_type==='Web View'", + "fieldname": "footer_web_view", + "fieldtype": "Link", + "label": "Footer Web View", + "options": "Web View" + }, + { + "default": "Standard", + "fieldname": "top_bar_type", + "fieldtype": "Select", + "label": "Top Bar Type", + "options": "Standard\nWeb View" + }, + { + "depends_on": "eval:doc.top_bar_type==='Web View'", + "fieldname": "top_bar_web_view", + "fieldtype": "Link", + "label": "Top Bar Web View", + "options": "Web View" } ], "icon": "fa fa-cog", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 4356b1aa82..49b93fae1d 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -149,6 +149,7 @@ def get_website_settings(): context[key] = context[key][-1] add_website_theme(context) + add_webviews(context, settings) if not context.get("favicon"): context["favicon"] = "/assets/frappe/images/favicon.png" @@ -158,6 +159,17 @@ def get_website_settings(): return context +def add_webviews(context, settings): + # render footer as webview, not standard view + # see base.html for how this is handled + if settings.footer_type=='Web View' and settings.footer_web_view: + context.footer_content = frappe.get_doc('Web View', + settings.footer_web_view).render_content() + + if settings.top_bar_type=='Web View' and settings.top_bar_web_view: + context.navbar_content = frappe.get_doc('Web View', + settings.top_bar_web_view).render_content() + def get_items(parentfield): all_top_items = frappe.db.sql("""\ select * from `tabTop Bar Item` diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json index 63e70f9668..5704b97e0b 100644 --- a/frappe/website/doctype/website_theme/website_theme.json +++ b/frappe/website/doctype/website_theme/website_theme.json @@ -13,6 +13,7 @@ "configuration_section", "google_font", "font_size", + "font_properties", "column_break_7", "primary_color", "text_color", @@ -135,10 +136,16 @@ "fieldname": "light_color", "fieldtype": "Color", "label": "Light Color" + }, + { + "default": "300,600", + "fieldname": "font_properties", + "fieldtype": "Data", + "label": "Font Properties" } ], "links": [], - "modified": "2020-03-17 16:52:18.541152", + "modified": "2020-03-18 18:24:57.469492", "modified_by": "Administrator", "module": "Website", "name": "Website Theme", diff --git a/frappe/website/doctype/website_theme/website_theme_template.scss b/frappe/website/doctype/website_theme/website_theme_template.scss index 6267eb531d..ea6775a73d 100644 --- a/frappe/website/doctype/website_theme/website_theme_template.scss +++ b/frappe/website/doctype/website_theme/website_theme_template.scss @@ -1,6 +1,6 @@ {% if google_font %} -@import url('https://fonts.googleapis.com/css?family={{ google_font }}:400,400italic,600&subset=latin,latin-ext'); -$font-family-sans-serif: "{{ google_font }}", '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'; +@import url('https://fonts.googleapis.com/css?family={{ google_font }}:{{ font_properties }}&display=swap'); +$font-family-sans-serif: "{{ google_font }}", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; {% endif %} {% if primary_color %}$primary: {{ primary_color }};{% endif %} From 6b5a47c5997b64cd09e520ee7a62134d8ddfa451 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 27 Mar 2020 11:29:50 +0530 Subject: [PATCH 3/9] fix: minor bug fixes --- frappe/templates/web.html | 2 +- .../web_view/templates/web_view_content.html | 21 ++++++++++++++++--- .../doctype/web_view_item/web_view_item.json | 8 ++++++- .../doctype/website_theme/website_theme.json | 9 +++++++- .../doctype/website_theme/website_theme.py | 2 -- .../website_theme/website_theme_template.scss | 4 +--- 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/frappe/templates/web.html b/frappe/templates/web.html index e61672124a..d2d38a6320 100644 --- a/frappe/templates/web.html +++ b/frappe/templates/web.html @@ -13,7 +13,7 @@ {% block page_container %} -
+