diff --git a/.codacy.yml b/.codacy.yml deleted file mode 100644 index 4754a63e7e..0000000000 --- a/.codacy.yml +++ /dev/null @@ -1,2 +0,0 @@ -exclude_paths: - - '**.sql' \ No newline at end of file diff --git a/.deepsource.toml b/.deepsource.toml deleted file mode 100644 index f5edb47a13..0000000000 --- a/.deepsource.toml +++ /dev/null @@ -1,17 +0,0 @@ -version = 1 - -test_patterns = [ - "**/test_*.py" -] - -exclude_patterns = [ - "frappe/patches/**", - "*.min.js" -] - -[[analyzers]] -name = "python" -enabled = true - - [analyzers.meta] - runtime_version = "3.x.x" diff --git a/.eslintignore b/.eslintignore index 2fd65d307d..baf9bb2cc5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,5 +5,4 @@ frappe/core/doctype/doctype/boilerplate/* frappe/core/doctype/report/boilerplate/* frappe/public/js/frappe/class.js frappe/templates/includes/* -frappe/tests/testcafe/* -frappe/www/website_script.js \ No newline at end of file +frappe/www/website_script.js diff --git a/.mergify.yml b/.mergify.yml index 5b0ec71b1c..eae959b8a0 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -14,7 +14,6 @@ pull_request_rules: - name: Automatic squash on CI success and review conditions: - status-success=Sider - - status-success=Semantic Pull Request - status-success=Travis CI - Pull Request - status-success=security/snyk (frappe) - label!=dont-merge diff --git a/frappe/.stylelintrc b/.stylelintrc similarity index 100% rename from frappe/.stylelintrc rename to .stylelintrc diff --git a/ci/fix-mariadb.sh b/ci/fix-mariadb.sh deleted file mode 100755 index 886ec5e0d0..0000000000 --- a/ci/fix-mariadb.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# stolen from http://cgit.drupalcode.org/octopus/commit/?id=db4f837 -includedir=`mysql_config --variable=pkgincludedir` -thiscwd=`pwd` -_THIS_DB_VERSION=`mysql -V 2>&1 | tr -d "\n" | cut -d" " -f6 | awk '{ print $1}' | cut -d"-" -f1 | awk '{ print $1}' | sed "s/[\,']//g"` -if [ "$_THIS_DB_VERSION" = "5.5.40" ] && [ ! -e "$includedir-$_THIS_DB_VERSION-fixed.log" ] ; then - cd $includedir - sudo patch -p1 < $thiscwd/ci/my_config.h.patch &> /dev/null - sudo touch $includedir-$_THIS_DB_VERSION-fixed.log -fi diff --git a/ci/my_config.h.patch b/ci/my_config.h.patch deleted file mode 100644 index 5247b5b39b..0000000000 --- a/ci/my_config.h.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff -burp a/my_config.h b/my_config.h ---- a/my_config.h 2014-10-09 19:32:46.000000000 -0400 -+++ b/my_config.h 2014-10-09 19:35:12.000000000 -0400 -@@ -641,17 +641,4 @@ - #define SIZEOF_TIME_T 8 - /* #undef TIME_T_UNSIGNED */ - --/* -- stat structure (from ) is conditionally defined -- to have different layout and size depending on the defined macros. -- The correct macro is defined in my_config.h, which means it MUST be -- included first (or at least before - so, practically, -- before including any system headers). -- -- __GLIBC__ is defined in --*/ --#ifdef __GLIBC__ --#error MUST be included first! --#endif -- - #endif - diff --git a/cypress/integration/control_rating.js b/cypress/integration/control_rating.js index 31c036d240..592ed87004 100644 --- a/cypress/integration/control_rating.js +++ b/cypress/integration/control_rating.js @@ -18,7 +18,7 @@ context('Control Rating', () => { get_dialog_with_rating().as('dialog'); cy.get('div.rating') - .children('i.fa') + .children('svg') .first() .click() .should('have.class', 'star-click'); @@ -33,11 +33,11 @@ context('Control Rating', () => { get_dialog_with_rating(); cy.get('div.rating') - .children('i.fa') + .children('svg') .first() .invoke('trigger', 'mouseenter') .should('have.class', 'star-hover') .invoke('trigger', 'mouseleave') .should('not.have.class', 'star-hover'); }); -}); \ No newline at end of file +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 7f0afdf035..1964b96d70 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -275,7 +275,7 @@ Cypress.Commands.add('get_open_dialog', () => { }); Cypress.Commands.add('hide_dialog', () => { - cy.wait(200); + cy.wait(300); cy.get_open_dialog().find('.btn-modal-close').click(); cy.get('.modal:visible').should('not.exist'); }); @@ -312,7 +312,6 @@ Cypress.Commands.add('add_filter', () => { cy.get('.filter-section .filter-button').click(); cy.wait(300); cy.get('.filter-popover').should('exist'); - cy.get('.filter-popover').find('.add-filter').click(); }); Cypress.Commands.add('clear_filters', () => { diff --git a/frappe/app.py b/frappe/app.py index adf2bfa8c9..29ef69ef2d 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -181,6 +181,9 @@ def make_form_dict(request): else: args = request.form or request.args + if not isinstance(args, dict): + frappe.throw("Invalid request arguments") + try: frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \ for k, v in iteritems(args) }) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 830af68de7..281e699640 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -15,6 +15,8 @@ from frappe.model.document import Document from frappe.core.doctype.communication.email import make from frappe.utils.background_jobs import get_jobs from frappe.automation.doctype.assignment_rule.assignment_rule import get_repeated +from frappe.contacts.doctype.contact.contact import get_contacts_linked_from +from frappe.contacts.doctype.contact.contact import get_contacts_linking_to month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} week_map = {'Monday': 0, 'Tuesday': 1, 'Wednesday': 2, 'Thursday': 3, 'Friday': 4, 'Saturday': 5, 'Sunday': 6} @@ -328,13 +330,8 @@ class AutoRepeat(Document): def fetch_linked_contacts(self): if self.reference_doctype and self.reference_document: - res = frappe.db.get_all('Contact', - fields=['email_id'], - filters=[ - ['Dynamic Link', 'link_doctype', '=', self.reference_doctype], - ['Dynamic Link', 'link_name', '=', self.reference_document] - ]) - + res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) + res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) email_ids = list(set([d.email_id for d in res])) if not email_ids: frappe.msgprint(_('No contacts linked to document'), alert=True) diff --git a/frappe/boot.py b/frappe/boot.py index 8cf75e02bb..0dfcb8d1b4 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -21,7 +21,7 @@ from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabl from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links -from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings +from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo def get_bootinfo(): """build and return boot info""" @@ -62,6 +62,7 @@ def get_bootinfo(): doclist.extend(get_meta_bundle("Page")) bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) bootinfo.navbar_settings = get_navbar_settings() + bootinfo.notification_settings = get_notification_settings() # ipinfo if frappe.session.data.get('ipinfo'): @@ -90,6 +91,7 @@ def get_bootinfo(): bootinfo.link_preview_doctypes = get_link_preview_doctypes() bootinfo.additional_filters_config = get_additional_filters_from_hooks() bootinfo.desk_settings = get_desk_settings() + bootinfo.app_logo_url = get_app_logo() return bootinfo @@ -323,4 +325,7 @@ def get_desk_settings(): for key in desk_properties: desk_settings[key] = desk_settings.get(key) or role.get(key) - return desk_settings \ No newline at end of file + return desk_settings + +def get_notification_settings(): + return frappe.get_cached_doc('Notification Settings', frappe.session.user) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 987ba7d3d6..42fa039f74 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -256,3 +256,27 @@ def get_contact_with_phone_number(number): def get_contact_name(email_id): contact = frappe.get_list("Contact Email", filters={"email_id": email_id}, fields=["parent"], limit=1) return contact[0].parent if contact else None + +def get_contacts_linking_to(doctype, docname, fields=None): + """Return a list of contacts containing a link to the given document.""" + return frappe.get_list('Contact', fields=fields, filters=[ + ['Dynamic Link', 'link_doctype', '=', doctype], + ['Dynamic Link', 'link_name', '=', docname] + ]) + +def get_contacts_linked_from(doctype, docname, fields=None): + """Return a list of contacts that are contained in (linked from) the given document.""" + link_fields = frappe.get_meta(doctype).get('fields', { + 'fieldtype': 'Link', + 'options': 'Contact' + }) + if not link_fields: + return [] + + contact_names = frappe.get_value(doctype, docname, fieldname=[f.fieldname for f in link_fields]) + if not contact_names: + return [] + + return frappe.get_list('Contact', fields=fields, filters={ + 'name': ('in', contact_names) + }) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 8a9090bc37..cbcfa350f5 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -656,7 +656,7 @@ class DocType(Document): flags = {"flags": re.ASCII} if six.PY3 else {} # a DocType name should not start or end with an empty space - if re.match("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags): + if re.search("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags): frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError) # a DocType's name should not start with a number or underscore diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index db510981a4..2244bc9e4e 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -25,7 +25,7 @@ class NavbarSettings(Document): @frappe.whitelist(allow_guest=True) def get_app_logo(): - app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo') + app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo', cache=True) if not app_logo: app_logo = frappe.get_hooks('app_logo_url')[-1] diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 0d6aa3d7d1..e02d9e5db0 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -3,6 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals +from typing import Dict, List import frappe, json from frappe.model.document import Document @@ -11,12 +12,13 @@ from datetime import datetime from croniter import croniter from frappe.utils.background_jobs import enqueue, get_jobs + class ScheduledJobType(Document): def autoname(self): - self.name = '.'.join(self.method.split('.')[-2:]) + self.name = ".".join(self.method.split(".")[-2:]) def validate(self): - if self.frequency != 'All': + if self.frequency != "All": # force logging for all events other than continuous ones (ALL) self.create_log = 1 @@ -84,7 +86,7 @@ class ScheduledJobType(Document): def log_status(self, status): # log file - frappe.logger("scheduler").info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site)) + frappe.logger("scheduler").info(f"Scheduled Job {status}: {self.method} for {frappe.local.site}") self.update_scheduler_log(status) def update_scheduler_log(self, status): @@ -111,28 +113,28 @@ class ScheduledJobType(Document): @frappe.whitelist() -def execute_event(doc): - frappe.only_for('System Manager') +def execute_event(doc: str): + frappe.only_for("System Manager") doc = json.loads(doc) - frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue(force=True) + frappe.get_doc("Scheduled Job Type", doc.get("name")).enqueue(force=True) -def run_scheduled_job(job_type): - '''This is a wrapper function that runs a hooks.scheduler_events method''' +def run_scheduled_job(job_type: str): + """This is a wrapper function that runs a hooks.scheduler_events method""" try: - frappe.get_doc('Scheduled Job Type', dict(method=job_type)).execute() + frappe.get_doc("Scheduled Job Type", dict(method=job_type)).execute() except Exception: print(frappe.get_traceback()) -def sync_jobs(hooks=None): +def sync_jobs(hooks: Dict = None): frappe.reload_doc("core", "doctype", "scheduled_job_type") scheduler_events = hooks or frappe.get_hooks("scheduler_events") all_events = insert_events(scheduler_events) clear_events(all_events) -def insert_events(scheduler_events): +def insert_events(scheduler_events: Dict) -> List: cron_jobs, event_jobs = [], [] for event_type in scheduler_events: events = scheduler_events.get(event_type) @@ -144,7 +146,7 @@ def insert_events(scheduler_events): return cron_jobs + event_jobs -def insert_cron_jobs(events): +def insert_cron_jobs(events: Dict) -> List: cron_jobs = [] for cron_format in events: for event in events.get(cron_format): @@ -153,25 +155,29 @@ def insert_cron_jobs(events): return cron_jobs -def insert_event_jobs(events, event_type): +def insert_event_jobs(events: List, event_type: str) -> List: event_jobs = [] for event in events: event_jobs.append(event) - frequency = event_type.replace('_', ' ').title() + frequency = event_type.replace("_", " ").title() insert_single_event(frequency, event) return event_jobs -def insert_single_event(frequency, event, cron_format=None): +def insert_single_event(frequency: str, event: str, cron_format: str = None): cron_expr = {"cron_format": cron_format} if cron_format else {} - doc = frappe.get_doc({ - "doctype": "Scheduled Job Type", - "method": event, - "cron_format": cron_format, - "frequency": frequency - }) + doc = frappe.get_doc( + { + "doctype": "Scheduled Job Type", + "method": event, + "cron_format": cron_format, + "frequency": frequency, + } + ) - if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }): + if not frappe.db.exists( + "Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr} + ): try: doc.insert() except frappe.DuplicateEntryError: @@ -179,7 +185,12 @@ def insert_single_event(frequency, event, cron_format=None): doc.insert() -def clear_events(all_events): - for event in frappe.get_all("Scheduled Job Type", ("name", "method")): - if event.method not in all_events: +def clear_events(all_events: List): + for event in frappe.get_all( + "Scheduled Job Type", fields=["name", "method", "server_script"] + ): + is_server_script = event.server_script + is_defined_in_hooks = event.method in all_events + + if not (is_defined_in_hooks or is_server_script): frappe.delete_doc("Scheduled Job Type", event.name) diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js index a317d69166..95a63780f8 100644 --- a/frappe/core/doctype/server_script/server_script.js +++ b/frappe/core/doctype/server_script/server_script.js @@ -6,46 +6,11 @@ frappe.ui.form.on('Server Script', { frm.trigger('setup_help'); }, refresh: function(frm) { - if (frm.doc.script_type === 'Scheduler Event' && !frm.doc.disabled) { - frm.add_custom_button('Schedule Script', function() { - var d = new frappe.ui.Dialog({ - title: "Schedule Script Execution", - fields: [ - { - fieldname: "event_type", - label: __('Select Event Type'), - fieldtype: "Select", - options: "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long" - }, - ], - primary_action_label: __('Schedule Script'), - primary_action: () => { - d.get_primary_btn().attr('disabled', true); - var data = d.get_values(); - d.hide(); - if(data) { - frm.events.schedule_script(frm, data); - } - - } - }); - - d.show(); - - }); + if (frm.doc.script_type != 'Scheduler Event') { + frm.dashboard.hide(); } }, - schedule_script(frm, data) { - frm.call({ - method: "frappe.core.doctype.server_script.server_script.setup_scheduler_events", - args: { - 'script_name': frm.doc.name, - 'frequency': data.event_type - } - }); - }, - setup_help(frm) { frm.get_field('help_html').html(`

DocType Event

diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 9aa7b5afe5..b7e49673f8 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -8,6 +8,7 @@ "field_order": [ "script_type", "reference_doctype", + "event_frequency", "doctype_event", "api_method", "allow_guest", @@ -84,11 +85,24 @@ { "fieldname": "help_html", "fieldtype": "HTML" + }, + { + "depends_on": "eval:doc.script_type == \"Scheduler Event\"", + "fieldname": "event_frequency", + "fieldtype": "Select", + "label": "Event Frequency", + "mandatory_depends_on": "eval:doc.script_type == \"Scheduler Event\"", + "options": "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long" } ], "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-01-03 18:50:14.767595", + "links": [ + { + "link_doctype": "Scheduled Job Type", + "link_fieldname": "server_script" + } + ], + "modified": "2021-02-18 12:36:19.803425", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 88d68dba14..8838d9e954 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import ast +from typing import Dict, List import frappe from frappe.model.document import Document @@ -14,67 +15,146 @@ from frappe import _ class ServerScript(Document): def validate(self): - frappe.only_for('Script Manager', True) + frappe.only_for("Script Manager", True) + self.validate_script() + self.sync_scheduled_jobs() + self.clear_scheduled_events() + + def on_update(self): + frappe.cache().delete_value("server_script_map") + self.sync_scheduler_events() + + def on_trash(self): + if self.script_type == "Scheduler Event": + for job in self.scheduled_jobs: + frappe.delete_doc("Scheduled Job Type", job.name) + + @property + def scheduled_jobs(self) -> List[Dict[str, str]]: + return frappe.get_all( + "Scheduled Job Type", + filters={"server_script": self.name}, + fields=["name", "stopped"], + ) + + def validate_script(self): + """Utilizes the ast module to check for syntax errors + """ ast.parse(self.script) - @staticmethod - def on_update(): - frappe.cache().delete_value('server_script_map') + def sync_scheduled_jobs(self): + """Sync Scheduled Job Type statuses if Server Script's disabled status is changed + """ + if self.script_type != "Scheduler Event" or not self.has_value_changed("disabled"): + return - def execute_method(self): - if self.script_type == 'API': - # validate if guest is allowed - if frappe.session.user == 'Guest' and not self.allow_guest: - raise frappe.PermissionError - _globals, _locals = safe_exec(self.script) - return _globals.frappe.flags # output can be stored in flags - else: - # wrong report type! + for scheduled_job in self.scheduled_jobs: + if bool(scheduled_job.stopped) != bool(self.disabled): + job = frappe.get_doc("Scheduled Job Type", scheduled_job.name) + job.stopped = self.disabled + job.save() + + def sync_scheduler_events(self): + """Create or update Scheduled Job Type documents for Scheduler Event Server Scripts + """ + if not self.disabled and self.event_frequency and self.script_type == "Scheduler Event": + setup_scheduler_events(script_name=self.name, frequency=self.event_frequency) + + def clear_scheduled_events(self): + """Deletes existing scheduled jobs by Server Script if self.event_frequency has changed + """ + if self.script_type == "Scheduler Event" and self.has_value_changed("event_frequency"): + for scheduled_job in self.scheduled_jobs: + frappe.delete_doc("Scheduled Job Type", scheduled_job.name) + + def execute_method(self) -> Dict: + """Specific to API endpoint Server Scripts + + Raises: + frappe.DoesNotExistError: If self.script_type is not API + frappe.PermissionError: If self.allow_guest is unset for API accessed by Guest user + + Returns: + dict: Evaluates self.script with frappe.utils.safe_exec.safe_exec and returns the flags set in it's safe globals + """ + # wrong report type! + if self.script_type != "API": raise frappe.DoesNotExistError - def execute_doc(self, doc): - # execute event - safe_exec(self.script, None, dict(doc = doc)) + # validate if guest is allowed + if frappe.session.user == "Guest" and not self.allow_guest: + raise frappe.PermissionError + + # output can be stored in flags + _globals, _locals = safe_exec(self.script) + return _globals.frappe.flags + + def execute_doc(self, doc: Document): + """Specific to Document Event triggered Server Scripts + + Args: + doc (Document): Executes script with for a certain document's events + """ + safe_exec(self.script, _locals={"doc": doc}) def execute_scheduled_method(self): - if self.script_type == 'Scheduler Event': - safe_exec(self.script) - else: - # wrong report type! + """Specific to Scheduled Jobs via Server Scripts + + Raises: + frappe.DoesNotExistError: If script type is not a scheduler event + """ + if self.script_type != "Scheduler Event": raise frappe.DoesNotExistError - def get_permission_query_conditions(self, user): + safe_exec(self.script) + + def get_permission_query_conditions(self, user: str) -> List[str]: + """Specific to Permission Query Server Scripts + + Args: + user (str): Takes user email to execute script and return list of conditions + + Returns: + list: Returns list of conditions defined by rules in self.script + """ locals = {"user": user, "conditions": ""} safe_exec(self.script, None, locals) if locals["conditions"]: return locals["conditions"] + @frappe.whitelist() def setup_scheduler_events(script_name, frequency): - method = frappe.scrub('{0}-{1}'.format(script_name, frequency)) - scheduled_script = frappe.db.get_value('Scheduled Job Type', - dict(method=method)) + """Creates or Updates Scheduled Job Type documents based on the specified script name and frequency + + Args: + script_name (str): Name of the Server Script document + frequency (str): Event label compatible with the Frappe scheduler + """ + method = frappe.scrub(f"{script_name}-{frequency}") + scheduled_script = frappe.db.get_value("Scheduled Job Type", {"method": method}) if not scheduled_script: - doc = frappe.get_doc(dict( - doctype = 'Scheduled Job Type', - method = method, - frequency = frequency, - server_script = script_name - )) + frappe.get_doc( + { + "doctype": "Scheduled Job Type", + "method": method, + "frequency": frequency, + "server_script": script_name, + } + ).insert() - doc.insert() - - frappe.msgprint(_('Enabled scheduled execution for script {0}').format(script_name)) + frappe.msgprint(_("Enabled scheduled execution for script {0}").format(script_name)) else: - doc = frappe.get_doc('Scheduled Job Type', scheduled_script) - doc.update(dict( - doctype = 'Scheduled Job Type', - method = method, - frequency = frequency, - server_script = script_name - )) + doc = frappe.get_doc("Scheduled Job Type", scheduled_script) + + if doc.frequency == frequency: + return + + doc.frequency = frequency doc.save() - frappe.msgprint(_('Scheduled execution for script {0} has updated').format(script_name)) + frappe.msgprint( + _("Scheduled execution for script {0} has updated").format(script_name) + ) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 77b61f22bb..d16db5fecd 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -2,7 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, unittest +import frappe, unittest, uuid from frappe.model.delete_doc import delete_doc from frappe.utils.data import today, add_to_date @@ -240,6 +240,29 @@ class TestUser(unittest.TestCase): self.assertRaises(frappe.ValidationError, user.reset_password, False) + def test_user_rollback(self): + """ """ + frappe.db.commit() + frappe.db.begin() + user_id = str(uuid.uuid4()) + email = f'{user_id}@example.com' + try: + frappe.flags.in_import = True # disable throttling + frappe.get_doc(dict( + doctype='User', + email=email, + first_name=user_id, + )).insert() + finally: + frappe.flags.in_import = False + + # Check user has been added + self.assertIsNotNone(frappe.db.get("User", {"email": email})) + + # Check that rollback works + frappe.db.rollback() + self.assertIsNone(frappe.db.get("User", {"email": email})) + def delete_contact(user): frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 99d1deeb03..747ace5de6 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -302,7 +302,7 @@ "no_copy": 1 }, { - "default": "0", + "default": "1", "fieldname": "logout_all_sessions", "fieldtype": "Check", "label": "Logout From All Devices After Changing Password" @@ -669,7 +669,7 @@ } ], "max_attachments": 5, - "modified": "2021-01-02 11:21:50.507786", + "modified": "2021-02-01 16:11:06.037543", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index f1ad41db6c..a655e9e1da 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -17,6 +17,10 @@ class KanbanBoard(Document): def on_update(self): frappe.clear_cache(doctype=self.reference_doctype) + def before_insert(self): + for column in self.columns: + column.order = get_order_for_column(self, column.column_name) + def validate_column_name(self): for column in self.columns: if not column.column_name: @@ -125,6 +129,53 @@ def update_order(board_name, order): board.save() return board, updated_cards +@frappe.whitelist() +def update_order_for_single_card(board_name, docname, from_colname, to_colname, old_index, new_index): + '''Save the order of cards in columns''' + board = frappe.get_doc('Kanban Board', board_name) + doctype = board.reference_doctype + fieldname = board.field_name + old_index = frappe.parse_json(old_index) + new_index = frappe.parse_json(new_index) + + # save current order and index of columns to be updated + from_col_order, from_col_idx = get_kanban_column_order_and_index(board, from_colname) + to_col_order, to_col_idx = get_kanban_column_order_and_index(board, to_colname) + + if from_colname == to_colname: + from_col_order = to_col_order + + to_col_order.insert(new_index, from_col_order.pop((old_index))) + + # save updated order + board.columns[from_col_idx].order = frappe.as_json(from_col_order) + board.columns[to_col_idx].order = frappe.as_json(to_col_order) + board.save() + + # update changed value in doc + frappe.set_value(doctype, docname, fieldname, to_colname) + + return board + +def get_kanban_column_order_and_index(board, colname): + for i, col in enumerate(board.columns): + if col.column_name == colname: + col_order = frappe.parse_json(col.order) + col_idx = i + + return col_order, col_idx + +@frappe.whitelist() +def add_card(board_name, docname, colname): + board = frappe.get_doc('Kanban Board', board_name) + + col_order, col_idx = get_kanban_column_order_and_index(board, colname) + col_order.insert(0, docname) + + board.columns[col_idx].order = frappe.as_json(col_order) + + board.save() + return board @frappe.whitelist() def quick_kanban_board(doctype, board_name, field_name, project=None): @@ -133,6 +184,13 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): doc = frappe.new_doc('Kanban Board') meta = frappe.get_meta(doctype) + doc.kanban_board_name = board_name + doc.reference_doctype = doctype + doc.field_name = field_name + + if project: + doc.filters = '[["Task","project","=","{0}"]]'.format(project) + options = '' for field in meta.fields: if field.fieldname == field_name: @@ -149,12 +207,6 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): column_name=column )) - doc.kanban_board_name = board_name - doc.reference_doctype = doctype - doc.field_name = field_name - - if project: - doc.filters = '[["Task","project","=","{0}"]]'.format(project) if doctype in ['Note', 'ToDo']: doc.private = 1 @@ -162,6 +214,12 @@ def quick_kanban_board(doctype, board_name, field_name, project=None): doc.save() return doc +def get_order_for_column(board, colname): + filters = [[board.reference_doctype, board.field_name, '=', colname]] + if board.filters: + filters.append(frappe.parse_json(board.filters)[0]) + + return frappe.as_json(frappe.get_list(board.reference_doctype, filters=filters, pluck='name')) @frappe.whitelist() def update_column_order(board_name, order): diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 9b124cd6f4..34726bdf8a 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -42,7 +42,6 @@ def create_notification_settings(user): _doc = frappe.new_doc('Notification Settings') _doc.name = user _doc.insert(ignore_permissions=True) - frappe.db.commit() @frappe.whitelist() diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 6bddd09fc7..7d1a697f6b 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -86,7 +86,7 @@ def get_result(doc, filters, to_date=None): filters = frappe.parse_json(filters) if not filters: - filters = [] + filters = [] if to_date: filters.append([doc.document_type, 'creation', '<', to_date]) @@ -107,9 +107,13 @@ def get_percentage_difference(doc, filters, result): return previous_result = calculate_previous_result(doc, filters) - difference = (result - previous_result)/100.0 - - return difference + if previous_result == 0: + return None + else: + if result == previous_result: + return 0 + else: + return ((result/previous_result)-1)*100.0 def calculate_previous_result(doc, filters): @@ -197,4 +201,4 @@ def add_card_to_dashboard(args): card.save() dashboard.append('cards', dashboard_link) - dashboard.save() \ No newline at end of file + dashboard.save() diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index c39e7f52c0..f44a57e339 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -201,7 +201,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.abort_setup(r.message.fail); } }, - error: this.abort_setup("Error in setup", true) + error: () => this.abort_setup("Error in setup") }); } diff --git a/frappe/desk/page/user_profile/user_profile_controller.js b/frappe/desk/page/user_profile/user_profile_controller.js index 61f8ec3c06..c1a89f316e 100644 --- a/frappe/desk/page/user_profile/user_profile_controller.js +++ b/frappe/desk/page/user_profile/user_profile_controller.js @@ -151,32 +151,30 @@ class UserProfile { // eslint-disable-next-line no-unused-vars render_percentage_chart(field, title) { - // REDESIGN-TODO: chart seems to be broken. Enable this once fixed. - this.wrapper.find('.percentage-chart-container').hide(); - // frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', { - // user: this.user_id, - // field: field - // }).then(chart => { - // if (chart.labels.length) { - // this.percentage_chart = new frappe.Chart('.performance-percentage-chart', { - // type: 'percentage', - // data: { - // labels: chart.labels, - // datasets: chart.datasets - // }, - // truncateLegends: 1, - // barOptions: { - // height: 11, - // depth: 1 - // }, - // height: 200, - // maxSlices: 8, - // colors: ['purple', 'blue', 'cyan', 'teal', 'pink', 'red', 'orange', 'yellow'], - // }); - // } else { - // this.wrapper.find('.percentage-chart-container').hide(); - // } - // }); + frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', { + user: this.user_id, + field: field + }).then(chart => { + if (chart.labels.length) { + this.percentage_chart = new frappe.Chart('.performance-percentage-chart', { + type: 'percentage', + data: { + labels: chart.labels, + datasets: chart.datasets + }, + truncateLegends: 1, + barOptions: { + height: 11, + depth: 1 + }, + height: 200, + maxSlices: 8, + colors: ['purple', 'blue', 'cyan', 'teal', 'pink', 'red', 'orange', 'yellow'], + }); + } else { + this.wrapper.find('.percentage-chart-container').hide(); + } + }); } create_line_chart_filters() { @@ -452,4 +450,4 @@ class UserProfileTimeline extends BaseTimeline { } frappe.provide('frappe.ui'); -frappe.ui.UserProfile = UserProfile; \ No newline at end of file +frappe.ui.UserProfile = UserProfile; diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index 9b0b5e41d7..3fcabb9495 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -17,7 +17,7 @@ class TestEmailBody(unittest.TestCase):

Hey John Doe!

This is embedded image you asked for

- +
''' email_text = ''' @@ -25,7 +25,7 @@ Hey John Doe! This is the text version of this email ''' - img_path = os.path.abspath('assets/frappe/images/favicon.png') + img_path = os.path.abspath('assets/frappe/images/frappe-favicon.svg') with open(img_path, 'rb') as f: img_content = f.read() img_base64 = base64.b64encode(img_content).decode() @@ -77,12 +77,11 @@ This is the text version of this email def test_image(self): img_signature = ''' -Content-Type: image/png +Content-Type: image/svg+xml MIME-Version: 1.0 Content-Transfer-Encoding: base64 -Content-Disposition: inline; filename="favicon.png" +Content-Disposition: inline; filename="frappe-favicon.svg" ''' - self.assertTrue(img_signature in self.email_string) self.assertTrue(self.img_base64 in self.email_string) @@ -117,7 +116,7 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> def test_replace_filename_with_cid(self): original_message = '''
- test + test
''' diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json index e4c4e278b0..1e0ae161bc 100644 --- a/frappe/geo/country_info.json +++ b/frappe/geo/country_info.json @@ -2729,11 +2729,11 @@ }, "Zimbabwe": { "code": "zw", - "currency": "ZWD", - "currency_fraction": "Thebe", + "currency": "ZWL", + "currency_fraction": "Cent", "currency_fraction_units": 100, "currency_name": "Zimbabwe Dollar", - "currency_symbol": "P", + "currency_symbol": "ZWL$", "number_format": "# ###.##", "timezones": [ "Africa/Harare" diff --git a/frappe/hooks.py b/frappe/hooks.py index 97a8b70953..3e206f0ad3 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -58,6 +58,11 @@ website_route_rules = [ {"from_route": "/kb/", "to_route": "Help Article"}, {"from_route": "/newsletters", "to_route": "Newsletter"}, {"from_route": "/profile", "to_route": "me"}, + {"from_route": "/app/", "to_route": "app"}, +] + +website_redirects = [ + {"source": r"/desk(.*)", "target": r"/app\1"}, ] base_template = "templates/base.html" diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py index 1092c3240e..d84e6ef11d 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/social_login_key.py @@ -124,7 +124,7 @@ class SocialLoginKey(Document): "provider_name": "Frappe", "enable_social_login": 1, "custom_base_url": 1, - "icon":"/assets/frappe/images/favicon.png", + "icon":"/assets/frappe/images/frappe-favicon.svg", "redirect_url": "/api/method/frappe.www.login.login_via_frappe", "api_endpoint": "/api/method/frappe.integrations.oauth2.openid_profile", "api_endpoint_args":None, diff --git a/frappe/model/document.py b/frappe/model/document.py index 1cd981ead8..3ecc335cdd 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1015,6 +1015,8 @@ class Document(BaseDocument): def notify_update(self): """Publish realtime that the current document is modified""" + if frappe.flags.in_patch: return + frappe.publish_realtime("doc_update", {"modified": self.modified, "doctype": self.doctype, "name": self.name}, doctype=self.doctype, docname=self.name, after_commit=True) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index e04d3d56b9..61983d322c 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -61,7 +61,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe for module_name in frappe.local.app_modules.get(app_name) or []: folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__) - get_doc_files(files, folder, force, sync_everything, verbose=verbose) + get_doc_files(files, folder) l = len(files) if l: @@ -77,7 +77,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe # print each progress bar on new line print() -def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=False): +def get_doc_files(files, start_path): """walk and sync all doctypes and pages""" # load in sequence - warning for devs diff --git a/frappe/patches.txt b/frappe/patches.txt index 03f7ea71a1..5400c96354 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -209,7 +209,7 @@ frappe.patches.v9_1.resave_domain_settings frappe.patches.v9_1.revert_domain_settings frappe.patches.v9_1.move_feed_to_activity_log execute:frappe.delete_doc('Page', 'data-import-tool', ignore_missing=True) -frappe.patches.v10_0.reload_countries_and_currencies # 14-10-2020 +frappe.patches.v10_0.reload_countries_and_currencies # 2021-02-03 frappe.patches.v10_0.refactor_social_login_keys frappe.patches.v10_0.enable_chat_by_default_within_system_settings frappe.patches.v10_0.remove_custom_field_for_disabled_domain @@ -329,4 +329,5 @@ frappe.core.doctype.page.patches.drop_unused_pages execute:frappe.get_doc('Role', 'Guest').save() # remove desk access frappe.patches.v13_0.rename_desk_page_to_workspace # 02.02.2021 frappe.patches.v13_0.delete_package_publish_tool +frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings frappe.patches.v13_0.rename_custom_client_script diff --git a/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py new file mode 100644 index 0000000000..fcf8afc826 --- /dev/null +++ b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + + +def execute(): + if frappe.db.table_exists('List View Setting'): + existing_list_view_settings = frappe.get_all('List View Settings', as_list=True) + for list_view_setting in frappe.get_all('List View Setting', fields = ['disable_count', 'disable_sidebar_stats', 'disable_auto_refresh', 'name']): + name = list_view_setting.pop('name') + if name not in [x[0] for x in existing_list_view_settings]: + list_view_setting['doctype'] = 'List View Settings' + list_view_settings = frappe.get_doc(list_view_setting) + # setting name here is necessary because autoname is set as prompt + list_view_settings.name = name + list_view_settings.insert() + frappe.delete_doc("DocType", "List View Setting", force=True) + frappe.db.commit() diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py index 491d959755..3a3b14faad 100644 --- a/frappe/printing/doctype/letter_head/letter_head.py +++ b/frappe/printing/doctype/letter_head/letter_head.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import is_image from frappe.model.document import Document +from frappe import _ class LetterHead(Document): def before_insert(self): @@ -13,14 +14,20 @@ class LetterHead(Document): def validate(self): self.set_image() - if not self.is_default: - if not frappe.db.sql("""select count(*) from `tabLetter Head` where ifnull(is_default,0)=1"""): + self.validate_disabled_and_default() + + def validate_disabled_and_default(self): + if self.disabled and self.is_default: + frappe.throw(_("Letter Head cannot be both disabled and default")) + + if not self.is_default and not self.disabled: + if not frappe.db.exists('Letter Head', dict(is_default=1)): self.is_default = 1 def set_image(self): if self.source=='Image': if self.image and is_image(self.image): - self.content = ''.format(self.image) + self.content = ''.format(self.image) frappe.msgprint(frappe._('Header HTML set from attachment {0}').format(self.image), alert = True) else: frappe.msgprint(frappe._('Please attach an image file to set HTML'), alert = True, indicator = 'orange') diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index 7e1db1eddb..0ae8786e95 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -412,6 +412,12 @@ frappe.ui.form.PrintView = class { ` ); + if (frappe.utils.is_rtl(this.lang_code)) { + this.$print_format_body.find('head').append( + `` + ); + } + this.$print_format_body.find('body').html( `` ); diff --git a/frappe/public/build.json b/frappe/public/build.json index 35e9d62436..73b8bc60fe 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -27,8 +27,6 @@ "public/js/frappe/microtemplate.js", "public/js/frappe/query_string.js", - "public/js/frappe/ui/dropzone.js", - "public/js/frappe/ui/upload.html", "public/js/frappe/upload.js", "public/js/frappe/model/meta.js", @@ -36,7 +34,6 @@ "public/js/frappe/model/perm.js", "website/js/website.js", - "public/js/frappe/utils/rating_icons.html", "public/js/frappe/socketio_client.js" ], "js/bootstrap-4-web.min.js": "website/js/bootstrap-4.js", @@ -62,7 +59,6 @@ ], "js/dialog.min.js": [ "public/js/frappe/dom.js", - "public/js/frappe/form/formatters.js", "public/js/frappe/form/layout.js", "public/js/frappe/ui/field_group.js", @@ -79,8 +75,6 @@ "public/css/octicons/octicons.css", "public/less/desk.less", "public/less/module.less", - "public/less/link_preview.less", - "public/less/form.less", "public/less/mobile.less", "public/less/controls.less", "public/less/chat.less", @@ -99,22 +93,21 @@ "public/scss/print.scss" ], "concat:js/libs.min.js": [ - "public/js/lib/awesomplete/awesomplete.min.js", "public/js/lib/Sortable.min.js", "public/js/lib/jquery/jquery.hotkeys.js", "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js", "node_modules/vue/dist/vue.min.js", "node_modules/moment/min/moment-with-locales.min.js", "node_modules/moment-timezone/builds/moment-timezone-with-data.min.js", - "public/js/lib/socket.io.min.js", + "node_modules/socket.io-client/dist/socket.io.slim.js", "public/js/lib/jSignature.min.js", - "public/js/frappe/translate.js", "public/js/lib/leaflet/leaflet.js", "public/js/lib/leaflet/leaflet.draw.js", "public/js/lib/leaflet/L.Control.Locate.js", "public/js/lib/leaflet/easy-button.js" ], "js/desk.min.js": [ + "public/js/frappe/translate.js", "public/js/frappe/class.js", "public/js/frappe/polyfill.js", "public/js/frappe/provide.js", @@ -152,7 +145,6 @@ "public/js/frappe/ui/dialog.js", "public/js/frappe/ui/capture.js", "public/js/frappe/ui/app_icon.js", - "public/js/frappe/ui/dropzone.js", "public/js/frappe/ui/theme_switcher.js", "public/js/frappe/model/model.js", @@ -179,7 +171,6 @@ "public/js/frappe/utils/preview_email.js", "public/js/frappe/utils/file_manager.js", - "public/js/frappe/ui/upload.html", "public/js/frappe/upload.js", "public/js/frappe/ui/tree.js", @@ -194,7 +185,6 @@ "public/js/frappe/ui/toolbar/search.js", "public/js/frappe/ui/toolbar/tag_utils.js", "public/js/frappe/ui/toolbar/search.html", - "public/js/frappe/ui/toolbar/search_header.html", "public/js/frappe/ui/toolbar/search_utils.js", "public/js/frappe/ui/toolbar/about.js", "public/js/frappe/ui/toolbar/navbar.html", @@ -210,11 +200,11 @@ "public/js/frappe/ui/sort_selector.js", "public/js/frappe/change_log.html", + "public/js/frappe/ui/workspace_loading_skeleton.html", "public/js/frappe/desk.js", "public/js/frappe/query_string.js", "public/js/frappe/ui/comment.js", - "public/js/frappe/utils/rating_icons.html", "public/js/frappe/chat.js", "public/js/frappe/utils/energy_point_utils.js", @@ -232,7 +222,6 @@ "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" ], - "css/list.min.css": "public/less/gantt.less", "js/list.min.js": [ "public/js/frappe/ui/listing.html", @@ -299,11 +288,12 @@ "css/web_form.css": [ "website/css/web_form.css", "public/css/octicons/octicons.css", - "public/less/controls.less", + "public/scss/controls.scss", "node_modules/frappe-datatable/dist/frappe-datatable.css" ], "css/email.css": "public/scss/email.scss", "js/barcode_scanner.min.js": "public/js/frappe/barcode_scanner/quagga.js", "js/user_profile_controller.min.js": "desk/page/user_profile/user_profile_controller.js", - "css/login.css": "public/scss/login.scss" + "css/login.css": "public/scss/login.scss", + "js/data_import_tools.min.js": "public/js/frappe/data_import/index.js" } diff --git a/frappe/public/css/common.css b/frappe/public/css/common.css deleted file mode 100644 index b08be904a6..0000000000 --- a/frappe/public/css/common.css +++ /dev/null @@ -1,253 +0,0 @@ -/* the element that this class is applied to, should have a max width for this to work*/ -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -} -a { - cursor: pointer; -} -a, -a:hover, -a:active, -a:focus, -.btn, -.btn:hover, -.btn:active, -.btn:focus { - outline: 0; -} -img { - max-width: 100%; -} -p { - margin: 10px 0px; -} -.text-color { - color: #36414C !important; -} -.text-muted { - color: #8D99A6 !important; -} -.text-extra-muted { - color: #d1d8dd !important; -} -a, -.badge { - -webkit-transition: 0.2s; - -o-transition: 0.2s; - transition: 0.2s; -} -.btn { - -webkit-transition: background-color 0.2s; - -o-transition: background-color 0.2s; - transition: background-color 0.2s; -} -a.disabled, -a.disabled:hover { - color: #888; - cursor: default; - text-decoration: none; -} -a.grey, -.sidebar-section a, -.control-value a, -.data-row a { - text-decoration: none; -} -a.grey:hover, -.sidebar-section a:hover, -.control-value a:hover, -.data-row a:hover, -a.grey:focus, -.sidebar-section a:focus, -.control-value a:focus, -.data-row a:focus { - text-decoration: underline; -} -a.text-muted, -a.text-extra-muted { - text-decoration: none; -} -.underline { - text-decoration: underline; -} -.inline-block { - display: inline-block; -} -.bold, -.strong { - font-weight: bold; -} -kbd { - color: inherit; - background-color: #F0F4F7; -} -.btn [class^="fa fa-"], -.nav [class^="fa fa-"], -.btn [class*="fa fa-"], -.nav [class*="fa fa-"] { - display: inline-block; -} -.dropdown-menu > li > a { - padding: 14px; - white-space: normal; -} -.dropdown-menu { - min-width: 200px; - padding: 0px; - font-size: 12px; - max-height: 400px; - overflow: auto; - border-radius: 0px 0px 4px 4px; -} -.dropdown-menu .dropdown-header { - padding: 3px 14px; - font-size: 11px; - font-weight: 200; - padding-top: 12px; -} -.dropdown-menu .divider { - margin: 0px; -} -a.badge-hover:hover .badge, -a.badge-hover:focus .badge, -a.badge-hover:active .badge { - background-color: #D8DFE5; -} -.msgprint { - word-wrap: break-word; -} -.msgprint pre { - text-align: left; -} -.centered { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); -} -.border-top { - border-top: 1px solid #d1d8dd; -} -.border-bottom { - border-bottom: 1px solid #d1d8dd; -} -.border-left { - border-left: 1px solid #d1d8dd; -} -.border-right { - border-right: 1px solid #d1d8dd; -} -.border { - border: 1px solid #d1d8dd; -} -.close-inline { - font-size: 120%; - font-weight: bold; - line-height: 1; - cursor: pointer; - color: inherit; - display: inline-block; -} -.close-inline:hover, -.close-inline:focus { - text-decoration: none; -} -.middle { - vertical-align: middle; -} -.full-center-container { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} -.full-center { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); -} -#freeze { - z-index: 1020; - bottom: 0px; - opacity: 0; - background-color: #fafbfc; -} -#freeze .freeze-message-container { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} -#freeze .freeze-message { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - text-align: center; - color: #36414C !important; -} -#freeze.dark { - background-color: #334143; -} -#freeze.in { - opacity: 0.5; -} -a.no-decoration { - text-decoration: none; - color: inherit; -} -a.no-decoration:hover, -a.no-decoration:focus, -a.no-decoration:active { - text-decoration: none; - color: inherit; -} -.padding { - padding: 15px; -} -.margin { - margin: 15px; -} -.margin-top { - margin-top: 15px; -} -.margin-bottom { - margin-bottom: 15px; -} -.margin-left { - margin-left: 15px; -} -.margin-right { - margin-right: 15px; -} -@media (max-width: 767px) { - .text-center-xs { - text-align: center; - } -} -.grayscale { - -webkit-filter: grayscale(100%); - filter: grayscale(100%); -} -.uppercase { - padding-bottom: 4px; - text-transform: uppercase; - font-size: 12px; - letter-spacing: 0.4px; - color: #8D99A6; -} -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100%; - vertical-align: middle; -} diff --git a/frappe/public/css/docs.css b/frappe/public/css/docs.css deleted file mode 100644 index 3c57d0bf45..0000000000 --- a/frappe/public/css/docs.css +++ /dev/null @@ -1,604 +0,0 @@ -/* the element that this class is applied to, should have a max width for this to work*/ -.navbar .dropdown-toggle { - padding-top: 8px; - padding-bottom: 8px; -} -.navbar-fixed-top { - left: 0px; - right: 0px; -} -.navbar a { - font-size: 12px; - font-weight: bold; -} -.navbar-icon-home { - vertical-align: middle; -} -.navbar-icon-home:hover, -.navbar-icon-home:focus, -.navbar-icon-home:active, -.navbar-icon-home-hover { - opacity: 1; - Filter: alpha(opacity=100); - /* For IE8 and earlier */ -} -.navbar-user-image { - width: 24px; - height: 24px; - margin-right: 3px; - border-radius: 4px; -} -@media (max-width: 991px) { - .navbar-desk { - width: 35% !important; - } - .navbar-desk ~ ul > li { - float: left; - } - .navbar-desk ~ ul > li a { - padding-left: 10px !important; - padding-right: 10px !important; - } - .navbar-desk ~ ul > li a .avatar { - margin-right: 0; - } - .dropdown-navbar-new-comments > a { - padding: 8px 0 !important; - margin-left: 0 !important; - } -} -@media (max-width: 767px) { - .navbar-desk { - width: 50% !important; - } -} -#search-modal .modal-dialog, -#search-modal .modal-content { - background: transparent; -} -#search-modal .modal-header { - background: #fff; - width: 100%; -} -#search-modal .modal-header form { - vertical-align: middle; -} -#search-modal .modal-header button { - line-height: 0; - position: absolute; - right: 0; - top: 0; - z-index: 9; - padding: 9px; -} -.dropdown-navbar-new-comments > a { - border: 0; -} -.dropdown-navbar-new-comments .dropdown-menu { - margin-top: 0; -} -.dropdown-help .dropdown-menu { - width: 350px !important; - max-height: 440px; - overflow: auto; -} -.dropdown-help .dropdown-menu .input-group { - width: 100%; - background-color: #f5f7fa; - padding: 8px 12px; -} -.dropdown-help .dropdown-menu input { - width: 100%; - padding: 5px 10px; - outline: none; - border-radius: 3px 0 0 3px; - border: 1px solid #d1d8dd; - opacity: 0.9; - line-height: 1.5; -} -.dropdown-help .dropdown-menu button { - border: 1px solid #d1d8dd; -} -@media (max-width: 767px) { - .dropdown-help .dropdown-menu { - position: fixed !important; - top: 40px; - width: 100% !important; - } -} -@media (max-width: 767px) { - .dropdown-mobile.open .dropdown-menu { - position: absolute; - border-top: 1px solid rgba(0, 0, 0, 0.14902); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); - background-color: #fff; - right: 0; - left: auto; - } - .dropdown-mobile.open .dropdown-menu > li > a { - padding: 12px; - } - .dropdown-help { - display: none !important; - } -} -.navbar-new-comments { - display: inline-block; - min-width: 24px; - height: 24px; - border-radius: 4px; - color: #fff; - text-align: center; - padding: 2px 5px; - background-color: #b8c2cc; -} -.navbar-new-comments-true { - background-color: #ff5858; -} -.navbar-form .awesomplete { - margin-left: -15px; - width: 300px; -} -@media (max-width: 1199px) { - .navbar-form .awesomplete { - width: 280px; - } -} -@media (max-width: 991px) { - .navbar-form .awesomplete { - width: 250px; - } -} -#navbar-search { - width: 100%; - background-color: rgba(255, 255, 255, 0.9); -} -.navbar .navbar-search-icon { - color: #6C7680; - font-size: inherit; - position: relative; - right: 24px; - top: 1px; -} -.navbar .badge { - font-weight: normal; -} -#navbar-search-results { - left: auto; - right: inherit; - margin-top: -1px; - max-height: 300px; - overflow-y: auto; - overflow-x: hidden; -} -.navbar-center { - float: left; - color: #6C7680; -} -#navbar-breadcrumbs > li > a:before { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: 0.3em; - display: inline-block; - speak: none; - font-size: 24px; - transition: 0.2s; - position: relative; - top: 3px; - content: "\f105"; - margin-right: 10px; - color: #C0C9D2; -} -#navbar-breadcrumbs > li > a:hover:before, -#navbar-breadcrumbs > li > a:focus:before, -#navbar-breadcrumbs > li > a:active:before { - color: #36414C; -} -#navbar-breadcrumbs > li > a { - padding: 6px 15px 10px 0px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 170px; -} -@media (min-width: 991px) and (max-width: 1199px) { - #navbar-breadcrumbs > li > a { - max-width: 120px; - } -} -.toolbar-user-fullname { - max-width: 150px; - display: inline-block; -} -.navbar-brand > img { - display: inline-block; -} -.toggle-sidebar { - margin-right: 10px; -} -.navbar-default .navbar-nav > li > a, -.navbar-default .navbar-brand { - color: #8D99A6; -} -body { - font-size: 16px; - line-height: 1.65em; - color: #454e57; - -webkit-font-smoothing: antialiased; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -} -.container { - max-width: 870px; -} -img { - max-width: 100%; -} -.splash { - border-bottom: 1px solid #d1d8dd; -} -.splash .jumbotron { - background-color: transparent; - padding: 40px 0 60px 0; - text-align: center; -} -.splash .jumbotron h1 { - font-size: 48px; - font-weight: 400; - opacity: 0.9; - color: #2E3338; -} -.splash .jumbotron p { - font-size: 24px; - font-color: #8D99A6 !important; - letter-spacing: 0px; - opacity: 0.7; - margin-bottom: 90px; - font-weight: 300; - line-height: 1.4em; -} -.splash .section { - padding: 30px 0 0 0; -} -.page-container { - padding-top: 38px; - margin: 0 auto; - max-width: 870px; -} -.page-container .webpage-content ol > li, -.page-container .webpage-content ul > li { - margin: 13px auto; -} -.page-container .webpage-content ol > li li, -.page-container .webpage-content ul > li li { - margin: 4px auto; -} -.page-container .webpage-content ol li ol { - list-style-type: disc; -} -.page-container .webpage-content ul, -.page-container .webpage-content ol { - margin-bottom: 32px; -} -@media (min-width: 768px) { - .page-container .page-content { - width: 83%; - margin: 0 auto; - } -} -#page-index { - padding-top: 0; - width: 100%; - margin: 0; -} -#page-index .page-content { - width: 100%; - margin: 0; -} -body[data-path="index"] .navbar .toggle-sidebar i { - color: #fff; -} -code { - color: #e66a12; - background: #fff6df; -} -pre { - background: #fafbfc; - border: 1px solid #e1e9f0; - border-radius: 2px; -} -.hljs { - background: transparent; - border: none; - padding: 1.2em 1.5em 1.5em; - color: #454e57; -} -.hljs-keyword, -.hljs-tag, -.css .hljs-class, -.css .hljs-id, -.lisp .hljs-title, -.nginx .hljs-title, -.hljs-request, -.hljs-status, -.clojure .hljs-attribute { - color: #e66a12; -} -.diff .hljs-deletion, -.hljs-string, -.hljs-tag .hljs-value, -.hljs-preprocessor, -.hljs-pragma, -.hljs-built_in, -.hljs-javadoc, -.smalltalk .hljs-class, -.smalltalk .hljs-localvars, -.smalltalk .hljs-array, -.css .hljs-rules .hljs-value, -.hljs-attr_selector, -.hljs-pseudo, -.apache .hljs-cbracket, -.tex .hljs-formula, -.coffeescript .hljs-attribute { - color: #dd4a68; -} -.hljs-number, -.hljs-date, -.hljs-regexp, -.hljs-literal, -.hljs-hexcolor, -.smalltalk .hljs-symbol, -.smalltalk .hljs-char, -.go .hljs-constant, -.hljs-change, -.lasso .hljs-variable, -.makefile .hljs-variable, -.asciidoc .hljs-bullet, -.markdown .hljs-bullet, -.asciidoc .hljs-link_url, -.markdown .hljs-link_url { - color: #7575ff; -} -.hljs-shebang, -.diff .hljs-addition, -.hljs-comment, -.hljs-annotation, -.hljs-template_comment, -.hljs-pi, -.hljs-doctype { - color: #6a906a; -} -.dos .hljs-keyword, -.hljs-decorator, -.hljs-title, -.hljs-type, -.diff .hljs-header, -.ruby .hljs-class .hljs-parent, -.apache .hljs-tag, -.nginx .hljs-built_in, -.tex .hljs-command, -.hljs-prompt { - color: #4f4fa4; -} -.navbar { - background-color: #36414C !important; - margin-left: auto; - margin-right: auto; -} -.navbar .container { - max-width: 870px; -} -.navbar .brand-logo { - width: 30px; - margin-top: -4px; - margin-right: 7px; -} -.navbar a { - font-size: 16px; - font-weight: normal; - color: #fff !important; -} -.navbar a.navbar-brand { - font-weight: bold; -} -.navbar a.toggle-sidebar { - margin-top: 8px; -} -.sidebar a { - font-size: 14px; - padding-top: 14px !important; - padding-bottom: 14px !important; -} -.breadcrumb { - line-height: 1em; - color: #8D99A6; - background-color: transparent; - margin-bottom: 32px; - padding: 0px; - padding-left: 20px; - background: url('/assets/img/up.png') 0% 30% no-repeat; -} -.breadcrumb .icon { - display: none; -} -.breadcrumb a, -.breadcrumb a:hover, -.breadcrumb a:focus, -.breadcrumb a:visited { - color: #7575ff; - font-size: 16px; -} -.hero-and-content a, -.hero-and-content a:hover, -.hero-and-content a:focus, -.hero-and-content a:visited { - color: #5E64FF; -} -.hero-and-content a.btn { - color: inherit; -} -a.btn-primary { - color: #7575ff; -} -a.btn-primary:hover, -a.btn-primary:focus, -a.btn-primary:visited { - color: #7575ff; -} -.btn-next-wrapper { - margin-top: 32px; - text-align: right; -} -h2 { - margin-top: 48px; - font-size: 24px; -} -h3, -h4 { - margin-top: 48px; -} -p { - margin-bottom: 16px; -} -.hero-and-content > p { - max-width: 723px; - margin: 0 auto; -} -.navbar { - background-color: transparent; - border: none; - padding: 15px 0px; - border-radius: 0px; - border-bottom: 1px solid #d1d8dd; -} -.section { - padding: 64px 0 0 0; -} -.dev-header { - margin-bottom: 30px; -} -.docs-footer { - padding: 30px 0px 60px 0px; - border-top: 1px solid #d1d8dd; - max-width: 870px; - margin: 0 auto; - margin-top: 80px; - font-size: 14px; -} -.docs-footer h3 { - margin-top: 24px; - font-size: 16px; -} -.docs-footer img.frappe-bird { - width: 40px; - height: 40px; - background: #fff; - margin-bottom: 10px; - padding: 5px; -} -.docs-footer a { - color: #8D99A6; -} -.docs-footer li { - display: inline-block; - margin: 0 10px; -} -.docs-footer .built-with-frappe { - margin-top: -50px; -} -.browser-image { - min-height: 200px; - border: 1px solid #d1d8dd; - border-bottom: 0px; -} -.fake-browser-frame { - position: relative; - margin: 24px auto 0px; - box-shadow: 0px -6px 100px 1px rgba(0, 0, 0, 0.1), 0px -6px 50px 1px rgba(0, 0, 0, 0.4); -} -.fake-browser-frame::before { - content: ""; - height: 24px; - position: absolute; - top: -24px; - left: 0px; - right: 0px; - border: 1px solid #d1d8dd; - background: #f5f7fa; - border-bottom: none; - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} -.fake-browser-frame::after { - content: '\f111 \00a0\00a0 \f111 \00a0\00a0 \f111'; - position: absolute; - color: #d1d8dd; - top: -15px; - left: 8px; - /* octicon */ - font: normal normal; - font-size: 8px; - font-family: 'FontAwesome'; - line-height: 1; - display: inline-block; - text-decoration: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.fake-iphone-frame { - position: relative; - padding: 40px 8px; - border: 1px solid #d1d8dd; - border-radius: 15px; -} -.fake-ipad-frame { - position: relative; - padding: 8px 40px; - border: 1px solid #d1d8dd; - border-radius: 15px; -} -hr { - margin: 48px 0px 30px; -} -.edit { - color: #8d99a6; -} -a.edit, -a.edit:hover, -a.edit:focus, -a.edit:visited, -.edit-container .icon { - color: #8d99a6; -} -.btn-next { - margin-top: 30px; - margin-bottom: 0px; -} -.btn-next:after { - content: " \2192"; -} -#current td { - font-weight: bold; -} -#current td code { - font-weight: normal; - background: transparent; - font-family: "Helvetica Neue", Helvetica, Arial, "Open Sans", sans-serif; - color: #454e57; - font-size: 16px; -} -.hero-and-content [data-html-block="hero"] { - overflow-y: hidden; -} -.page-content-wrapper > .row .col-sm-8 { - width: 100%; -} -.page-content-wrapper > .row .col-sm-4 { - display: none; -} diff --git a/frappe/public/css/fonts/inter/inter.css b/frappe/public/css/fonts/inter/inter.css index 8fba7cf2cf..e7c92eed4e 100644 --- a/frappe/public/css/fonts/inter/inter.css +++ b/frappe/public/css/fonts/inter/inter.css @@ -1,5 +1,6 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 100; src: url("/assets/frappe/css/fonts/inter/inter_thin.woff2") format("woff2"), @@ -7,6 +8,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 100; src: url("/assets/frappe/css/fonts/inter/inter_thinitalic.woff2") format("woff2"), @@ -15,6 +17,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 200; src: url("/assets/frappe/css/fonts/inter/inter_extralight.woff2") format("woff2"), @@ -22,6 +25,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 200; src: url("/assets/frappe/css/fonts/inter/inter_extralightitalic.woff2") format("woff2"), @@ -30,6 +34,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 300; src: url("/assets/frappe/css/fonts/inter/inter_light.woff2") format("woff2"), @@ -37,6 +42,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 300; src: url("/assets/frappe/css/fonts/inter/inter_lightitalic.woff2") format("woff2"), @@ -45,6 +51,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 400; src: url("/assets/frappe/css/fonts/inter/inter_regular.woff2") format("woff2"), @@ -52,6 +59,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 400; src: url("/assets/frappe/css/fonts/inter/inter_italic.woff2") format("woff2"), @@ -60,6 +68,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 500; src: url("/assets/frappe/css/fonts/inter/inter_medium.woff2") format("woff2"), @@ -67,6 +76,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 500; src: url("/assets/frappe/css/fonts/inter/inter_mediumitalic.woff2") format("woff2"), @@ -75,6 +85,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 600; src: url("/assets/frappe/css/fonts/inter/inter_semibold.woff2") format("woff2"), @@ -82,6 +93,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 600; src: url("/assets/frappe/css/fonts/inter/inter_semibolditalic.woff2") format("woff2"), @@ -90,6 +102,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 700; src: url("/assets/frappe/css/fonts/inter/inter_bold.woff2") format("woff2"), @@ -97,6 +110,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 700; src: url("/assets/frappe/css/fonts/inter/inter_bolditalic.woff2") format("woff2"), @@ -105,6 +119,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 800; src: url("/assets/frappe/css/fonts/inter/inter_extrabold.woff2") format("woff2"), @@ -112,6 +127,7 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 800; src: url("/assets/frappe/css/fonts/inter/inter_extrabolditalic.woff2") format("woff2"), @@ -120,6 +136,7 @@ @font-face { font-family: 'Inter'; + font-display: swap; font-style: normal; font-weight: 900; src: url("/assets/frappe/css/fonts/inter/inter_black.woff2") format("woff2"), @@ -127,8 +144,9 @@ } @font-face { font-family: 'Inter'; + font-display: swap; font-style: italic; font-weight: 900; src: url("/assets/frappe/css/fonts/inter/inter_blackitalic.woff2") format("woff2"), url("/assets/frappe/css/fonts/inter/inter_blackitalic.woff") format("woff"); -} \ No newline at end of file +} diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css deleted file mode 100644 index baad12b495..0000000000 --- a/frappe/public/css/form.css +++ /dev/null @@ -1,729 +0,0 @@ -.form-print-wrapper { - border: 1px solid #d1d8dd; - border-top: none; -} -.print-preview-wrapper { - padding: 30px 0px; - background-color: #f5f7fa; -} -.print-toolbar { - margin: 0px; - padding: 10px 0px; - border-bottom: 1px solid #d1d8dd; -} -.print-toolbar > div { - padding-right: 0px; -} -.print-toolbar > div:last-child { - padding-right: 15px; -} -.form-inner-toolbar { - padding: 10px 15px 0px; - background-color: #fafbfc; - text-align: right; -} -.form-inner-toolbar .btn { - margin-bottom: 10px; -} -.form-clickable-section { - border-top: 1px solid #d1d8dd; - padding: 10px 15px; - background-color: #F7FAFC; -} -.form-page.second-page { - border-top: 1px solid #d1d8dd; -} -.form-message { - padding: 15px 30px; - border-bottom: 1px solid #d1d8dd; -} -.document-flow-wrapper { - padding: 40px 15px 30px; - font-size: 12px; - border-bottom: 1px solid #EBEFF2; -} -.document-flow-wrapper .document-flow { - display: inline-block; - position: relative; - left: 50%; - transform: translateX(-50%); -} -.document-flow-wrapper .document-flow .document-flow-link-wrapper { - width: 140px; - display: inline-block; -} -.document-flow-wrapper .document-flow .document-flow-link-wrapper:not(:last-child) { - border-top: 1px solid #b8c2cc; - margin-right: -4px; -} -.document-flow-wrapper .document-flow .document-flow-link-wrapper:last-child { - margin-right: -140px; -} -.document-flow-wrapper .document-flow .document-flow-link { - margin-top: -10px; - display: inline-block; -} -.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):hover .document-flow-link-label, -.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):focus .document-flow-link-label, -.document-flow-wrapper .document-flow .document-flow-link:not(.disabled):active .document-flow-link-label { - text-decoration: underline; -} -.document-flow-wrapper .document-flow .document-flow-link-label { - display: inline-block; - margin-left: -50%; - margin-top: 5px; -} -@media (max-width: 767px) { - .document-flow-wrapper { - display: none; - } -} -.form-dashboard { - background-color: #fafbfc; -} -.form-dashboard-wrapper { - margin: -15px 0px; -} -.form-documents h6 { - margin-top: 15px; -} -.form-dashboard-section { - margin: 0px -15px; - padding: 15px 30px; - border-bottom: 1px solid #EBEFF2; -} -.form-dashboard-section:first-child { - padding-top: 0px; -} -.form-dashboard-section:last-child { - border-bottom: none; -} -.form-heatmap .heatmap { - display: flex; - justify-content: center; -} -.form-heatmap .heatmap-message { - margin-top: 10px; -} -@media (max-width: 991px) { - .form-heatmap { - overflow: hidden; - overflow-x: scroll; - } -} -.inline-graph .inline-graph-half { - width: 48%; - display: inline-block; - position: relative; - height: 30px; -} -.inline-graph .inline-graph-half .inline-graph-count { - font-size: 10px; - position: absolute; - left: 0; - right: 0; - top: 3px; - padding: 0px 5px; - text-align: left; -} -.inline-graph .inline-graph-half .inline-graph-bar { - position: absolute; - left: 0; - right: 0; - top: 20px; -} -.inline-graph .inline-graph-half .inline-graph-bar-inner { - display: block; - float: left; - background-color: #d1d8dd; - height: 6px; - border-radius: 0px 3px 3px 0px; -} -.inline-graph .inline-graph-half .inline-graph-bar-inner.dark { - background-color: #36414C; -} -.inline-graph .inline-graph-half:first-child { - border-right: 1px solid #d1d8dd; - margin-right: -3px; -} -.inline-graph .inline-graph-half:first-child .inline-graph-count { - text-align: right; -} -.inline-graph .inline-graph-half:first-child .inline-graph-bar-inner { - float: right; - border-radius: 3px 0px 0px 3px; -} -.progress-area { - padding-top: 15px; - padding-bottom: 15px; -} -.form-links .document-link { - margin-bottom: 10px; - height: 22px; -} -.form-links .document-link:hover .badge-link { - text-decoration: underline; -} -.form-links .document-link:hover .badge-link[disabled='disabled'] { - text-decoration: none; -} -.form-links .count { - display: inline-block; - margin-left: 5px; - margin-right: 5px; -} -h6.uppercase, -.h6.uppercase { - font-size: 11px; - font-weight: normal; - letter-spacing: 0.4px; - text-transform: uppercase; - color: #8D99A6; -} -.form-section { - margin: 0px; - padding: 15px; -} -.form-section .form-section-description { - margin-bottom: 10px; -} -.form-section .form-section-heading { - margin: 10px 0px; -} -.form-section .section-head { - margin: 0px 0px 15px 15px; - cursor: pointer; -} -.form-section .section-head .collapse-indicator { - color: #d1d8dd; - margin-left: 10px; - position: relative; - bottom: -1px; -} -.form-section .section-head .collapse-indicator.octicon-chevron-up { - bottom: -2px; -} -.form-section .section-head.collapsed { - margin-bottom: 0px; -} -.form-section:not(:last-child), -.form-inner-toolbar { - border-bottom: 1px solid #d1d8dd; -} -.empty-section { - display: none !important; -} -.modal .form-layout { - margin: -15px; -} -.modal .form-grid .form-layout { - margin: 0px; -} -.modal .form-section { - padding: 15px 7px; -} -.help ol { - padding-left: 19px; -} -.field_description_top { - margin-bottom: 3px; -} -.user-actions { - margin-bottom: 15px; -} -.user-actions a { - font-weight: bold; -} -.badge-important { - background-color: #e74c3c; -} -.address-box { - background-color: #fafbfc; - padding: 0px 15px; - margin: 15px 0px; - border: 1px solid #d1d8dd; - border-radius: 3px; - font-size: 12px; -} -.timeline { - margin: 30px 0px; -} -.timeline .timeline-head .comment-input { - height: auto; -} -.timeline-item { - margin-top: 0px; -} -.timeline-item b { - color: #36414C !important; -} -.timeline-item blockquote { - font-size: inherit; -} -.timeline-item .btn-more { - margin-left: 65px; -} -.timeline-item .gmail_extra { - display: none; -} -.timeline-items { - position: relative; -} -.timeline { - position: relative; -} -.timeline::before { - content: " "; - border-left: 1px solid #d1d8dd; - position: absolute; - top: 0px; - bottom: -124px; - left: 43px; - z-index: 0; -} -.timeline.in-dialog::before { - bottom: 0px; -} -@media (max-width: 991px) { - .timeline::before { - bottom: -64px; - } -} -.timeline-item.user-content { - margin: 30px 0px 30px 27px; -} -.timeline-item.user-content .media-body { - border: 1px solid #d1d8dd; - border-radius: 3px; - margin-left: -7px; - position: relative; - max-width: calc(100% - 50px); - padding-right: 0px; - overflow: visible; -} -.timeline-item.user-content .avatar-medium { - margin-right: 10px; - height: 45px; - width: 45px; -} -.timeline-item.user-content .action-btns { - position: absolute; - right: 0; - padding: 8px 15px 0 5px; -} -.timeline-item.user-content .action-btns .edit-btn-container { - margin-right: 13px; -} -.timeline-item.user-content .comment-header { - background-color: #fafbfc; - padding: 10px 15px 8px 13px; - margin: 0px; - color: #8D99A6; - border-bottom: 1px solid #EBEFF2; -} -.timeline-item.user-content .comment-header.links-active { - padding-right: 77px; -} -.timeline-item.user-content .comment-header .asset-details { - display: inline-block; - width: 100%; -} -.timeline-item.user-content .comment-header .asset-details .btn-link { - border: 0; - border-radius: 0; - padding: 0; -} -.timeline-item.user-content .comment-header .asset-details .btn-link:hover { - text-decoration: none; -} -.timeline-item.user-content .comment-header .commented-on-small { - display: none; -} -.timeline-item.user-content .comment-header .octicon-heart { - color: #ff5858; - cursor: pointer; -} -.timeline-item.user-content .reply { - padding: 15px; - overflow: auto; -} -.timeline-item.user-content .reply > div > p:first-child { - margin-top: 0px; -} -.timeline-item.user-content .reply > div > p:last-child { - margin-bottom: 0px; -} -.timeline-item.user-content .reply hr { - margin: 10px 0px; -} -.timeline-item.user-content .close-btn-container .close { - color: inherit; - opacity: 1; - padding: 0; - font-size: 18px; -} -.timeline-item.user-content .edit-btn-container { - padding: 0; -} -.timeline-item.user-content .edit-btn-container .edit { - color: inherit; - font-size: 21px; - line-height: 1; -} -.timeline-item.user-content .edit-btn-container .edit .octicon-check { - font-size: 1em; -} -.timeline-item.user-content .edit-btn-container .edit:hover, -.timeline-item.user-content .edit-btn-container .edit:focus { - color: #000; -} -.timeline-item.user-content .comment-likes { - margin-left: 5px; -} -.timeline-item.user-content .media-body:after, -.timeline-item.user-content .media-body:before { - right: 100%; - top: 15px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} -.timeline-item.user-content .media-body:after { - border-color: rgba(136, 183, 213, 0); - border-right-color: #fafbfc; - border-width: 6px; - margin-top: -6px; -} -.timeline-item.user-content .media-body:before { - border-color: rgba(194, 225, 245, 0); - border-right-color: #d1d8dd; - border-width: 7px; - margin-top: -7px; -} -.timeline-item.notification-content { - padding-left: 30px; - margin: 30px 0px; - position: relative; - color: #8D99A6; -} -.timeline-item.notification-content * { - color: #8D99A6; -} -.timeline-item.notification-content .fa-fw { - margin-left: 36px; -} -.timeline-item.notification-content div.small { - padding-left: 40px; -} -.timeline-item.notification-content div.small .fa-fw { - margin-left: 0px; -} -.timeline-item.notification-content .octicon-heart { - color: #ff5858 !important; -} -.timeline-item.notification-content::before { - content: " "; - width: 7px; - height: 7px; - background-color: #d1d8dd; - position: absolute; - left: 40px; - border-radius: 50%; - top: 5px; -} -.timeline-item .reply-link { - margin-left: 15px; - font-size: 12px; -} -.timeline-head { - background-color: white; - border: 1px solid #d1d8dd; - border-radius: 3px; - position: relative; - z-index: 1; -} -.timeline-head .comment-input-header { - background-color: #fafbfc; - padding: 7px 15px; - border-bottom: 1px solid #EBEFF2; -} -.timeline-head .comment-input-container { - padding: 15px; -} -.timeline-head .comment-input-container .awesomplete > ul { - min-width: 200px; -} -.timeline-head .comment-input { - border-color: #EBEFF2; - max-width: 100%; -} -.timeline-head .comment-input:focus { - box-shadow: none; -} -@media (max-width: 767px) { - .timeline-head { - border-left: none; - border-right: none; - border-radius: 0px; - } -} -.signature-field { - min-height: 300px; - background: #fff; - border: 1px solid #d1d8dd; - border-radius: 3px; - position: relative; - margin-top: -10px; -} -.signature-display { - margin: 7px 0px; - background: #fff; -} -.signature-btn-row { - position: absolute; - bottom: 12px; - right: 12px; -} -.signature-reset { - z-index: 10; - height: 30px; - width: 30px; - padding: 4px 0px; -} -.signature-img { - background: #fff; - border-radius: 3px; - margin-top: 5px; - max-height: 150px; -} -.timeline-new-email { - margin: 30px 0px; - padding-left: 70px; - position: relative; -} -.timeline-new-email::before { - content: " "; - width: 7px; - height: 7px; - background-color: #d1d8dd; - position: absolute; - left: 40px; - border-radius: 50%; - top: 5px; -} -.form-footer h5 { - margin: 15px 0px; - font-weight: bold; -} -.control-label, -.grid-heading-row { - color: #8D99A6; - font-size: 12px; -} -.control-label { - margin-bottom: 5px; - font-weight: normal; -} -.like-disabled-input { - margin-bottom: 7px; - min-height: 30px; - font-weight: bold; - background-color: #f5f7fa; - padding: 5px 10px; - border-radius: 3px; -} -.disabled-check { - color: #f5f7fa; - margin-right: 5px; - margin-bottom: -2px; -} -.like-disabled-input.for-description { - font-weight: normal; - font-size: 12px; -} -.frappe-control { - margin-bottom: 10px; -} -.frappe-control .help-box { - margin-top: 3px; -} -.frappe-control pre { - white-space: pre-wrap; - background-color: inherit; - border: none; - padding: 0px; - margin: 0px; -} -.flex-justify-center { - display: flex; - justify-content: center; -} -.flex-justify-end { - display: flex; - justify-content: flex-end; -} -.hide-control { - display: none !important; -} -.shared-user { - margin-bottom: 10px; -} -.attach-missing-image, -.attach-image-display { - cursor: pointer; -} -select.form-control { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} -.form-control.bold { - color: #000; - font-weight: bold; - background-color: #fffdf4; -} -.form-control[data-fieldtype="Password"] { - position: inherit; -} -.password-strength-indicator { - float: right; - padding: 15px; - margin-top: -41px; - margin-right: -7px; -} -.password-strength-message { - margin-top: -10px; -} -.control-code, -.control-code.bold { - height: 400px; - font-family: Monaco, "Courier New", monospace; - color: #36414C; - font-size: 12px; - line-height: 1.7em; -} -.delivery-status-indicator { - display: inline-block; - margin-top: -3px; - margin-left: 1px; - font-weight: 500; - color: #8D99A6; -} -.attach-btn { - margin-top: 10px; -} -@media (min-width: 768px) { - .layout-main .form-column.col-sm-12 > form > .input-max-width { - max-width: 50%; - padding-right: 15px; - } - .col-sm-6 .form-grid .form-column.col-sm-12 > form > .input-max-width { - max-width: none; - padding-right: 0px; - } - .form-column.col-sm-6 textarea[data-fieldtype="Code"] { - height: 120px !important; - } -} -@media (max-width: 991px) { - .form-section .form-section-heading { - margin-top: 10px; - } -} -@media (max-width: 767px) { - .form-section .section-head { - padding: 15px 15px 15px 0px; - } - .form-section .section-body .form-column:first-child .radio, - .form-section .section-body .form-column:first-child .checkbox { - margin-top: 0; - } - .form-column { - border-bottom: 1px solid #EBEFF2; - padding-top: 15px; - padding-bottom: 15px; - } - .form-column:last-child { - border-bottom: 0px; - } - .form-section { - padding-left: 0px !important; - padding-right: 0px !important; - } - .form-grid { - margin-left: -17px; - margin-right: -17px; - border-left: none !important; - border-right: none !important; - border-radius: none; - } - .form-page .form-section { - padding: 0px 15px; - } - .frappe-control.form-page { - padding: 7px 15px; - border-bottom: 1px solid #EBEFF2; - margin: 0px -15px; - } - .frappe-control.form-page .link-btn { - top: -2px; - } - .frappe-control.form-page .like-disabled-input { - min-height: 0px !important; - } - .frappe-control.form-page:last-child { - margin-bottom: 0px; - } - .form-page .frappe-control:last-child { - border-bottom: 0px; - } - .form-page .frappe-control[data-fieldtype="Table"] { - padding: 0px 15px; - margin-top: -1px; - border-bottom: none; - } - .form-page .frappe-control[data-fieldtype="Table"] label { - margin-top: 7px; - } - .form-page .form-control { - border: none; - border-bottom: 1px solid #d1d8dd; - box-shadow: none; - background-color: inherit; - height: auto; - padding: 0px; - margin-bottom: 7px; - border-radius: 0px; - text-align: left !important; - } - .form-page .form-control:focus { - box-shadow: none; - } -} -/* goals */ -.goals-page-container { - background-color: #fafbfc; - padding-top: 1px; -} -.goals-page-container .goal-container { - background-color: #fff; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - border-radius: 2px; - padding: 10px; - margin: 10px; -} -body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] { - height: 80px !important; -} -.frappe-control[data-fieldtype="Attach"] .attached-file { - position: relative; - margin-top: 5px; -} -.frappe-control[data-fieldtype="Attach"] .attached-file .close { - position: absolute; - top: 0; - right: 0; -} diff --git a/frappe/public/css/mobile.css b/frappe/public/css/mobile.css deleted file mode 100644 index 1cf8bb011a..0000000000 --- a/frappe/public/css/mobile.css +++ /dev/null @@ -1,411 +0,0 @@ -/* the element that this class is applied to, should have a max width for this to work*/ -html { - min-height: 100%; -} -body { - height: 100%; - margin: 0px; - padding: 0px !important; -} -html, -body { - overflow-x: hidden; - overflow-y: overlay; -} -@media (max-width: 991px) { - .intro-area, - .footnote-area { - padding: 15px; - } - .grid-row-open { - top: 0; - } - .layout-main { - position: relative; - } - body[data-route^="Form"] .page-title h1 { - margin-top: 12px; - } - body[data-route^="Form"] .page-title h1.editable-title { - padding-right: 80px; - } - body[data-route^="Form"] .page-title .indicator { - display: inline-block; - margin-top: 12px; - } - body[data-route^="Form"] .page-actions { - padding-top: 20px !important; - padding-bottom: 0px !important; - padding-left: 0px !important; - } - body[data-route^="Form"] .page-head .sub-heading { - font-weight: normal; - font-size: 10px; - display: block; - position: absolute; - right: 140px; - min-width: 200px; - } - body[data-route^="Form"] .title-text { - margin-top: 10px; - } - .toggle-navbar-new-comments { - padding: 8px 0px !important; - } - .navbar > .container > .navbar-header { - float: left; - width: 80%; - } - .navbar > .container > .navbar-right { - float: right; - } - .module-item { - padding: 7px 0px !important; - } - .module-item h4 { - font-weight: normal; - } - #navbar-breadcrumbs { - margin: 0px; - display: inline-block; - max-width: 150px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - #navbar-breadcrumbs > li, - #navbar-breadcrumbs > li > a { - display: inline-block; - vertical-align: middle; - } - #navbar-breadcrumbs > li > a:before { - content: "\f104"; - margin-right: 10px; - color: #6C7680; - } - #navbar-breadcrumbs li:not(:nth-last-child(-n+1)) { - display: none; - } - .navbar-nav { - margin: 0px; - margin-right: -15px; - } - .sidebar .form-group { - margin-bottom: 0px; - } - #sidebar-search { - height: 27px; - } - .sidebar .navbar-search-icon { - float: right; - color: #6C7680; - font-size: inherit; - position: relative; - right: 7px; - top: -20px; - height: 0; - } - .sidebar form { - padding: 7px; - } - .sidebar .main-menu { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 41px; - overflow-y: auto; - } - .sidebar .user-menu { - padding: 9px 14px; - background-color: #f5f7fa; - position: absolute; - left: 0; - bottom: 0; - right: 0; - } - .sidebar .user-menu, - .sidebar .user-menu .octicon { - color: #6C7680; - } - .sidebar .user-menu img { - margin-top: -1px; - } - body[data-route^="Module"] .navbar-center { - display: block !important; - position: absolute; - top: 10px; - left: 25%; - right: 25%; - text-align: center; - } - body.no-breadcrumbs .navbar .navbar-home { - display: inline-block !important; - padding-left: 0px; - margin-left: 0px; - padding-top: 6px; - } - body.no-breadcrumbs .navbar .navbar-home:before { - font-family: FontAwesome; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: 0.3em; - display: inline-block; - speak: none; - font-size: 24px; - transition: 0.2s; - position: relative; - top: 3px; - content: "\f104"; - margin-right: 10px; - color: #6C7680; - } - body.no-breadcrumbs .navbar .navbar-home:hover:before, - body.no-breadcrumbs .navbar .navbar-home:focus:before, - body.no-breadcrumbs .navbar .navbar-home:active:before { - color: #36414C !important; - } - body[data-route=""] .navbar .navbar-home, - body[data-route="desktop"] .navbar .navbar-home { - padding: 8px 10px; - } - body[data-route=""] .navbar .navbar-home:before, - body[data-route="desktop"] .navbar .navbar-home:before { - display: none; - } - body[data-route=""] .navbar .navbar-home img, - body[data-route="desktop"] .navbar .navbar-home img { - margin-top: 0; - } - body[data-route=""] .toggle-sidebar, - body[data-route="desktop"] .toggle-sidebar { - display: none !important; - } - body[data-sidebar="0"] .toggle-sidebar { - display: none !important; - } - body[data-sidebar="0"] #navbar-breadcrumbs, - body[data-sidebar="0"] .navbar-home { - margin-left: 15px !important; - } -} -@media (max-width: 991px) and (max-width: 480px) { - #navbar-breadcrumbs li > a { - width: 100px; - } -} -@media (max-width: 767px) { - .toggle-sidebar { - margin-right: 0; - } - body[data-route^="Form"] .page-title .title-text { - font-size: 16px; - width: calc(100% - 90px); - } - body[data-route^="Form"] .page-title .indicator { - float: left; - margin-top: 10px; - margin-right: 5px; - } - .modal .modal-dialog { - margin: 0px; - padding: 0px; - width: 100%; - background-color: #fff; - } - .modal .modal-content { - border-radius: 0px; - border: none; - height: 100%; - } - .modal .modal-body .form-layout { - margin: -15px; - } - .modal .file-upload .input-upload { - width: 100%; - text-align: center; - } - .modal .file-upload .input-upload .btn-browse { - width: 100%; - } - .modal .file-upload .web-link-wrapper { - display: block; - width: 100% !important; - text-align: center; - } - .modal .file-upload .web-link-wrapper .file-upload-or { - display: block; - margin: 15px 24px; - } - .modal .file-upload .web-link-wrapper .input-link { - width: 100% !important; - } - .layout-main-section-wrapper { - padding: 0px; - } - .layout-main-section { - border-left-color: transparent !important; - border-right-color: transparent !important; - } - .list-row { - padding: 13px 15px !important; - } - .doclist-row { - position: relative; - padding-right: 10px; - } - .doclist-row .list-id { - font-weight: normal; - } - .doclist-row .list-row-id { - left: 18px; - text-align: left; - margin-top: 3px; - } - .doclist-row.has-checkbox .list-row-id { - left: 40px; - } - .doclist-row .list-row-indicator { - position: absolute; - right: 0px; - top: -20px; - } - .doclist-row .list-row-modified { - margin-right: -10px; - } - .doclist-row .list-row-left { - z-index: 1; - } - .doclist-row .list-row-right { - float: right; - } - .doclist-row .list-row-right .list-row-indicator { - top: 4px; - } - .doclist-row .list-row-right .list-row-indicator .indicator::before, - .doclist-row .list-row-right .list-row-indicator .indicator::after { - height: 12px; - width: 12px; - border-radius: 12px; - } - .doclist-row .list-row-right.no-right-column { - position: absolute; - top: 0; - right: 10px; - left: -10px; - width: 100%; - } - body[data-route^="chat"] .navbar-center { - display: block !important; - position: absolute; - top: 10px; - left: 25%; - right: 25%; - text-align: center; - } - #page-chat .layout-side-section { - position: relative; - left: 0px; - border-right: 1px solid #d1d8dd; - padding-left: 0px; - float: left; - width: 76px; - } - #page-chat .layout-main-section-wrapper { - position: absolute; - left: 75px; - right: 0px; - border-left: 1px solid #d1d8dd; - float: left; - } - #page-chat .module-sidebar-item { - margin: 0px; - } - #page-chat .module-sidebar-item .chat-sidebar-link { - padding: 15px; - } - #page-chat .timeline-head { - padding: 15px 15px 7px; - } - #page-chat .list-row { - padding: 7px 0px; - } - #page-chat .message-row-right { - margin-top: 10px; - text-align: left; - } - body[data-route^="Form"] .page-head .sub-heading { - right: 90px; - } - .timeline::before { - content: none; - } - .timeline .timeline-new-email { - margin: 20px 0; - padding-left: 15px; - } - .timeline .timeline-new-email::before { - content: none; - } - .timeline .timeline-item.user-content { - margin: 20px 15px; - } - .timeline .timeline-item.user-content .media-body { - margin-left: 0; - max-width: 100%; - overflow: hidden; - } - .timeline .timeline-item.user-content .media-body:before { - content: none; - } - .timeline .timeline-item.user-content .action-btns { - padding: 7px 10px 2px 5px; - } - .timeline .timeline-item.user-content .action-btns .edit-btn-container { - margin-right: 0; - } - .timeline .timeline-item.user-content .comment-header { - padding: 7px 10px; - } - .timeline .timeline-item.user-content .comment-header .links-active { - padding-right: 10px; - } - .timeline .timeline-item.user-content .comment-header .reply-link { - margin-left: 0; - } - .timeline .timeline-item.user-content .comment-header .asset-details { - width: calc(100% - 30px); - } - .timeline .timeline-item.user-content .avatar-medium { - margin-right: 10px; - } - .timeline .timeline-item.user-content .reply { - padding: 10px; - } - .timeline .timeline-item.user-content .commented-on-small { - display: inline-block; - } - .timeline .timeline-item.user-content .commented-on-small { - display: inline-block; - } - .timeline .timeline-item.notification-content { - padding-left: 15px; - margin: 20px 0; - } - .timeline .timeline-item.notification-content::before { - content: none; - } - .timeline .timeline-item.notification-content .small { - padding-left: 0; - } - .timeline .timeline-item .delivery-status-indicator { - float: left; - margin: 0 5px 0 0; - } - .timeline .asset-details { - line-height: 24px; - /*Height of avtar image -36px to align text center vertically*/ - } -} diff --git a/frappe/public/css/variables.css b/frappe/public/css/variables.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg index d6852c620f..f5539d2bab 100644 --- a/frappe/public/icons/timeless/symbol-defs.svg +++ b/frappe/public/icons/timeless/symbol-defs.svg @@ -1,4 +1,4 @@ - + diff --git a/frappe/public/images/favicon.png b/frappe/public/images/favicon.png deleted file mode 100644 index d66d3920f8..0000000000 Binary files a/frappe/public/images/favicon.png and /dev/null differ diff --git a/frappe/public/js/docs.js b/frappe/public/js/docs.js deleted file mode 100644 index c216de67f9..0000000000 --- a/frappe/public/js/docs.js +++ /dev/null @@ -1,52 +0,0 @@ -// used in documenation site built via document generator - -$(function() { - if(window.hljs) { - $('pre code').each(function(i, block) { - hljs.highlightBlock(block); - }); - } - - // search - $('.sidebar-navbar-items .octicon-search, .navbar .octicon-search').parent().on("click", function() { - var modal = frappe.get_modal("Search", - '

\ -

Search via Google

'); - modal.find(".search-input").on("keyup", function(e) { - if(e.which===13) { - modal.find(".btn-search").trigger("click"); - } - if(e.which===9) { - e.preventDefault(); - modal.find(".btn-search").focus(); - return false; - } - var text = $(this).val(); - modal.find(".btn-search").attr("href", "https://google.com/search?q=" - + text + "+site:" + (window.docs_base_url || "")); - }); - modal.modal("show"); - return false; - }); - -}); - -frappe = { - get_modal: function(title, body_html) { - var modal = $('').appendTo(document.body); - - return modal; - }, -}; diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index f63005278d..fd440dcbbc 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -1305,8 +1305,6 @@ class { this.set_wrapper(selector ? selector : "body") this.set_options(options) - // Load Emojis. - frappe.chat.emoji() } /** @@ -2783,7 +2781,8 @@ frappe.chat.setup = () => { } } -$(document).on('ready toolbar_setup', () => -{ - frappe.chat.setup() -}) +// TODO: Re-enable after re-designing chat +// $(document).on('ready toolbar_setup', () => +// { +// frappe.chat.setup() +// }) diff --git a/frappe/public/js/frappe/data_import/import_preview.js b/frappe/public/js/frappe/data_import/import_preview.js index 477cfb0786..786692e552 100644 --- a/frappe/public/js/frappe/data_import/import_preview.js +++ b/frappe/public/js/frappe/data_import/import_preview.js @@ -188,8 +188,8 @@ frappe.data_import.ImportPreview = class ImportPreview { .join(','); this.datatable.style.setStyle(row_classes, { pointerEvents: 'none', - backgroundColor: frappe.ui.color.get_color_shade('white', 'light'), - color: frappe.ui.color.get_color_shade('black', 'extra-light') + backgroundColor: frappe.ui.color.get_color_shade('gray', 'extra-light'), + color: frappe.ui.color.get_color_shade('gray', 'dark') }); } diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index cac2e65885..3912373bde 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -45,10 +45,7 @@ frappe.Application = Class.extend({ this.setup_frappe_vue(); this.load_bootinfo(); this.load_user_permissions(); - this.set_app_logo_url() - .then(() => { - this.make_nav_bar(); - }); + this.make_nav_bar(); this.set_favicon(); this.setup_analytics(); this.set_fullwidth_if_enabled(); @@ -82,8 +79,11 @@ frappe.Application = Class.extend({ } if (frappe.user_roles.includes('System Manager')) { - this.show_change_log(); - this.show_update_available(); + // delayed following requests to make boot faster + setTimeout(() => { + this.show_change_log(); + this.show_update_available(); + }, 1000); } if (!frappe.boot.developer_mode) { @@ -470,19 +470,6 @@ frappe.Application = Class.extend({ $('').appendTo("head"); $('').appendTo("head"); }, - - set_app_logo_url: function() { - return frappe.call('frappe.core.doctype.navbar_settings.navbar_settings.get_app_logo') - .then(r => { - frappe.app.logo_url = r.message; - if (window.cordova) { - let host = frappe.request.url; - host = host.slice(0, host.length - 1); - frappe.app.logo_url = host + frappe.app.logo_url; - } - }); - }, - trigger_primary_action: function() { if(window.cur_dialog && cur_dialog.display) { // trigger primary @@ -498,6 +485,7 @@ frappe.Application = Class.extend({ if (frappe.utils.is_rtl()) { var ls = document.createElement('link'); ls.rel="stylesheet"; + ls.type = "text/css"; ls.href= "assets/css/frappe-rtl.css"; document.getElementsByTagName('head')[0].appendChild(ls); $('body').addClass('frappe-rtl'); diff --git a/frappe/public/js/frappe/file_uploader/FilePreview.vue b/frappe/public/js/frappe/file_uploader/FilePreview.vue index 7161dc8dc5..cca7dfde2a 100644 --- a/frappe/public/js/frappe/file_uploader/FilePreview.vue +++ b/frappe/public/js/frappe/file_uploader/FilePreview.vue @@ -11,17 +11,18 @@
- + {{ file.name | file_name }} - +
{{ file.name | file_name }} - - - +
+
{{ file.file_obj.size | file_size }} @@ -30,7 +31,7 @@
- {{ __('Drag and drop files here or') }} + {{ __('Drag and drop files here or upload from') }}
-
+
{{ __('Link') }}
+
{{ upload_notes }} @@ -65,21 +73,13 @@
-
@@ -187,6 +187,9 @@ export default { return this.files.length > 0 && this.files.every( file => file.total !== 0 && file.progress === file.total); + }, + allow_take_photo() { + return window.navigator.mediaDevices; } }, methods: { @@ -206,15 +209,19 @@ export default { on_file_input(e) { this.add_files(this.$refs.file_input.files); }, - remove_file(i) { - this.files = this.files.filter((file, j) => i !== j); + remove_file(file) { + this.files = this.files.filter(f => f !== file); }, - toggle_private(i) { - this.files[i].private = !this.files[i].private; - }, - toggle_all_private(flag) { - if (flag == null) { - flag = this.files.every(file => file.private); + toggle_all_private() { + let flag; + let private_values = this.files.filter(file => file.private); + if (private_values.length < this.files.length) { + // there are some private and some public + // set all to private + flag = true; + } else { + // all are private, set all to public + flag = false; } this.files = this.files.map(file => { file.private = flag; @@ -416,7 +423,25 @@ export default { xhr.send(form_data); }); - } + }, + capture_image() { + const capture = new frappe.ui.Capture({ + animate: false, + error: true + }); + capture.show(); + capture.submit(data_url => { + let filename = `capture_${frappe.datetime.now_datetime().replaceAll(/[: -]/g, '_')}.png`; + this.url_to_file(data_url, filename, 'image/png').then((file) => + this.add_files([file]) + ); + }); + }, + url_to_file(url, filename, mime_type) { + return fetch(url) + .then(res => res.arrayBuffer()) + .then(buffer => new File([buffer], filename, { type: mime_type })); + }, } } diff --git a/frappe/public/js/frappe/file_uploader/index.js b/frappe/public/js/frappe/file_uploader/index.js index 0a822971e1..28ce96cd44 100644 --- a/frappe/public/js/frappe/file_uploader/index.js +++ b/frappe/public/js/frappe/file_uploader/index.js @@ -48,6 +48,13 @@ export default class FileUploader { this.uploader = this.$fileuploader.$children[0]; + this.uploader.$watch('files', (files) => { + let all_private = files.every(file => file.private); + if (this.dialog) { + this.dialog.set_secondary_action_label(all_private ? __('Set all public') : __('Set all private')); + } + }, { deep: true }); + if (files && files.length) { this.uploader.add_files(files); } @@ -66,8 +73,10 @@ export default class FileUploader { title: __('Upload'), primary_action_label: __('Upload'), primary_action: () => this.upload_files(), - secondary_action_label: __('Toggle Private'), - secondary_action: () => this.uploader.toggle_all_private() + secondary_action_label: __('Set all private'), + secondary_action: () => { + this.uploader.toggle_all_private(); + } }); this.wrapper = this.dialog.body; diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js index ddde8ab2f7..30eb277f08 100644 --- a/frappe/public/js/frappe/form/controls/autocomplete.js +++ b/frappe/public/js/frappe/form/controls/autocomplete.js @@ -1,6 +1,7 @@ import Awesomplete from 'awesomplete'; frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({ + trigger_change_on_input_event: false, make_input() { this._super(); this.setup_awesomplete(); @@ -89,18 +90,16 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({ }); this.$input.on("awesomplete-open", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).removeClass("modal-dialog-scrollable"); - } + this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable'); + this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable'); + this.autocomplete_open = true; }); this.$input.on("awesomplete-close", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).addClass("modal-dialog-scrollable"); - } + this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable', true); + this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable', true); + this.autocomplete_open = false; }); diff --git a/frappe/public/js/frappe/form/controls/barcode.js b/frappe/public/js/frappe/form/controls/barcode.js index c2314d6664..8ac812c0ed 100644 --- a/frappe/public/js/frappe/form/controls/barcode.js +++ b/frappe/public/js/frappe/form/controls/barcode.js @@ -8,7 +8,7 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({ this.default_svg = ''; let $input_wrapper = this.$wrapper.find('.control-input-wrapper'); this.barcode_area = $( - `
${this.default_svg}
` + `
${this.default_svg}
` ); this.barcode_area.appendTo($input_wrapper); }, @@ -55,7 +55,14 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({ get_options(value) { // get JsBarcode options - let options = JSON.parse('{ "height" : 40 }'); + let options = {}; + options.background = "var(--control-bg)"; + options.lineColor = "var(--text-color)"; + options.font = "var(--font-stack)"; + options.fontSize = "16"; + options.width = "3"; + options.height = "50"; + if (frappe.utils.is_json(this.df.options)) { options = JSON.parse(this.df.options); if (options.format && options.format === 'EAN') { diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index d7f873bee0..9981398b84 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -74,7 +74,7 @@ frappe.ui.form.Control = Class.extend({ frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain); // Match parent grid controls read only status - if (status === 'Write' && (this.grid || (this.layout && this.layout.grid))) { + if (status === 'Write' && (this.grid || (this.layout && this.layout.grid) && !cint(this.df.allow_on_submit))) { var grid = this.grid || this.layout.grid; if (grid.display_status == 'Read') { status = 'Read'; diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js index 3c5faf4a9a..46ab62b717 100644 --- a/frappe/public/js/frappe/form/controls/base_input.js +++ b/frappe/public/js/frappe/form/controls/base_input.js @@ -127,13 +127,6 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ let display_value = frappe.format(value, this.df, { no_icon: true, inline: true }, doc); this.disp_area && $(this.disp_area).html(display_value); }, - - bind_change_event: function() { - var me = this; - this.$input && this.$input.on("change", this.change || function(e) { - me.parse_validate_and_set_in_model(me.get_input_value(), e); - }); - }, set_label: function(label) { if(label) this.df.label = label; diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 401de2ed5d..f381d1b4a2 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -3,6 +3,7 @@ frappe.provide('frappe.phone_call'); frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ html_element: "input", input_type: "text", + trigger_change_on_input_event: true, make_input: function() { if(this.$input) return; @@ -22,8 +23,20 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ this.has_input = true; this.bind_change_event(); this.setup_autoname_check(); - // somehow this event does not bubble up to document - // after v7, if you can debug, remove this + }, + bind_change_event: function() { + const change_handler = e => { + if (this.change) this.change(e); + else { + let value = this.get_input_value(); + this.parse_validate_and_set_in_model(value, e); + } + }; + this.$input.on("change", change_handler); + if (this.trigger_change_on_input_event) { + // debounce to avoid repeated validations on value change + this.$input.on("input", frappe.utils.debounce(change_handler, 500)); + } }, setup_autoname_check: function() { if (!this.df.parent) return; @@ -116,5 +129,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ } else { return v; } + }, + toggle_container_scroll: function(el_class, scroll_class, add=false) { + let el = this.$input.parents(el_class)[0]; + if (el) $(el).toggleClass(scroll_class, add); } }); diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index da214029be..ca214ca0fa 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -1,5 +1,5 @@ - frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ + trigger_change_on_input_event: false, make_input: function() { this._super(); this.make_picker(); diff --git a/frappe/public/js/frappe/form/controls/image.js b/frappe/public/js/frappe/form/controls/image.js index b70822377d..90de22138a 100644 --- a/frappe/public/js/frappe/form/controls/image.js +++ b/frappe/public/js/frappe/form/controls/image.js @@ -14,7 +14,7 @@ frappe.ui.form.ControlImage = frappe.ui.form.Control.extend({ this.$img = $("") .appendTo(this.$body); } else { - this.$buffer = $("
") + this.$buffer = $(`
${frappe.utils.icon('restriction', 'md')}
`) .appendTo(this.$body); } return false; diff --git a/frappe/public/js/frappe/form/controls/int.js b/frappe/public/js/frappe/form/controls/int.js index 9b59444fd3..aca3a85603 100644 --- a/frappe/public/js/frappe/form/controls/int.js +++ b/frappe/public/js/frappe/form/controls/int.js @@ -1,4 +1,5 @@ frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({ + trigger_change_on_input_event: false, make: function () { this._super(); // $(this.label_area).addClass('pull-right'); diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index b9c3dc80ec..e0a72ed8c1 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -9,16 +9,17 @@ import Awesomplete from 'awesomplete'; frappe.ui.form.recent_link_validations = {}; frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ + trigger_change_on_input_event: false, make_input: function() { var me = this; - // line-height: 1 is for Mozilla 51, shows extra padding otherwise - $('').prependTo(this.input_area); + $(``).prependTo(this.input_area); this.$input_area = $(this.input_area); this.$input = this.$input_area.find('input'); this.$link = this.$input_area.find('.link-btn'); @@ -135,7 +136,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ return $('
  • ') .data('item.autocomplete', d) .prop('aria-selected', 'false') - .html('

    ' + html + '

    ') + .html(`

    ${html}

    `) .get(0); }, sort: function() { @@ -235,18 +236,16 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ }); this.$input.on("awesomplete-open", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).removeClass("modal-dialog-scrollable"); - } + this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable'); + this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable'); + this.autocomplete_open = true; }); this.$input.on("awesomplete-close", () => { - let modal = this.$input.parents('.modal-dialog')[0]; - if (modal) { - $(modal).addClass("modal-dialog-scrollable"); - } + this.toggle_container_scroll('.modal-dialog', 'modal-dialog-scrollable', true); + this.toggle_container_scroll('.grid-form-body .form-area', 'scrollable', true); + this.autocomplete_open = false; }); diff --git a/frappe/public/js/frappe/form/controls/multiselect_list.js b/frappe/public/js/frappe/form/controls/multiselect_list.js index 5884a18753..f1ebd06350 100644 --- a/frappe/public/js/frappe/form/controls/multiselect_list.js +++ b/frappe/public/js/frappe/form/controls/multiselect_list.js @@ -1,4 +1,5 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({ + trigger_change_on_input_event: false, make_input() { let template = `
    -
    +