From c2c725c8ce08ab932f7391c6a1deff40a282b1bf Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 25 Mar 2022 19:10:11 +0100 Subject: [PATCH 01/40] fix: store reference to leaflet drawControl --- frappe/public/js/frappe/form/controls/geolocation.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 280eac3941..83a85d9317 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -146,9 +146,8 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f }; // create control and add to map - var drawControl = new L.Control.Draw(options); - - this.map.addControl(drawControl); + this.drawControl = new L.Control.Draw(options); + this.map.addControl(this.drawControl); this.map.on('draw:created', (e) => { var type = e.layerType, From cd7d45757b14e8e41767d0abc3395b858167bdb1 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Tue, 29 Mar 2022 15:23:03 +0530 Subject: [PATCH 02/40] test: Added test script for control type "Date" --- cypress/integration/control_date.js | 122 ++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 cypress/integration/control_date.js diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js new file mode 100644 index 0000000000..d1ef62dd90 --- /dev/null +++ b/cypress/integration/control_date.js @@ -0,0 +1,122 @@ +context('Date Control', () => { + before(() => { + cy.login(); + cy.visit('/app/doctype'); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Date Control', + fields: [ + { + "label": "Date", + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1 + }, + ] + }); + }); + }); + it('Selecting a date from the datepicker', () => { + cy.new_form('Test Date Control'); + cy.get_field('date','Date').click(); + cy.get('.datepicker--nav-title').click(); + cy.get('.datepicker--nav-title').click({force: true}); + + + //Inputing values in the date field + cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click(); + cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click(); + cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click(); + + //Verifying if the selected date is displayed in the date field + cy.get_field('date','Date').should('have.value', '01-15-2020'); + }); + + it('Checking next and previous button', () => { + cy.get_field('date','Date').click(); + + //Clicking on the next button in the datepicker + cy.get('.datepicker--nav-action[data-action=next]').click(); + + //Selecting a date from the datepicker + cy.get('.datepicker--cell[data-date=15]').click({force: true}); + + //Verifying if the selected date has been displayed in the date field + cy.get_field('date','Date').should('have.value', '02-15-2020'); + cy.wait(500); + cy.get_field('date','Date').click(); + + //Clicking on the previous button in the datepicker + cy.get('.datepicker--nav-action[data-action=prev]').click(); + + //Selecting a date from the datepicker + cy.get('.datepicker--cell[data-date=15]').click({force: true}); + + //Verifying if the selected date has been displayed in the date field + cy.get_field('date','Date').should('have.value', '01-15-2020'); + }); + + it('Clicking on "Today" button gives todays date', () => { + cy.get_field('date','Date').click(); + + //Clicking on "Today" button + cy.get('.datepicker--button').click(); + + //Picking up the todays date + const todaysDate = Cypress.moment().format('MM-DD-YYYY'); + cy.log(todaysDate); + + //Verifying if clicking on "Today" button matches today's date + cy.get_field('date','Date').should('have.value', todaysDate); + }); + + it.only('Configuring first day of the week', () => { + //Visiting "System Settings" page + cy.visit('/app/system-settings/System%20Settings'); + + //Visiting the "Date and Number Format" section + cy.contains('Date and Number Format').click(); + + //Changing the configuration for "First day of the week" field + cy.get('select[data-fieldname="first_day_of_the_week"]').select('Tuesday'); + cy.get('.page-head .page-actions').findByRole('button', {name: 'Save'}).click(); + cy.new_form('Test Date Control'); + cy.get_field('date','Date').click(); + + //Checking if the first day shown in the datepicker is the one which is configured in the System Settings Page + cy.get('.datepicker--days-names').eq(0).should('contain.text', 'Tu'); + cy.visit('/app/doctype'); + + //Adding filter in the doctype list + cy.add_filter(); + cy.get('.fieldname-select-area').type('Created On{enter}'); + cy.get('.filter-field > .form-group > .input-with-feedback').click(); + + //Checking if the first day shown in the datepicker is the one which is configured in the System Settings Page + cy.get('.datepicker--days-names').eq(0).should('contain.text', 'Tu'); + + //Adding event + cy.visit('/app/event'); + cy.click_listview_primary_button('Add Event'); + cy.get('textarea[data-fieldname=subject]').type('Test'); + //cy.fill_field('subject','Test','textarea'); + cy.get('form > .has-error > .form-group > .control-input-wrapper > .control-input > .input-with-feedback[data-fieldtype="Datetime"]').click(); + cy.get('.datepicker.active > .datepicker--content > .datepicker--days > .datepicker--cells > .datepicker--cell[data-date=10]').click({force: true}); + cy.click_listview_primary_button('Save'); + cy.visit('/app/event'); + cy.get('.custom-btn-group > .btn').click(); + + //Opening Calendar view for the event created + cy.get('[data-view="Calendar"] > .grey-link').click(); + + //Checking if the calendar view has the first day as the configured day in the System Settings Page + cy.get('.fc-head-container').eq(0).should('contain.text', 'Tue'); + + //Deleting the created event + cy.visit('/app/event'); + cy.get('.list-row-checkbox').eq(0).click(); + cy.get('.actions-btn-group > .btn').contains('Actions').click(); + cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click(); + cy.click_modal_primary_button('Yes'); + }); +}); \ No newline at end of file From 23fa892aca775d53f4d15c7821a935a577a18975 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Tue, 29 Mar 2022 15:30:02 +0530 Subject: [PATCH 03/40] test: Fixing sider issues --- cypress/integration/control_date.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index d1ef62dd90..d6220c3c51 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -18,7 +18,7 @@ context('Date Control', () => { }); it('Selecting a date from the datepicker', () => { cy.new_form('Test Date Control'); - cy.get_field('date','Date').click(); + cy.get_field('date', 'Date').click(); cy.get('.datepicker--nav-title').click(); cy.get('.datepicker--nav-title').click({force: true}); @@ -29,11 +29,11 @@ context('Date Control', () => { cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click(); //Verifying if the selected date is displayed in the date field - cy.get_field('date','Date').should('have.value', '01-15-2020'); + cy.get_field('date', 'Date').should('have.value', '01-15-2020'); }); it('Checking next and previous button', () => { - cy.get_field('date','Date').click(); + cy.get_field('date', 'Date').click(); //Clicking on the next button in the datepicker cy.get('.datepicker--nav-action[data-action=next]').click(); @@ -42,9 +42,9 @@ context('Date Control', () => { cy.get('.datepicker--cell[data-date=15]').click({force: true}); //Verifying if the selected date has been displayed in the date field - cy.get_field('date','Date').should('have.value', '02-15-2020'); + cy.get_field('date', 'Date').should('have.value', '02-15-2020'); cy.wait(500); - cy.get_field('date','Date').click(); + cy.get_field('date', 'Date').click(); //Clicking on the previous button in the datepicker cy.get('.datepicker--nav-action[data-action=prev]').click(); @@ -53,11 +53,11 @@ context('Date Control', () => { cy.get('.datepicker--cell[data-date=15]').click({force: true}); //Verifying if the selected date has been displayed in the date field - cy.get_field('date','Date').should('have.value', '01-15-2020'); + cy.get_field('date', 'Date').should('have.value', '01-15-2020'); }); it('Clicking on "Today" button gives todays date', () => { - cy.get_field('date','Date').click(); + cy.get_field('date', 'Date').click(); //Clicking on "Today" button cy.get('.datepicker--button').click(); @@ -67,7 +67,7 @@ context('Date Control', () => { cy.log(todaysDate); //Verifying if clicking on "Today" button matches today's date - cy.get_field('date','Date').should('have.value', todaysDate); + cy.get_field('date', 'Date').should('have.value', todaysDate); }); it.only('Configuring first day of the week', () => { @@ -81,7 +81,7 @@ context('Date Control', () => { cy.get('select[data-fieldname="first_day_of_the_week"]').select('Tuesday'); cy.get('.page-head .page-actions').findByRole('button', {name: 'Save'}).click(); cy.new_form('Test Date Control'); - cy.get_field('date','Date').click(); + cy.get_field('date', 'Date').click(); //Checking if the first day shown in the datepicker is the one which is configured in the System Settings Page cy.get('.datepicker--days-names').eq(0).should('contain.text', 'Tu'); From cc3a046f465e84bc9f3528e5bcb61406b7e42c2d Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Tue, 29 Mar 2022 15:40:26 +0530 Subject: [PATCH 04/40] test: Corrected selectors --- cypress/integration/control_date.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index d6220c3c51..27be1c3264 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -98,10 +98,8 @@ context('Date Control', () => { //Adding event cy.visit('/app/event'); cy.click_listview_primary_button('Add Event'); - cy.get('textarea[data-fieldname=subject]').type('Test'); - //cy.fill_field('subject','Test','textarea'); - cy.get('form > .has-error > .form-group > .control-input-wrapper > .control-input > .input-with-feedback[data-fieldtype="Datetime"]').click(); - cy.get('.datepicker.active > .datepicker--content > .datepicker--days > .datepicker--cells > .datepicker--cell[data-date=10]').click({force: true}); + cy.fill_field('subject', 'Test', 'Textarea'); + cy.fill_field('starts_on', '01-01-2022 00:00:00', 'Datetime'); cy.click_listview_primary_button('Save'); cy.visit('/app/event'); cy.get('.custom-btn-group > .btn').click(); From eed29df34dd4681d8df0260b11d27ca931a1c7a1 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Tue, 29 Mar 2022 16:31:03 +0530 Subject: [PATCH 05/40] test: Corrected Selector --- cypress/integration/control_date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index 27be1c3264..bdc3a7237f 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -98,7 +98,7 @@ context('Date Control', () => { //Adding event cy.visit('/app/event'); cy.click_listview_primary_button('Add Event'); - cy.fill_field('subject', 'Test', 'Textarea'); + cy.fill_field('subject', 'Test', 'Small Text'); cy.fill_field('starts_on', '01-01-2022 00:00:00', 'Datetime'); cy.click_listview_primary_button('Save'); cy.visit('/app/event'); From 3edf254ef3171b2148e915df1ddcba1583fe7f36 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Tue, 29 Mar 2022 17:09:40 +0530 Subject: [PATCH 06/40] test: Corrected selector --- cypress/integration/control_date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index bdc3a7237f..c45602b4d9 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -98,7 +98,7 @@ context('Date Control', () => { //Adding event cy.visit('/app/event'); cy.click_listview_primary_button('Add Event'); - cy.fill_field('subject', 'Test', 'Small Text'); + cy.get('textarea[data-fieldname=subject]').type('Test'); cy.fill_field('starts_on', '01-01-2022 00:00:00', 'Datetime'); cy.click_listview_primary_button('Save'); cy.visit('/app/event'); From 8296d6e84a29b156421414aafb596ca125083b77 Mon Sep 17 00:00:00 2001 From: phot0n Date: Thu, 31 Mar 2022 18:12:09 +0530 Subject: [PATCH 07/40] fix: use backticks for fieldname while preparing filters --- frappe/model/db_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 16056d382a..b573e1d301 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -476,7 +476,7 @@ class DatabaseQuery(object): if 'ifnull(' in f.fieldname: column_name = self.cast_name(f.fieldname, "ifnull(") else: - column_name = self.cast_name(f"{tname}.{f.fieldname}") + column_name = self.cast_name(f"{tname}.`{f.fieldname}`") if f.operator.lower() in additional_filters_config: f.update(get_additional_filter_field(additional_filters_config, f, f.value)) From d032822093615b6e677854fbe5fa4cc84d7eb276 Mon Sep 17 00:00:00 2001 From: phot0n Date: Thu, 31 Mar 2022 21:25:07 +0530 Subject: [PATCH 08/40] fix: use backticks in test_cast_name --- frappe/tests/test_db_query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index b4c7c7cce7..62842dc167 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -507,10 +507,10 @@ class TestReportview(unittest.TestCase): if frappe.db.db_type == "postgres": self.assertTrue("strpos( cast( \"tabautoinc_dt_test\".\"name\" as varchar), \'1\')" in query) - self.assertTrue("where cast(\"tabautoinc_dt_test\".name as varchar) = \'1\'" in query) + self.assertTrue("where cast(\"tabautoinc_dt_test\".\"name\" as varchar) = \'1\'" in query) else: self.assertTrue("locate(\'1\', `tabautoinc_dt_test`.`name`)" in query) - self.assertTrue("where `tabautoinc_dt_test`.name = 1" in query) + self.assertTrue("where `tabautoinc_dt_test`.`name` = 1" in query) dt.delete(ignore_permissions=True) From a77381de2b970a0c5cfc8766a3eca787f8e0e369 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Wed, 30 Mar 2022 16:10:13 +0530 Subject: [PATCH 09/40] feat: Added fuzzy matching to awesome bar --- .../js/frappe/ui/toolbar/fuzzy_match.js | 196 ++++++++++++++++++ .../js/frappe/ui/toolbar/search_utils.js | 123 +++-------- 2 files changed, 230 insertions(+), 89 deletions(-) create mode 100644 frappe/public/js/frappe/ui/toolbar/fuzzy_match.js diff --git a/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js new file mode 100644 index 0000000000..755882384b --- /dev/null +++ b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js @@ -0,0 +1,196 @@ +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// VERSION +// 0.1.0 (2016-03-28) Initial release +// +// AUTHOR +// Forrest Smith +// +// CONTRIBUTORS +// J�rgen Tjern� - async helper +// Anurag Awasthi - updated to 0.2.0 + +const SEQUENTIAL_BONUS = 15; // bonus for adjacent matches +const SEPARATOR_BONUS = 30; // bonus if match occurs after a separator +const CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower +const FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched + +const LEADING_LETTER_PENALTY = -5; // penalty applied for every letter in str before the first match +const MAX_LEADING_LETTER_PENALTY = -15; // maximum penalty for leading letters +const UNMATCHED_LETTER_PENALTY = -1; + + +/** + * Does a fuzzy search to find pattern inside a string. + * @param {*} pattern string pattern to search for + * @param {*} str string string which is being searched + * @returns [boolean, number] a boolean which tells if pattern was + * found or not and a search score + */ +function fuzzy_match(pattern, str) { + const recursionCount = 0; + const recursionLimit = 10; + const matches = []; + const maxMatches = 256; + + return fuzzyMatchRecursive( + pattern, + str, + 0 /* patternCurIndex */, + 0 /* strCurrIndex */, + null /* srcMatces */, + matches, + maxMatches, + 0 /* nextMatch */, + recursionCount, + recursionLimit + ); +} + +function fuzzyMatchRecursive( + pattern, + str, + patternCurIndex, + strCurrIndex, + srcMatces, + matches, + maxMatches, + nextMatch, + recursionCount, + recursionLimit +) { + let outScore = 0; + + // Return if recursion limit is reached. + if (++recursionCount >= recursionLimit) { + return [false, outScore]; + } + + // Return if we reached ends of strings. + if (patternCurIndex === pattern.length || strCurrIndex === str.length) { + return [false, outScore]; + } + + // Recursion params + let recursiveMatch = false; + let bestRecursiveMatches = []; + let bestRecursiveScore = 0; + + // Loop through pattern and str looking for a match. + let firstMatch = true; + while (patternCurIndex < pattern.length && strCurrIndex < str.length) { + // Match found. + if ( + pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase() + ) { + if (nextMatch >= maxMatches) { + return [false, outScore]; + } + + if (firstMatch && srcMatces) { + matches = [...srcMatces]; + firstMatch = false; + } + + const recursiveMatches = []; + const [matched, recursiveScore] = fuzzyMatchRecursive( + pattern, + str, + patternCurIndex, + strCurrIndex + 1, + matches, + recursiveMatches, + maxMatches, + nextMatch, + recursionCount, + recursionLimit + ); + + if (matched) { + // Pick best recursive score. + if (!recursiveMatch || recursiveScore > bestRecursiveScore) { + bestRecursiveMatches = [...recursiveMatches]; + bestRecursiveScore = recursiveScore; + } + recursiveMatch = true; + } + + matches[nextMatch++] = strCurrIndex; + ++patternCurIndex; + } + ++strCurrIndex; + } + + const matched = patternCurIndex === pattern.length; + + if (matched) { + outScore = 100; + + // Apply leading letter penalty + let penalty = LEADING_LETTER_PENALTY * matches[0]; + penalty = + penalty < MAX_LEADING_LETTER_PENALTY + ? MAX_LEADING_LETTER_PENALTY + : penalty; + outScore += penalty; + + //Apply unmatched penalty + const unmatched = str.length - nextMatch; + outScore += UNMATCHED_LETTER_PENALTY * unmatched; + + // Apply ordering bonuses + for (let i = 0; i < nextMatch; i++) { + const currIdx = matches[i]; + + if (i > 0) { + const prevIdx = matches[i - 1]; + if (currIdx == prevIdx + 1) { + outScore += SEQUENTIAL_BONUS; + } + } + + // Check for bonuses based on neighbor character value. + if (currIdx > 0) { + // Camel case + const neighbor = str[currIdx - 1]; + const curr = str[currIdx]; + if ( + neighbor !== neighbor.toUpperCase() && + curr !== curr.toLowerCase() + ) { + outScore += CAMEL_BONUS; + } + const isNeighbourSeparator = neighbor == "_" || neighbor == " "; + if (isNeighbourSeparator) { + outScore += SEPARATOR_BONUS; + } + } else { + // First letter + outScore += FIRST_LETTER_BONUS; + } + } + + // Return best result + if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { + // Recursive score is better than "this" + matches = [...bestRecursiveMatches]; + outScore = bestRecursiveScore; + return [true, outScore]; + } else if (matched) { + // "this" score is better than recursive + return [true, outScore]; + } else { + return [false, outScore]; + } + } + return [false, outScore]; +} + + +module.exports = { + fuzzy_match +}; diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index 9700276568..8572160737 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -1,4 +1,6 @@ frappe.provide('frappe.search'); +import { fuzzyMatch } from './fuzzy_match.js' + frappe.search.utils = { setup_recent: function() { @@ -533,101 +535,44 @@ frappe.search.utils = { }, fuzzy_search: function(keywords, _item) { - // Returns 10 for case-perfect contain, 0 for not found - // 9 for perfect contain, - // 0 - 6 for fuzzy contain - - // **Specific use-case step** - keywords = keywords || ''; - var item = __(_item || ''); - var item_without_hyphen = item.replace(/-/g, " "); - - var item_length = item.length; - var query_length = keywords.length; - var length_ratio = query_length / item_length; - var max_skips = 3, max_mismatch_len = 2; - - if (query_length > item_length) { - return 0; - } - - // check for perfect string matches or - // matches that start with the keyword - if ([item, item_without_hyphen].includes(keywords) - || [item, item_without_hyphen].some((txt) => txt.toLowerCase().indexOf(keywords) === 0)) { - return 10 + length_ratio; - } - - if (item.indexOf(keywords) !== -1 && keywords !== keywords.toLowerCase()) { - return 9 + length_ratio; - } - - item = item.toLowerCase(); - keywords = keywords.toLowerCase(); - - if (item.indexOf(keywords) !== -1) { - return 8 + length_ratio; - } - - var skips = 0, mismatches = 0; - outer: for (var i = 0, j = 0; i < query_length; i++) { - if (mismatches !== 0) skips++; - if (skips > max_skips) return 0; - var k_ch = keywords.charCodeAt(i); - mismatches = 0; - while (j < item_length) { - if (item.charCodeAt(j++) === k_ch) { - continue outer; - } - if(++mismatches > max_mismatch_len) return 0 ; - } - return 0; - } - - // Since indexOf didn't pass, there will be atleast 1 skip - // hence no divide by zero, but just to be safe - if((skips + mismatches) > 0) { - return (5 + length_ratio)/(skips + mismatches); - } else { - return 0; - } + var match = fuzzyMatch(keywords, _item); + return match[1]; }, bolden_match_part: function(str, subseq) { - var rendered = ""; - if(this.fuzzy_search(subseq, str) === 0) { + if(fuzzyMatch(subseq, str)[0] === false) { return str; - } else if(this.fuzzy_search(subseq, str) > 6) { - var regEx = new RegExp("("+ subseq +")", "ig"); - return str.replace(regEx, '$1'); - } else { - var str_orig = str; - var str = str.toLowerCase(); - var str_len = str.length; - var subseq = subseq.toLowerCase(); - - outer: for(var i = 0, j = 0; i < subseq.length; i++) { - var sub_ch = subseq.charCodeAt(i); - while(j < str_len) { - if(str.charCodeAt(j) === sub_ch) { - var str_char = str_orig.charAt(j); - if(str_char === str_char.toLowerCase()) { - rendered += '' + subseq.charAt(i) + ''; - } else { - rendered += '' + subseq.charAt(i).toUpperCase() + ''; - } - j++; - continue outer; - } - rendered += str_orig.charAt(j); - j++; - } - return str_orig; - } - rendered += str_orig.slice(j); - return rendered; } + if(str.indexOf(subseq) == 0) { + var tail = str.split(subseq)[1]; + return '' + subseq + '' + tail; + } + var rendered = ""; + var str_orig = str; + var str = str.toLowerCase(); + var str_len = str.length; + var subseq = subseq.toLowerCase(); + outer: for(var i = 0, j = 0; i < subseq.length; i++) { + var sub_ch = subseq.charCodeAt(i); + while(j < str_len) { + if(str.charCodeAt(j) === sub_ch) { + var str_char = str_orig.charAt(j); + if(str_char === str_char.toLowerCase()) { + rendered += '' + subseq.charAt(i) + ''; + } else { + rendered += '' + subseq.charAt(i).toUpperCase() + ''; + } + j++; + continue outer; + } + rendered += str_orig.charAt(j); + j++; + } + return str_orig; + } + rendered += str_orig.slice(j); + return rendered; }, get_executables(keywords) { From 096d6459f7aa6fb84aff726ce59125a0598b03c9 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Fri, 1 Apr 2022 10:57:48 +0530 Subject: [PATCH 10/40] fix: Fix case conversions --- .../js/frappe/ui/toolbar/fuzzy_match.js | 150 +++++++++--------- .../js/frappe/ui/toolbar/search_utils.js | 6 +- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js index 755882384b..0d122faad4 100644 --- a/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js +++ b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js @@ -32,103 +32,103 @@ const UNMATCHED_LETTER_PENALTY = -1; * found or not and a search score */ function fuzzy_match(pattern, str) { - const recursionCount = 0; - const recursionLimit = 10; + const recursion_count = 0; + const recursion_limit = 10; const matches = []; - const maxMatches = 256; + const max_matches = 256; - return fuzzyMatchRecursive( + return fuzzy_match_recursive( pattern, str, - 0 /* patternCurIndex */, - 0 /* strCurrIndex */, - null /* srcMatces */, + 0 /* pattern_cur_index */, + 0 /* str_curr_index */, + null /* src_matces */, matches, - maxMatches, - 0 /* nextMatch */, - recursionCount, - recursionLimit + max_matches, + 0 /* next_match */, + recursion_count, + recursion_limit ); } -function fuzzyMatchRecursive( +function fuzzy_match_recursive( pattern, str, - patternCurIndex, - strCurrIndex, - srcMatces, + pattern_cur_index, + str_curr_index, + src_matces, matches, - maxMatches, - nextMatch, - recursionCount, - recursionLimit + max_matches, + next_match, + recursion_count, + recursion_limit ) { - let outScore = 0; + let out_score = 0; // Return if recursion limit is reached. - if (++recursionCount >= recursionLimit) { - return [false, outScore]; + if (++recursion_count >= recursion_limit) { + return [false, out_score]; } // Return if we reached ends of strings. - if (patternCurIndex === pattern.length || strCurrIndex === str.length) { - return [false, outScore]; + if (pattern_cur_index === pattern.length || str_curr_index === str.length) { + return [false, out_score]; } // Recursion params - let recursiveMatch = false; - let bestRecursiveMatches = []; - let bestRecursiveScore = 0; + let recursive_match = false; + let best_recursive_matches = []; + let best_recursive_score = 0; // Loop through pattern and str looking for a match. - let firstMatch = true; - while (patternCurIndex < pattern.length && strCurrIndex < str.length) { + let first_match = true; + while (pattern_cur_index < pattern.length && str_curr_index < str.length) { // Match found. if ( - pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase() + pattern[pattern_cur_index].toLowerCase() === str[str_curr_index].toLowerCase() ) { - if (nextMatch >= maxMatches) { - return [false, outScore]; + if (next_match >= max_matches) { + return [false, out_score]; } - if (firstMatch && srcMatces) { - matches = [...srcMatces]; - firstMatch = false; + if (first_match && src_matces) { + matches = [...src_matces]; + first_match = false; } - const recursiveMatches = []; - const [matched, recursiveScore] = fuzzyMatchRecursive( + const recursive_matches = []; + const [matched, recursive_score] = fuzzy_match_recursive( pattern, str, - patternCurIndex, - strCurrIndex + 1, + pattern_cur_index, + str_curr_index + 1, matches, - recursiveMatches, - maxMatches, - nextMatch, - recursionCount, - recursionLimit + recursive_matches, + max_matches, + next_match, + recursion_count, + recursion_limit ); if (matched) { // Pick best recursive score. - if (!recursiveMatch || recursiveScore > bestRecursiveScore) { - bestRecursiveMatches = [...recursiveMatches]; - bestRecursiveScore = recursiveScore; + if (!recursive_match || recursive_score > best_recursive_score) { + best_recursive_matches = [...recursive_matches]; + best_recursive_score = recursive_score; } recursiveMatch = true; } - matches[nextMatch++] = strCurrIndex; - ++patternCurIndex; + matches[next_match++] = str_curr_index; + ++pattern_cur_index; } - ++strCurrIndex; + ++str_curr_index; } - const matched = patternCurIndex === pattern.length; + const matched = pattern_cur_index === pattern.length; if (matched) { - outScore = 100; + out_score = 100; // Apply leading letter penalty let penalty = LEADING_LETTER_PENALTY * matches[0]; @@ -136,58 +136,58 @@ function fuzzyMatchRecursive( penalty < MAX_LEADING_LETTER_PENALTY ? MAX_LEADING_LETTER_PENALTY : penalty; - outScore += penalty; + out_score += penalty; //Apply unmatched penalty - const unmatched = str.length - nextMatch; - outScore += UNMATCHED_LETTER_PENALTY * unmatched; + const unmatched = str.length - next_match; + out_score += UNMATCHED_LETTER_PENALTY * unmatched; // Apply ordering bonuses - for (let i = 0; i < nextMatch; i++) { - const currIdx = matches[i]; + for (let i = 0; i < next_match; i++) { + const curr_idx = matches[i]; if (i > 0) { - const prevIdx = matches[i - 1]; - if (currIdx == prevIdx + 1) { - outScore += SEQUENTIAL_BONUS; + const prev_idx = matches[i - 1]; + if (curr_idx == prev_idx + 1) { + out_score += SEQUENTIAL_BONUS; } } // Check for bonuses based on neighbor character value. - if (currIdx > 0) { + if (curr_idx > 0) { // Camel case - const neighbor = str[currIdx - 1]; - const curr = str[currIdx]; + const neighbor = str[curr_idx - 1]; + const curr = str[curr_idx]; if ( neighbor !== neighbor.toUpperCase() && curr !== curr.toLowerCase() ) { - outScore += CAMEL_BONUS; + out_score += CAMEL_BONUS; } - const isNeighbourSeparator = neighbor == "_" || neighbor == " "; - if (isNeighbourSeparator) { - outScore += SEPARATOR_BONUS; + const is_neighbour_separator = neighbor == "_" || neighbor == " "; + if (is_neighbour_separator) { + out_score += SEPARATOR_BONUS; } } else { // First letter - outScore += FIRST_LETTER_BONUS; + out_score += FIRST_LETTER_BONUS; } } // Return best result - if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { + if (recursive_match && (!matched || best_recursive_score > out_score)) { // Recursive score is better than "this" - matches = [...bestRecursiveMatches]; - outScore = bestRecursiveScore; - return [true, outScore]; + matches = [...best_recursive_matches]; + out_score = best_recursive_score; + return [true, out_score]; } else if (matched) { // "this" score is better than recursive - return [true, outScore]; + return [true, out_score]; } else { - return [false, outScore]; + return [false, out_score]; } } - return [false, outScore]; + return [false, out_score]; } diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index 8572160737..23da05e2b6 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -1,5 +1,5 @@ frappe.provide('frappe.search'); -import { fuzzyMatch } from './fuzzy_match.js' +import { fuzzy_match } from './fuzzy_match.js' frappe.search.utils = { @@ -535,12 +535,12 @@ frappe.search.utils = { }, fuzzy_search: function(keywords, _item) { - var match = fuzzyMatch(keywords, _item); + var match = fuzzy_match(keywords, _item); return match[1]; }, bolden_match_part: function(str, subseq) { - if(fuzzyMatch(subseq, str)[0] === false) { + if(fuzzy_match(subseq, str)[0] === false) { return str; } if(str.indexOf(subseq) == 0) { From 49e1e54e4a933144ef230a9bd6f171f186f3b46c Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Fri, 1 Apr 2022 11:21:14 +0530 Subject: [PATCH 11/40] fix: Fix sider issues --- .../js/frappe/ui/toolbar/search_utils.js | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index 23da05e2b6..c9fe3f85a8 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -1,5 +1,5 @@ frappe.provide('frappe.search'); -import { fuzzy_match } from './fuzzy_match.js' +import { fuzzy_match } from './fuzzy_match.js'; frappe.search.utils = { @@ -535,44 +535,44 @@ frappe.search.utils = { }, fuzzy_search: function(keywords, _item) { - var match = fuzzy_match(keywords, _item); - return match[1]; + var match = fuzzy_match(keywords, _item); + return match[1]; }, bolden_match_part: function(str, subseq) { if(fuzzy_match(subseq, str)[0] === false) { return str; } - if(str.indexOf(subseq) == 0) { - var tail = str.split(subseq)[1]; - return '' + subseq + '' + tail; - } - var rendered = ""; - var str_orig = str; - var str = str.toLowerCase(); - var str_len = str.length; - var subseq = subseq.toLowerCase(); + if(str.indexOf(subseq) == 0) { + var tail = str.split(subseq)[1]; + return '' + subseq + '' + tail; + } + var rendered = ""; + var str_orig = str; + var str = str.toLowerCase(); + var str_len = str.length; + var subseq = subseq.toLowerCase(); - outer: for(var i = 0, j = 0; i < subseq.length; i++) { - var sub_ch = subseq.charCodeAt(i); - while(j < str_len) { - if(str.charCodeAt(j) === sub_ch) { - var str_char = str_orig.charAt(j); - if(str_char === str_char.toLowerCase()) { - rendered += '' + subseq.charAt(i) + ''; - } else { - rendered += '' + subseq.charAt(i).toUpperCase() + ''; - } - j++; - continue outer; - } - rendered += str_orig.charAt(j); - j++; - } - return str_orig; - } - rendered += str_orig.slice(j); - return rendered; + outer: for(var i = 0, j = 0; i < subseq.length; i++) { + var sub_ch = subseq.charCodeAt(i); + while(j < str_len) { + if(str.charCodeAt(j) === sub_ch) { + var str_char = str_orig.charAt(j); + if(str_char === str_char.toLowerCase()) { + rendered += '' + subseq.charAt(i) + ''; + } else { + rendered += '' + subseq.charAt(i).toUpperCase() + ''; + } + j++; + continue outer; + } + rendered += str_orig.charAt(j); + j++; + } + return str_orig; + } + rendered += str_orig.slice(j); + return rendered; }, get_executables(keywords) { From c94edc21f41a4a44feb2ef235fd6546fc576da13 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Fri, 1 Apr 2022 11:24:30 +0530 Subject: [PATCH 12/40] fix: Code clean up --- .../js/frappe/ui/toolbar/fuzzy_match.js | 17 +++---- .../js/frappe/ui/toolbar/search_utils.js | 46 +++++++++---------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js index 0d122faad4..59fccbccc5 100644 --- a/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js +++ b/frappe/public/js/frappe/ui/toolbar/fuzzy_match.js @@ -31,7 +31,7 @@ const UNMATCHED_LETTER_PENALTY = -1; * @returns [boolean, number] a boolean which tells if pattern was * found or not and a search score */ -function fuzzy_match(pattern, str) { +export function fuzzy_match(pattern, str) { const recursion_count = 0; const recursion_limit = 10; const matches = []; @@ -42,7 +42,7 @@ function fuzzy_match(pattern, str) { str, 0 /* pattern_cur_index */, 0 /* str_curr_index */, - null /* src_matces */, + null /* src_matches */, matches, max_matches, 0 /* next_match */, @@ -56,7 +56,7 @@ function fuzzy_match_recursive( str, pattern_cur_index, str_curr_index, - src_matces, + src_matches, matches, max_matches, next_match, @@ -91,8 +91,8 @@ function fuzzy_match_recursive( return [false, out_score]; } - if (first_match && src_matces) { - matches = [...src_matces]; + if (first_match && src_matches) { + matches = [...src_matches]; first_match = false; } @@ -116,7 +116,7 @@ function fuzzy_match_recursive( best_recursive_matches = [...recursive_matches]; best_recursive_score = recursive_score; } - recursiveMatch = true; + recursive_match = true; } matches[next_match++] = str_curr_index; @@ -189,8 +189,3 @@ function fuzzy_match_recursive( } return [false, out_score]; } - - -module.exports = { - fuzzy_match -}; diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index c9fe3f85a8..db411feffd 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -540,36 +540,36 @@ frappe.search.utils = { }, bolden_match_part: function(str, subseq) { - if(fuzzy_match(subseq, str)[0] === false) { + if (fuzzy_match(subseq, str)[0] === false) { return str; } - if(str.indexOf(subseq) == 0) { - var tail = str.split(subseq)[1]; - return '' + subseq + '' + tail; + if (str.indexOf(subseq) == 0) { + var tail = str.split(subseq)[1]; + return '' + subseq + '' + tail; } var rendered = ""; var str_orig = str; - var str = str.toLowerCase(); var str_len = str.length; - var subseq = subseq.toLowerCase(); + str = str.toLowerCase(); + subseq = subseq.toLowerCase(); - outer: for(var i = 0, j = 0; i < subseq.length; i++) { - var sub_ch = subseq.charCodeAt(i); - while(j < str_len) { - if(str.charCodeAt(j) === sub_ch) { - var str_char = str_orig.charAt(j); - if(str_char === str_char.toLowerCase()) { - rendered += '' + subseq.charAt(i) + ''; - } else { - rendered += '' + subseq.charAt(i).toUpperCase() + ''; - } - j++; - continue outer; - } - rendered += str_orig.charAt(j); - j++; - } - return str_orig; + outer: for (var i = 0, j = 0; i < subseq.length; i++) { + var sub_ch = subseq.charCodeAt(i); + while (j < str_len) { + if (str.charCodeAt(j) === sub_ch) { + var str_char = str_orig.charAt(j); + if (str_char === str_char.toLowerCase()) { + rendered += '' + subseq.charAt(i) + ''; + } else { + rendered += '' + subseq.charAt(i).toUpperCase() + ''; + } + j++; + continue outer; + } + rendered += str_orig.charAt(j); + j++; + } + return str_orig; } rendered += str_orig.slice(j); return rendered; From f406aab62d88e82d298b3a21de0de758d3fda900 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Fri, 1 Apr 2022 12:30:06 +0530 Subject: [PATCH 13/40] fix: Fix search issue with translated strings --- frappe/public/js/frappe/ui/toolbar/search_utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index db411feffd..1571522489 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -535,7 +535,9 @@ frappe.search.utils = { }, fuzzy_search: function(keywords, _item) { - var match = fuzzy_match(keywords, _item); + keywords = keywords || ''; + var item = __(_item || ''); + var match = fuzzy_match(keywords, item); return match[1]; }, From 7e1a0ed5de92fefa183b0626cec5d9ba62b8a687 Mon Sep 17 00:00:00 2001 From: phot0n Date: Thu, 31 Mar 2022 22:20:39 +0530 Subject: [PATCH 14/40] test: test for fieldname which start with int --- frappe/tests/test_db_query.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 62842dc167..47a5e775ee 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -514,6 +514,33 @@ class TestReportview(unittest.TestCase): dt.delete(ignore_permissions=True) + def test_fieldname_starting_with_int(self): + from frappe.core.doctype.doctype.test_doctype import new_doctype + + dt = new_doctype( + "dt_with_int_named_fieldname", + fields=[{ + "label": "1field", + "fieldname": "1field", + "fieldtype": "Int" + }] + ).insert(ignore_permissions=True) + + frappe.get_doc({ + "doctype": "dt_with_int_named_fieldname", + "1field": 10 + }).insert(ignore_permissions=True) + + + query = DatabaseQuery("dt_with_int_named_fieldname") + self.assertTrue(query.execute(filters={"1field": 10})) + self.assertTrue(query.execute(filters={"1field": ["like", "1%"]})) + self.assertTrue(query.execute(filters={"1field": ["in", "1,2,10"]})) + self.assertTrue(query.execute(filters={"1field": ["is", "set"]})) + self.assertFalse(query.execute(filters={"1field": ["not like", "1%"]})) + + dt.delete() + def add_child_table_to_blog_post(): child_table = frappe.get_doc({ From e9b1cdcdca0078452c16307617537f5d03fdc42e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 23 Mar 2022 18:16:37 +0530 Subject: [PATCH 15/40] refactor(BaseDocument)!: dont return `__dict__` if `key` is falsy --- frappe/model/base_document.py | 42 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 3e9d1317e8..0ba8a8ff17 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -133,31 +133,29 @@ class BaseDocument(object): return frappe.db.get_value(self.doctype, self.name, key) def get(self, key=None, filters=None, limit=None, default=None): - if key: - if isinstance(key, dict): - return _filter(self.get_all_children(), key, limit=limit) - if filters: - if isinstance(filters, dict): - value = _filter(self.__dict__.get(key, []), filters, limit=limit) - else: - default = filters - filters = None - value = self.__dict__.get(key, default) + if isinstance(key, dict): + return _filter(self.get_all_children(), key, limit=limit) + + if filters: + if isinstance(filters, dict): + value = _filter(self.__dict__.get(key, []), filters, limit=limit) else: + default = filters + filters = None value = self.__dict__.get(key, default) - - if value is None and key in ( - d.fieldname for d in self.meta.get_table_fields() - ): - value = [] - self.set(key, value) - - if limit and isinstance(value, (list, tuple)) and len(value) > limit: - value = value[:limit] - - return value else: - return self.__dict__ + value = self.__dict__.get(key, default) + + if value is None and key in ( + d.fieldname for d in self.meta.get_table_fields() + ): + value = [] + self.set(key, value) + + if limit and isinstance(value, (list, tuple)) and len(value) > limit: + value = value[:limit] + + return value def getone(self, key, filters=None): return self.get(key, filters=filters, limit=1)[0] From 73fc2f1d5944a62c230c5fbee3c3497c1ce14756 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 4 Apr 2022 11:18:46 +0530 Subject: [PATCH 16/40] fix: require `key` in `doc.get` --- frappe/model/base_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 0ba8a8ff17..57591d01d5 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -132,7 +132,7 @@ class BaseDocument(object): def get_db_value(self, key): return frappe.db.get_value(self.doctype, self.name, key) - def get(self, key=None, filters=None, limit=None, default=None): + def get(self, key, filters=None, limit=None, default=None): if isinstance(key, dict): return _filter(self.get_all_children(), key, limit=limit) From 3cabfdaa1d9be4cde164208f0fe5b811c946e2f0 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Tue, 5 Apr 2022 09:18:54 +0530 Subject: [PATCH 17/40] test: Fix cypress tests --- cypress/integration/awesome_bar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 8e503cce46..053d015366 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -13,7 +13,7 @@ context('Awesome Bar', () => { it('navigates to doctype list', () => { cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 700 }); cy.get('.awesomplete').findByRole('listbox').should('be.visible'); - cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{downarrow}{enter}', { delay: 700 }); + cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{enter}', { delay: 700 }); cy.get('.title-text').should('contain', 'To Do'); @@ -22,7 +22,7 @@ context('Awesome Bar', () => { it('find text in doctype list', () => { cy.findByPlaceholderText('Search or type a command (Ctrl + G)') - .type('test in todo{downarrow}{enter}', { delay: 700 }); + .type('test in todo{enter}', { delay: 700 }); cy.get('.title-text').should('contain', 'To Do'); @@ -32,7 +32,7 @@ context('Awesome Bar', () => { it('navigates to new form', () => { cy.findByPlaceholderText('Search or type a command (Ctrl + G)') - .type('new blog post{downarrow}{enter}', { delay: 700 }); + .type('new blog post{enter}', { delay: 700 }); cy.get('.title-text:visible').should('have.text', 'New Blog Post'); }); From 4c901a7074cdc0da13e549420f0b0ba636c7fbdd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 5 Apr 2022 20:38:42 +0200 Subject: [PATCH 18/40] fix: stabilize leaflet map - disable flyToBounds - invalidateSize after loading to avoid gray tiles --- frappe/public/js/frappe/form/controls/geolocation.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 280eac3941..7a1df64f28 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -58,7 +58,7 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f })); this.add_non_group_layers(data_layers, this.editableLayers); try { - this.map.flyToBounds(this.editableLayers.getBounds(), { + this.map.fitBounds(this.editableLayers.getBounds(), { padding: [50,50] }); } @@ -66,10 +66,10 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f // suppress error if layer has a point. } this.editableLayers.addTo(this.map); - this.map._onResize(); - } else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) { - this.locate_control.start(); + } else { + this.map.setView(frappe.utils.map_defaults.center, frappe.utils.map_defaults.zoom); } + this.map.invalidateSize(); } bind_leaflet_map() { @@ -97,8 +97,7 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f }); L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/'; - this.map = L.map(this.map_id).setView(frappe.utils.map_defaults.center, - frappe.utils.map_defaults.zoom); + this.map = L.map(this.map_id); L.tileLayer(frappe.utils.map_defaults.tiles, frappe.utils.map_defaults.options).addTo(this.map); From 253d0cabb43e3ce045929c712ee38ff7d5cf9d70 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 6 Apr 2022 10:04:33 +0530 Subject: [PATCH 19/40] fix: Move custom-actions under page-actions Change in custom-actions was affecting custom-actions in timeline the issue was introduced with https://github.com/frappe/frappe/pull/16470 --- frappe/public/scss/desk/page.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/public/scss/desk/page.scss b/frappe/public/scss/desk/page.scss index 5206e2919c..b0a24eed38 100644 --- a/frappe/public/scss/desk/page.scss +++ b/frappe/public/scss/desk/page.scss @@ -51,11 +51,6 @@ } } -.custom-actions { - display: flex; - align-items: center; -} - .page-actions { align-items: center; .btn { @@ -72,6 +67,11 @@ .custom-btn-group { display: inline-flex; } + + .custom-actions { + display: flex; + align-items: center; + } } .layout-main-section-wrapper { From 7c467366549d89253d9f19392d0958cfd64c1ec8 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Wed, 6 Apr 2022 12:26:26 +0530 Subject: [PATCH 20/40] fix: Use app name for parsing app name --- frappe/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index ffb595e9ad..c7dacc4ac1 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -222,7 +222,7 @@ def install_app(name, verbose=False, set_as_patched=True): # install pre-requisites if app_hooks.required_apps: for app in app_hooks.required_apps: - name = parse_app_name(name) + name = parse_app_name(app) install_app(name, verbose=verbose) frappe.flags.in_install = name From 9943423a1ff3897ed1b9e04d6916aedb8d430255 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 6 Apr 2022 16:10:14 +0530 Subject: [PATCH 21/40] fix: strip html from blog comments to prevent spam --- frappe/templates/includes/comments/comment.html | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frappe/templates/includes/comments/comment.html b/frappe/templates/includes/comments/comment.html index e0fc1c3c54..5be36d65a9 100644 --- a/frappe/templates/includes/comments/comment.html +++ b/frappe/templates/includes/comments/comment.html @@ -1,13 +1,17 @@ {% from "frappe/templates/includes/avatar_macro.html" import avatar %} -
+
- {{ avatar(user_id=(comment.comment_email or comment.sender), size='avatar-medium') }} + {{ avatar(user_id=(frappe.utils.strip_html(comment.comment_email or comment.sender)), size='avatar-medium') }}
-
- {{ comment.sender_full_name or comment.comment_by }} - {{ frappe.utils.pretty_date(comment.creation) }} +
+ + {{ frappe.utils.strip_html(comment.sender_full_name or comment.comment_by) | e }} + + + {{ frappe.utils.pretty_date(comment.creation) }} +
{{ comment.content | markdown }}
From e13c74b53ffba6be8989e2f52d1875c46c87717e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 6 Apr 2022 16:28:57 +0530 Subject: [PATCH 22/40] fix: strip html from comment content --- frappe/templates/includes/comments/comment.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/templates/includes/comments/comment.html b/frappe/templates/includes/comments/comment.html index 5be36d65a9..4713ee498d 100644 --- a/frappe/templates/includes/comments/comment.html +++ b/frappe/templates/includes/comments/comment.html @@ -13,6 +13,6 @@ {{ frappe.utils.pretty_date(comment.creation) }}
-
{{ comment.content | markdown }}
+
{{ frappe.utils.strip_html(comment.content) | markdown }}
\ No newline at end of file From ae335f5e1c96ac8febb7a42ac6c19b6ae3393a70 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 6 Apr 2022 16:29:22 +0530 Subject: [PATCH 23/40] test: spam links shouldn't render on blog post --- .../doctype/blog_post/test_blog_post.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index d649d25f7e..575b6c0fc0 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -117,6 +117,34 @@ class TestBlogPost(unittest.TestCase): frappe.flags.force_website_cache = True + def test_spam_comments(self): + # Make a temporary Blog Post (and a Blog Category) + blog = make_test_blog('Test Spam Comment') + + # Create a spam comment + frappe.get_doc( + doctype="Comment", + comment_type="Comment", + reference_doctype="Blog Post", + reference_name=blog.name, + comment_email="spam", + comment_by="spam", + published=1, + content="More spam content. spam with link.", + ).insert() + + # Visit the blog post page + set_request(path=blog.route) + blog_page_response = get_response() + blog_page_html = frappe.safe_decode(blog_page_response.get_data()) + + self.assertNotIn('spam', blog_page_html) + self.assertIn("More spam content. spam with link.", blog_page_html) + + # Cleanup + frappe.delete_doc("Blog Post", blog.name) + frappe.delete_doc("Blog Category", blog.blog_category) + def scrub(text): return WebsiteGenerator.scrub(None, text) From 5483caa8f59074b53ef541f0c26d0e1a9246dbd9 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Wed, 6 Apr 2022 18:56:11 +0530 Subject: [PATCH 24/40] test: Added test script for control type "Date" --- cypress/integration/control_date.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index c45602b4d9..0feffbb05d 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -63,14 +63,13 @@ context('Date Control', () => { cy.get('.datepicker--button').click(); //Picking up the todays date - const todaysDate = Cypress.moment().format('MM-DD-YYYY'); - cy.log(todaysDate); + const todays_date = Cypress.moment().format('MM-DD-YYYY'); //Verifying if clicking on "Today" button matches today's date - cy.get_field('date', 'Date').should('have.value', todaysDate); + cy.get_field('date', 'Date').should('have.value', todays_date); }); - it.only('Configuring first day of the week', () => { + it('Configuring first day of the week', () => { //Visiting "System Settings" page cy.visit('/app/system-settings/System%20Settings'); From 27cbcc3e8dfd0eb2e7a4fd4027cf0d728a1c2ef9 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Thu, 7 Apr 2022 11:19:08 +0530 Subject: [PATCH 25/40] test: Removed first_day_of_week test case --- cypress/integration/control_date.js | 48 ----------------------------- 1 file changed, 48 deletions(-) diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js index 0feffbb05d..35c585306c 100644 --- a/cypress/integration/control_date.js +++ b/cypress/integration/control_date.js @@ -68,52 +68,4 @@ context('Date Control', () => { //Verifying if clicking on "Today" button matches today's date cy.get_field('date', 'Date').should('have.value', todays_date); }); - - it('Configuring first day of the week', () => { - //Visiting "System Settings" page - cy.visit('/app/system-settings/System%20Settings'); - - //Visiting the "Date and Number Format" section - cy.contains('Date and Number Format').click(); - - //Changing the configuration for "First day of the week" field - cy.get('select[data-fieldname="first_day_of_the_week"]').select('Tuesday'); - cy.get('.page-head .page-actions').findByRole('button', {name: 'Save'}).click(); - cy.new_form('Test Date Control'); - cy.get_field('date', 'Date').click(); - - //Checking if the first day shown in the datepicker is the one which is configured in the System Settings Page - cy.get('.datepicker--days-names').eq(0).should('contain.text', 'Tu'); - cy.visit('/app/doctype'); - - //Adding filter in the doctype list - cy.add_filter(); - cy.get('.fieldname-select-area').type('Created On{enter}'); - cy.get('.filter-field > .form-group > .input-with-feedback').click(); - - //Checking if the first day shown in the datepicker is the one which is configured in the System Settings Page - cy.get('.datepicker--days-names').eq(0).should('contain.text', 'Tu'); - - //Adding event - cy.visit('/app/event'); - cy.click_listview_primary_button('Add Event'); - cy.get('textarea[data-fieldname=subject]').type('Test'); - cy.fill_field('starts_on', '01-01-2022 00:00:00', 'Datetime'); - cy.click_listview_primary_button('Save'); - cy.visit('/app/event'); - cy.get('.custom-btn-group > .btn').click(); - - //Opening Calendar view for the event created - cy.get('[data-view="Calendar"] > .grey-link').click(); - - //Checking if the calendar view has the first day as the configured day in the System Settings Page - cy.get('.fc-head-container').eq(0).should('contain.text', 'Tue'); - - //Deleting the created event - cy.visit('/app/event'); - cy.get('.list-row-checkbox').eq(0).click(); - cy.get('.actions-btn-group > .btn').contains('Actions').click(); - cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click(); - cy.click_modal_primary_button('Yes'); - }); }); \ No newline at end of file From a096b85b52b84a26e9610c4c561f7d708eb2dd86 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 7 Apr 2022 12:23:41 +0530 Subject: [PATCH 26/40] fix: Read Only fields visible despite no value --- .../js/frappe/form/controls/base_control.js | 16 ++++++++++++---- frappe/public/js/frappe/form/controls/control.js | 1 - frappe/public/js/frappe/form/controls/data.js | 2 ++ .../public/js/frappe/form/controls/read_only.js | 8 -------- frappe/public/js/frappe/model/perm.js | 7 +++++-- frappe/public/js/frappe/ui/field_group.js | 13 ++++++------- 6 files changed, 25 insertions(+), 22 deletions(-) delete mode 100644 frappe/public/js/frappe/form/controls/read_only.js diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index 4ee52d16b8..e22235f60f 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -44,6 +44,8 @@ frappe.ui.form.Control = class BaseControl { } if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) { + let status = "Write"; + // like in case of a dialog box if (cint(this.df.hidden)) { // eslint-disable-next-line @@ -55,10 +57,10 @@ frappe.ui.form.Control = class BaseControl { if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console return "None"; - } else if (cint(this.df.read_only || this.df.is_virtual)) { + } else if (cint(this.df.read_only || this.df.is_virtual || this.df.fieldtype === "Read Only")) { // eslint-disable-next-line if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console - return "Read"; + status = "Read"; } else if ((this.grid && this.grid.display_status == 'Read') || @@ -67,10 +69,16 @@ frappe.ui.form.Control = class BaseControl { this.layout.grid.display_status == 'Read')) { // parent grid is read if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console - return "Read"; + status = "Read"; } - return "Write"; + if ( + status === "Read" && + is_null(this.value) && + !in_list(["HTML", "Image", "Button"], this.df.fieldtype) + ) status = "None"; + + return status; } var status = frappe.perm.get_field_display_status(this.df, diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index bd04938e35..90e8697b1c 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -23,7 +23,6 @@ import './table'; import './color'; import './signature'; import './password'; -import './read_only'; import './button'; import './html'; import './markdown_editor'; diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index f4c9849528..95abba616a 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -262,3 +262,5 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp return this.grid || this.layout && this.layout.grid; } }; + +frappe.ui.form.ControlReadOnly = frappe.ui.form.ControlData; diff --git a/frappe/public/js/frappe/form/controls/read_only.js b/frappe/public/js/frappe/form/controls/read_only.js deleted file mode 100644 index 2f1d1a2bca..0000000000 --- a/frappe/public/js/frappe/form/controls/read_only.js +++ /dev/null @@ -1,8 +0,0 @@ -frappe.ui.form.ControlReadOnly = class ControlReadOnly extends frappe.ui.form.ControlData { - get_status(explain) { - var status = super.get_status(explain); - if(status==="Write") - status = "Read"; - return; - } -}; diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js index 0eabfdd337..3ea9c6bc95 100644 --- a/frappe/public/js/frappe/model/perm.js +++ b/frappe/public/js/frappe/model/perm.js @@ -225,7 +225,10 @@ $.extend(frappe.perm, { if (explain) console.log("By Workflow:" + status); // read only field is checked - if (status === "Write" && cint(df.read_only)) { + if (status === "Write" && ( + cint(df.read_only) || + df.fieldtype === "Read Only" + )) { status = "Read"; } if (explain) console.log("By Read Only:" + status); @@ -276,4 +279,4 @@ $.extend(frappe.perm, { return allowed_docs; } } -}); \ No newline at end of file +}); diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 1936f5115e..479c020fbb 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -22,17 +22,15 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { super.make(); this.refresh(); // set default - $.each(this.fields_list, function(i, field) { - if (field.df["default"]) { - let def_value = field.df["default"]; + $.each(this.fields_list, (_, field) => { + if (!is_null(field.df.default)) { + let def_value = field.df.default; - if (def_value == 'Today' && field.df["fieldtype"] == 'Date') { + if (def_value === "Today" && field.df.fieldtype === "Date") { def_value = frappe.datetime.get_today(); } - field.set_input(def_value); - // if default and has depends_on, render its fields. - me.refresh_dependency(); + this.set_value(field.df.fieldname, def_value); } }) @@ -129,6 +127,7 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { if (f) { f.set_value(val).then(() => { f.set_input(val); + f.refresh(); this.refresh_dependency(); resolve(); }); From 428b0bc4b130545bda186daddc2575a9b64839ef Mon Sep 17 00:00:00 2001 From: kamaljohnson Date: Thu, 7 Apr 2022 19:46:53 +0530 Subject: [PATCH 27/40] feat: add after insert event to server script this option was not available in the events dropdown. --- frappe/core/doctype/server_script/server_script.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 520c0008c5..548d21bb60 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -49,7 +49,7 @@ "fieldname": "doctype_event", "fieldtype": "Select", "label": "DocType Event", - "options": "Before Insert\nBefore Validate\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)" + "options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)" }, { "depends_on": "eval:doc.script_type==='API'", @@ -109,10 +109,11 @@ "link_fieldname": "server_script" } ], - "modified": "2021-09-04 12:02:43.671240", + "modified": "2022-04-07 19:41:23.178772", "modified_by": "Administrator", "module": "Core", "name": "Server Script", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -130,5 +131,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 7ceb8fd74799ca17909134244503d67e4c34db81 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 05:35:43 +0530 Subject: [PATCH 28/40] fix(style): minor style fixes --- frappe/public/scss/website/base.scss | 12 ++++++------ frappe/public/scss/website/footer.scss | 2 +- frappe/public/scss/website/page_builder.scss | 2 +- .../section_with_videos/section_with_videos.html | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index 7e5d9b5b66..5a6791815f 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -1,10 +1,10 @@ $font-size-xs: 0.7rem; $font-size-sm: 0.85rem; $font-size-lg: 1.12rem; -$font-size-xl: 1.25rem; -$font-size-2xl: 1.5rem; -$font-size-3xl: 2rem; -$font-size-4xl: 2.5rem; +$font-size-xl: 1.2rem; +$font-size-2xl: 1.4rem; +$font-size-3xl: 1.9rem; +$font-size-4xl: 2.4rem; $font-size-5xl: 3rem; $font-size-6xl: 4rem; @@ -51,12 +51,12 @@ h1 { h2 { font-size: $font-size-2xl; margin-top: 2rem; - margin-bottom: 0.75rem; + margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { font-size: $font-size-3xl; margin-top: 4rem; - margin-bottom: 1rem; + margin-bottom: 0.75rem; } @include media-breakpoint-up(xl) { font-size: $font-size-4xl; diff --git a/frappe/public/scss/website/footer.scss b/frappe/public/scss/website/footer.scss index e5dae72808..9a36d7ab6d 100644 --- a/frappe/public/scss/website/footer.scss +++ b/frappe/public/scss/website/footer.scss @@ -1,5 +1,5 @@ .web-footer { - margin: 5rem 0; + padding: 3rem 0; min-height: 140px; background-color: var(--fg-color); border-top: 1px solid $border-color; diff --git a/frappe/public/scss/website/page_builder.scss b/frappe/public/scss/website/page_builder.scss index 21058dcf53..cb4cb7ed4b 100644 --- a/frappe/public/scss/website/page_builder.scss +++ b/frappe/public/scss/website/page_builder.scss @@ -549,7 +549,7 @@ font-weight: 600; @include media-breakpoint-up(md) { - font-size: $font-size-2xl; + font-size: $font-size-xl; } } diff --git a/frappe/website/web_template/section_with_videos/section_with_videos.html b/frappe/website/web_template/section_with_videos/section_with_videos.html index 369d469e87..30bea5dead 100644 --- a/frappe/website/web_template/section_with_videos/section_with_videos.html +++ b/frappe/website/web_template/section_with_videos/section_with_videos.html @@ -13,7 +13,7 @@ {%- if video.title -%} -

{{ video.title }}

+

{{ video.title }}

{%- endif -%} {%- if video.content -%}

{{ video.content }}

From ddd807e818e2c00e5f82fe772d91fd2ad72a7cb5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 05:57:06 +0530 Subject: [PATCH 29/40] fix(style): minor style fixes --- frappe/public/scss/website/footer.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/scss/website/footer.scss b/frappe/public/scss/website/footer.scss index 9a36d7ab6d..ae7c15ab05 100644 --- a/frappe/public/scss/website/footer.scss +++ b/frappe/public/scss/website/footer.scss @@ -1,5 +1,6 @@ .web-footer { padding: 3rem 0; + margin-top: 3rem; min-height: 140px; background-color: var(--fg-color); border-top: 1px solid $border-color; From 63ad1efe3d5c753d5b81bfefa49324203b3a0626 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 06:12:32 +0530 Subject: [PATCH 30/40] fix(style): minor style fixes --- frappe/public/scss/desk/avatar.scss | 2 ++ frappe/public/scss/website/base.scss | 1 - frappe/public/scss/website/blog.scss | 2 +- frappe/public/scss/website/footer.scss | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/scss/desk/avatar.scss b/frappe/public/scss/desk/avatar.scss index 638256c21d..073f90e20f 100644 --- a/frappe/public/scss/desk/avatar.scss +++ b/frappe/public/scss/desk/avatar.scss @@ -73,6 +73,7 @@ display: inline-block; width: 100%; height: 100%; + object-fit: cover; background-color: var(--avatar-frame-bg); background-size: cover; background-repeat: no-repeat; @@ -145,6 +146,7 @@ .standard-image { width: 100%; height: 100%; + object-fit: cover; display: flex; justify-content: center; align-items: center; diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index 5a6791815f..abb2cec4dc 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -37,7 +37,6 @@ h1 { @include media-breakpoint-up(sm) { font-size: $font-size-5xl; - line-height: 2.5rem; margin-top: 3.5rem; margin-bottom: 1.25rem; } diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index 4f289db125..a6f06df112 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -98,7 +98,7 @@ .blog-header { margin-bottom: 3rem; - margin-top: 3rem; + margin-top: 5rem; } } diff --git a/frappe/public/scss/website/footer.scss b/frappe/public/scss/website/footer.scss index ae7c15ab05..3a15fa2017 100644 --- a/frappe/public/scss/website/footer.scss +++ b/frappe/public/scss/website/footer.scss @@ -1,6 +1,6 @@ .web-footer { padding: 3rem 0; - margin-top: 3rem; + margin-top: 4rem; min-height: 140px; background-color: var(--fg-color); border-top: 1px solid $border-color; From 4c350f41a2e7d8ce125850bb1d32bc564ba3cfdf Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 06:37:44 +0530 Subject: [PATCH 31/40] fix(style): minor style fixes --- frappe/public/scss/website/base.scss | 36 +++++++------------ frappe/public/scss/website/blog.scss | 5 +++ frappe/public/scss/website/error-state.scss | 1 + frappe/public/scss/website/footer.scss | 1 - .../blog_post/templates/blog_post.html | 2 +- .../blog_post/templates/blog_post_list.html | 4 +-- 6 files changed, 22 insertions(+), 27 deletions(-) diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index abb2cec4dc..90453fc9db 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -1,13 +1,3 @@ -$font-size-xs: 0.7rem; -$font-size-sm: 0.85rem; -$font-size-lg: 1.12rem; -$font-size-xl: 1.2rem; -$font-size-2xl: 1.4rem; -$font-size-3xl: 1.9rem; -$font-size-4xl: 2.4rem; -$font-size-5xl: 3rem; -$font-size-6xl: 4rem; - html { height: 100%; } @@ -29,66 +19,66 @@ h1, h2, h3, h4 { } h1 { - font-size: $font-size-3xl; + font-size: 2rem; line-height: 1.25; letter-spacing: -0.025em; margin-top: 3rem; margin-bottom: 0.75rem; @include media-breakpoint-up(sm) { - font-size: $font-size-5xl; + font-size: 2.5rem; margin-top: 3.5rem; margin-bottom: 1.25rem; } @include media-breakpoint-up(xl) { - font-size: $font-size-6xl; + font-size: 3.5rem; line-height: 1; margin-top: 4rem; } } h2 { - font-size: $font-size-2xl; + font-size: 1.4rem; margin-top: 2rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { - font-size: $font-size-3xl; + font-size: 2rem; margin-top: 4rem; margin-bottom: 0.75rem; } @include media-breakpoint-up(xl) { - font-size: $font-size-4xl; + font-size: 2.5rem; margin-top: 4rem; } } h3 { - font-size: $font-size-xl; + font-size: 1.2rem; margin-top: 1.5rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { - font-size: $font-size-2xl; + font-size: 1.4rem; margin-top: 2.5rem; } @include media-breakpoint-up(xl) { - font-size: $font-size-3xl; + font-size: 1.9rem; margin-top: 3.5rem; } } h4 { - font-size: $font-size-lg; + font-size: 1.1rem; margin-top: 1rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { - font-size: $font-size-xl; + font-size: 1.3rem; margin-top: 1.25rem; } @include media-breakpoint-up(xl) { - font-size: $font-size-2xl; + font-size: 1.5rem; margin-top: 1.75rem; } @@ -98,5 +88,5 @@ h4 { } .btn.btn-lg { - font-size: $font-size-lg; + font-size: 1.1rem; } diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index a6f06df112..b16e088f69 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -102,6 +102,11 @@ } } + .blog-comments { + margin-top: 1rem; + margin-bottom: 5rem; + } + .feedback-item svg { vertical-align: sub; diff --git a/frappe/public/scss/website/error-state.scss b/frappe/public/scss/website/error-state.scss index c869e9e1df..7a83fc0084 100644 --- a/frappe/public/scss/website/error-state.scss +++ b/frappe/public/scss/website/error-state.scss @@ -1,4 +1,5 @@ .error-page { + margin: 3rem 0; text-align: center; .img-404 { diff --git a/frappe/public/scss/website/footer.scss b/frappe/public/scss/website/footer.scss index 3a15fa2017..9a36d7ab6d 100644 --- a/frappe/public/scss/website/footer.scss +++ b/frappe/public/scss/website/footer.scss @@ -1,6 +1,5 @@ .web-footer { padding: 3rem 0; - margin-top: 4rem; min-height: 140px; background-color: var(--fg-color); border-top: 1px solid $border-color; diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index d8ece09f46..4bab50c33e 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -66,7 +66,7 @@ {% endif %} {% if not disable_comments %} -
+
{% include 'templates/includes/comments/comments.html' %}
{% endif %} diff --git a/frappe/website/doctype/blog_post/templates/blog_post_list.html b/frappe/website/doctype/blog_post/templates/blog_post_list.html index 1aa30316fe..4cb53d065c 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post_list.html +++ b/frappe/website/doctype/blog_post/templates/blog_post_list.html @@ -8,8 +8,8 @@
-

{{ blog_title or _('Blog') }}

-

{{ blog_introduction or '' }}

+

{{ blog_title or _('Blog') }}

+

{{ blog_introduction or '' }}

From 96a62339533f2c8cffca3e8db9250b88817af48f Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 07:19:44 +0530 Subject: [PATCH 32/40] fix(style): minor style fixes --- frappe/public/scss/website/base.scss | 4 ++++ frappe/public/scss/website/markdown.scss | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index 90453fc9db..90c98cfb45 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -87,6 +87,10 @@ h4 { } } +p { + line-height: 1.7; +} + .btn.btn-lg { font-size: 1.1rem; } diff --git a/frappe/public/scss/website/markdown.scss b/frappe/public/scss/website/markdown.scss index c2592b61e9..178b29e505 100644 --- a/frappe/public/scss/website/markdown.scss +++ b/frappe/public/scss/website/markdown.scss @@ -30,7 +30,8 @@ } p, li { - font-size: $font-size-lg; + font-size: 1.1rem; + line-height: 1.7; } li { From c0d8dfed157e2eca11b1d1aa73d691f4bdf6e29c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 07:50:07 +0530 Subject: [PATCH 33/40] fix(style): minor style fixes --- frappe/public/scss/website/markdown.scss | 5 ++++- frappe/public/scss/website/page_builder.scss | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frappe/public/scss/website/markdown.scss b/frappe/public/scss/website/markdown.scss index 178b29e505..6b5f7cbb6e 100644 --- a/frappe/public/scss/website/markdown.scss +++ b/frappe/public/scss/website/markdown.scss @@ -5,7 +5,6 @@ } .from-markdown { - color: $gray-700; line-height: 1.7; > :first-child { @@ -34,6 +33,10 @@ line-height: 1.7; } + p.lead { + @extend .lead; + } + li { padding-top: 1px; padding-bottom: 1px; diff --git a/frappe/public/scss/website/page_builder.scss b/frappe/public/scss/website/page_builder.scss index cb4cb7ed4b..da6c6b8e58 100644 --- a/frappe/public/scss/website/page_builder.scss +++ b/frappe/public/scss/website/page_builder.scss @@ -23,9 +23,11 @@ } .lead { + color: var(--text-muted); font-weight: normal; font-size: 1.25rem; - margin-bottom: 1.5rem; + margin-top: -1rem; + margin-bottom: 2.5rem; } .hero-subtitle { From c54f694e7c57aa781c03ce6e5a1f61fc3308a1ed Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 07:56:23 +0530 Subject: [PATCH 34/40] fix(style): minor style fixes --- frappe/public/scss/website/base.scss | 6 +++--- frappe/public/scss/website/page_builder.scss | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index 90c98cfb45..30f7b81008 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -70,16 +70,16 @@ h3 { h4 { font-size: 1.1rem; - margin-top: 1rem; + margin-top: 1.7rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { font-size: 1.3rem; - margin-top: 1.25rem; + margin-top: 2rem; } @include media-breakpoint-up(xl) { font-size: 1.5rem; - margin-top: 1.75rem; + margin-top: 2.5rem; } a { diff --git a/frappe/public/scss/website/page_builder.scss b/frappe/public/scss/website/page_builder.scss index da6c6b8e58..d16ea4e27d 100644 --- a/frappe/public/scss/website/page_builder.scss +++ b/frappe/public/scss/website/page_builder.scss @@ -16,12 +16,6 @@ } } -.hero-title, .hero-subtitle { - max-width: 42rem; - margin-top: 0rem; - margin-bottom: 0.5rem; -} - .lead { color: var(--text-muted); font-weight: normal; @@ -40,6 +34,12 @@ } } +.hero-title, .hero-subtitle { + max-width: 42rem; + margin-top: 0rem; + margin-bottom: 0.5rem; +} + .hero.align-center { h1, .hero-title, .hero-subtitle, .hero-buttons { text-align: center; From a682516f712504fd92c0a71933ad94dd72735cb7 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Apr 2022 10:38:03 +0530 Subject: [PATCH 35/40] fix(style): minor style fixes --- frappe/public/scss/website/base.scss | 8 ++++---- frappe/public/scss/website/blog.scss | 4 ++++ frappe/public/scss/website/index.scss | 4 ++-- frappe/public/scss/website/markdown.scss | 5 ++++- frappe/public/scss/website/page_builder.scss | 11 +++++++++-- .../section_with_cta/section_with_cta.html | 1 + .../section_with_features/section_with_features.html | 3 ++- .../section_with_small_cta.html | 1 + 8 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frappe/public/scss/website/base.scss b/frappe/public/scss/website/base.scss index 30f7b81008..d666bcd410 100644 --- a/frappe/public/scss/website/base.scss +++ b/frappe/public/scss/website/base.scss @@ -55,7 +55,7 @@ h2 { h3 { font-size: 1.2rem; - margin-top: 1.5rem; + margin-top: 2rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { @@ -70,16 +70,16 @@ h3 { h4 { font-size: 1.1rem; - margin-top: 1.7rem; + margin-top: 2rem; margin-bottom: 0.5rem; @include media-breakpoint-up(sm) { font-size: 1.3rem; - margin-top: 2rem; + margin-top: 2.5rem; } @include media-breakpoint-up(xl) { font-size: 1.5rem; - margin-top: 2.5rem; + margin-top: 3rem; } a { diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index b16e088f69..ebc147b238 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -14,6 +14,10 @@ } } +.blog-list-content { + margin-bottom: 3rem; +} + .blog-card { margin-bottom: 2rem; position: relative; diff --git a/frappe/public/scss/website/index.scss b/frappe/public/scss/website/index.scss index 0c96c62c17..933ac7ae22 100644 --- a/frappe/public/scss/website/index.scss +++ b/frappe/public/scss/website/index.scss @@ -114,8 +114,8 @@ @media (max-width: map-get($grid-breakpoints, "lg")) { .page-content-wrapper .container { - padding-left: 1rem; - padding-right: 1rem; + padding-left: 1.5rem; + padding-right: 1.5rem; } } diff --git a/frappe/public/scss/website/markdown.scss b/frappe/public/scss/website/markdown.scss index 6b5f7cbb6e..2b17c209cd 100644 --- a/frappe/public/scss/website/markdown.scss +++ b/frappe/public/scss/website/markdown.scss @@ -29,8 +29,11 @@ } p, li { - font-size: 1.1rem; line-height: 1.7; + + @include media-breakpoint-up(sm) { + font-size: 1.05rem; + } } p.lead { diff --git a/frappe/public/scss/website/page_builder.scss b/frappe/public/scss/website/page_builder.scss index d16ea4e27d..2ca51067b7 100644 --- a/frappe/public/scss/website/page_builder.scss +++ b/frappe/public/scss/website/page_builder.scss @@ -20,8 +20,14 @@ color: var(--text-muted); font-weight: normal; font-size: 1.25rem; - margin-top: -1rem; - margin-bottom: 2.5rem; + + margin-top: -0.5rem; + margin-bottom: 1.5rem; + + @include media-breakpoint-up(sm) { + margin-top: -1rem; + margin-bottom: 2.5rem; + } } .hero-subtitle { @@ -53,6 +59,7 @@ .section-description { max-width: 56rem; + color: var(--text-muted); margin-top: 0.5rem; font-size: $font-size-lg; diff --git a/frappe/website/web_template/section_with_cta/section_with_cta.html b/frappe/website/web_template/section_with_cta/section_with_cta.html index 4494dccecb..4015c05517 100644 --- a/frappe/website/web_template/section_with_cta/section_with_cta.html +++ b/frappe/website/web_template/section_with_cta/section_with_cta.html @@ -22,4 +22,5 @@
{%- endif -%} + {% if cta_url %}{% endif %}
diff --git a/frappe/website/web_template/section_with_features/section_with_features.html b/frappe/website/web_template/section_with_features/section_with_features.html index e9a1966f3d..b8cc3c2b90 100644 --- a/frappe/website/web_template/section_with_features/section_with_features.html +++ b/frappe/website/web_template/section_with_features/section_with_features.html @@ -6,7 +6,8 @@

{{ subtitle }}

{%- endif -%} -
+
{%- for feature in features -%}
diff --git a/frappe/website/web_template/section_with_small_cta/section_with_small_cta.html b/frappe/website/web_template/section_with_small_cta/section_with_small_cta.html index 7bd8c905ad..b898ebdcbe 100644 --- a/frappe/website/web_template/section_with_small_cta/section_with_small_cta.html +++ b/frappe/website/web_template/section_with_small_cta/section_with_small_cta.html @@ -16,4 +16,5 @@ {%- endif -%}
+ {% if cta_url %}{% endif %}
From 02286e4e6f4178d7137a0f8757a24e130ecd96dc Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 <81952590+Komal-Saraf0609@users.noreply.github.com> Date: Fri, 8 Apr 2022 19:12:58 +0530 Subject: [PATCH 36/40] test: Added test script for control type "Attach" (#16355) Adding automation test script for control/fieldtype "Attach". The above test script does the following testing: 1. Creating a new doctype with attach fieldtype. 2. Attaching a new image using the "Link" option from the options which the "Attach" button offers. 3. Checking if the URL of the attached image is getting displayed in the field of the newly created doctype. 4. Checking if the clicking on the "Clear" button clears the text in the field and again displays the "Attach" button. 5. Doing all the above testing by using the "Library" option from the options which the "Attach" button offers. --- cypress/integration/control_attach.js | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 cypress/integration/control_attach.js diff --git a/cypress/integration/control_attach.js b/cypress/integration/control_attach.js new file mode 100644 index 0000000000..0552780737 --- /dev/null +++ b/cypress/integration/control_attach.js @@ -0,0 +1,90 @@ +context('Attach Control', () => { + before(() => { + cy.login(); + cy.visit('/app/doctype'); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Attach Control', + fields: [ + { + "label": "Attach File or Image", + "fieldname": "attach", + "fieldtype": "Attach", + "in_list_view": 1, + }, + ] + }); + }); + }); + it('Checking functionality for "Link" button in the "Attach" fieldtype', () => { + //Navigating to the new form for the newly created doctype + cy.new_form('Test Attach Control'); + + //Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype + cy.findByRole('button', {name: 'Attach'}).click(); + + //Clicking on "Link" button to attach a file using the "Link" button + cy.findByRole('button', {name: 'Link'}).click(); + cy.findByPlaceholderText('Attach a web link').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); + + //Clicking on the Upload button to upload the file + cy.intercept("POST", "/api/method/upload_file").as("upload_image"); + cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500}); + cy.wait("@upload_image"); + cy.findByRole('button', {name: 'Save'}).click(); + + //Checking if the URL of the attached image is getting displayed in the field of the newly created doctype + cy.get('.attached-file > .ellipsis > .attached-file-link') + .should('have.attr', 'href') + .and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); + + //Clicking on the "Clear" button + cy.get('[data-action="clear_attachment"]').click(); + + //Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button + cy.get('.control-input > .btn-sm').should('contain', 'Attach'); + + //Deleting the doc + cy.go_to_list('Test Attach Control'); + cy.get('.list-row-checkbox').eq(0).click(); + cy.get('.actions-btn-group > .btn').contains('Actions').click(); + cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click(); + cy.click_modal_primary_button('Yes'); + }); + + it('Checking functionality for "Library" button in the "Attach" fieldtype', () => { + //Navigating to the new form for the newly created doctype + cy.new_form('Test Attach Control'); + + //Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype + cy.findByRole('button', {name: 'Attach'}).click(); + + //Clicking on "Library" button to attach a file using the "Library" button + cy.findByRole('button', {name: 'Library'}).click(); + cy.contains('72402.jpg').click(); + + //Clicking on the Upload button to upload the file + cy.intercept("POST", "/api/method/upload_file").as("upload_image"); + cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500}); + cy.wait("@upload_image"); + cy.findByRole('button', {name: 'Save'}).click(); + + //Checking if the URL of the attached image is getting displayed in the field of the newly created doctype + cy.get('.attached-file > .ellipsis > .attached-file-link') + .should('have.attr', 'href') + .and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); + + //Clicking on the "Clear" button + cy.get('[data-action="clear_attachment"]').click(); + + //Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button + cy.get('.control-input > .btn-sm').should('contain', 'Attach'); + + //Deleting the doc + cy.go_to_list('Test Attach Control'); + cy.get('.list-row-checkbox').eq(0).click(); + cy.get('.actions-btn-group > .btn').contains('Actions').click(); + cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click(); + cy.click_modal_primary_button('Yes'); + }); +}); \ No newline at end of file From 42cfcdadf94ff262515cdfd44af3b838b10b31cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Apr 2022 01:08:28 +0000 Subject: [PATCH 37/40] chore(deps): bump moment from 2.24.0 to 2.29.2 Bumps [moment](https://github.com/moment/moment) from 2.24.0 to 2.29.2. - [Release notes](https://github.com/moment/moment/releases) - [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) - [Commits](https://github.com/moment/moment/compare/2.24.0...2.29.2) --- updated-dependencies: - dependency-name: moment dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d2c1de7be5..fd27bc223b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "js-sha256": "^0.9.0", "jsbarcode": "^3.9.0", "localforage": "^1.9.0", - "moment": "^2.20.1", + "moment": "^2.29.2", "moment-timezone": "^0.5.28", "node-sass": "^7.0.0", "plyr": "^3.6.2", diff --git a/yarn.lock b/yarn.lock index 339e4c5669..12021982ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2998,10 +2998,10 @@ moment-timezone@^0.5.28: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.20.1: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +"moment@>= 2.9.0", moment@^2.29.2: + version "2.29.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" + integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== ms@2.0.0: version "2.0.0" From 9e4182d91f7130c9ce4c7957df59730ac3fb91ea Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 9 Apr 2022 16:37:59 +0530 Subject: [PATCH 38/40] fix: new route syntax for Logs in System Console --- frappe/desk/doctype/system_console/system_console.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json index 657e9df89d..c92b2005ed 100644 --- a/frappe/desk/doctype/system_console/system_console.json +++ b/frappe/desk/doctype/system_console/system_console.json @@ -1,7 +1,7 @@ { "actions": [ { - "action": "#List/Console Log/List", + "action": "app/console-log", "action_type": "Route", "label": "Logs" }, @@ -86,7 +86,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-09-15 17:17:44.844767", + "modified": "2022-04-09 16:35:32.345542", "modified_by": "Administrator", "module": "Desk", "name": "System Console", @@ -104,5 +104,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 60814c4e3fb8f3eb6be0e54cad7ba2a97c2b2104 Mon Sep 17 00:00:00 2001 From: Mohammed Redah Date: Sun, 10 Apr 2022 05:16:43 +0300 Subject: [PATCH 39/40] fix: Export Links in Customize Form (#16333) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/modules/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 4768faff48..0383327b68 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -45,7 +45,7 @@ def export_customizations(module, doctype, sync_on_migrate=0, with_permissions=0 if not frappe.get_conf().developer_mode: raise Exception('Not developer mode') - custom = {'custom_fields': [], 'property_setters': [], 'custom_perms': [], + custom = {'custom_fields': [], 'property_setters': [], 'custom_perms': [],'links':[], 'doctype': doctype, 'sync_on_migrate': sync_on_migrate} def add(_doctype): @@ -53,6 +53,8 @@ def export_customizations(module, doctype, sync_on_migrate=0, with_permissions=0 fields='*', filters={'dt': _doctype}) custom['property_setters'] += frappe.get_all('Property Setter', fields='*', filters={'doc_type': _doctype}) + custom['links'] += frappe.get_all('DocType Link', + fields='*', filters={'parent': _doctype}) add(doctype) From cc9613f5c238f1d515691d9547d564775e5e3e8c Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Sun, 10 Apr 2022 04:18:55 +0200 Subject: [PATCH 40/40] fix: update french translation (#16550) --- frappe/translations/fr.csv | 80 +++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/frappe/translations/fr.csv b/frappe/translations/fr.csv index 112130e945..67aca1443c 100644 --- a/frappe/translations/fr.csv +++ b/frappe/translations/fr.csv @@ -293,7 +293,7 @@ old_parent,grand_parent, (Ctrl + G),(Ctrl + G), ** Failed: {0} to {1}: {2},** Échec: {0} à {1}: {2}, **Currency** Master,Données de Base **Devise**, -0 - Draft; 1 - Submitted; 2 - Cancelled,0 - Brouillon; 1 - Soumis; 2 - Annulé, +0 - Draft; 1 - Submitted; 2 - Cancelled,0 - Brouillon; 1 - Validé; 2 - Annulé, 0 is highest,0 est le plus élevé, 1 Currency = [?] Fraction\nFor e.g. 1 USD = 100 Cent,1 Devise = [?] Fraction \nE.g. 1 USD = 100 centimes, 1 comment,1 commentaire, @@ -377,7 +377,7 @@ Align Labels to the Right,Alignez les Étiquettes à Droite, Align Value,Aligner la Valeur, All Images attached to Website Slideshow should be public,Toutes les images jointes au diaporama du site Web doivent être publiques, All customizations will be removed. Please confirm.,Toutes les personnalisations seront supprimées. Veuillez confirmer., -"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Tous les États et Rôles possibles du Flux de Travail. Options de Statut du Document : 0 est ""Enregistré"", 1 est ""Soumis"" et 2 est ""Annulé""", +"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Tous les États et Rôles possibles du Flux de Travail. Options de Statut du Document : 0 est ""Enregistré"", 1 est ""Validé"" et 2 est ""Annulé""", All-uppercase is almost as easy to guess as all-lowercase.,Tout en majuscules est presque aussi facile à deviner que tout en minuscules., Allocated To,Attribué à, Allow,Autoriser, @@ -404,7 +404,7 @@ Allow Self Approval,Autoriser l'auto-approbation, Allow approval for creator of the document,Autoriser l'approbation par le créateur du document, Allow events in timeline,Autoriser les événements dans la chronologie, Allow in Quick Entry,Autoriser dans les entrées rapides, -Allow on Submit,Autoriser à la Soumission, +Allow on Submit,Autoriser à la Validation, Allow only one session per user,Autoriser une seule session par utilisateur, Allow page break inside tables,Autoriser les sauts de page dans les tables, Allow saving if mandatory fields are not filled,Autoriser l'enregistrement si les champs obligatoires ne sont pas remplis, @@ -594,7 +594,7 @@ Cancelled Document restored as Draft,Le document annulé a été restauré en ta Cancelling,Annulation, Cancelling {0},Annulation de {0}, Cannot Remove,Ne peut être retiré, -Cannot cancel before submitting. See Transition {0},Impossible d'annuler avant de soumettre. Voir Transition {0}, +Cannot cancel before submitting. See Transition {0},Impossible d'annuler avant de valider. Voir Transition {0}, Cannot change docstatus from 0 to 2,Impossible de changer le statut du document de 0 à 2, Cannot change docstatus from 1 to 0,Impossible de changer le statut du document de 1 à 0, Cannot change header content,Impossible de changer le contenu de l'en-tête, @@ -627,7 +627,7 @@ Card Details,Détails de la carte, Categorize blog posts.,Catégoriser les posts de blog., Category Description,Description de la Catégorie, Cent,Centime, -"Certain documents, like an Invoice, should not be changed once final. The final state for such documents is called Submitted. You can restrict which roles can Submit.","Certains documents, comme une Facture, ne devraient pas être modifiés une fois finalisés. L'état final de ces documents est appelée Soumis. Vous pouvez limiter les rôles pouvant Soumettre.", +"Certain documents, like an Invoice, should not be changed once final. The final state for such documents is called Submitted. You can restrict which roles can Submit.","Certains documents, comme une Facture, ne devraient pas être modifiés une fois finalisés. L'état final de ces documents est appelée Validé. Vous pouvez limiter les rôles pouvant Valider.", Chain Integrity,Intégrité de la chaîne, Chaining Hash,Hachage de chaînage, Change Label (via Custom Translation),Modifier le libellé (via Traduction Personnalisée ), @@ -896,7 +896,7 @@ DocType {0} provided for the field {1} must have atleast one Link DocType can not be merged,DocType ne peut pas être fusionné, DocType can only be renamed by Administrator,DocType ne peut être renommé que par l'Administrateur, DocType is a Table / Form in the application.,DocType est un Tableau / Formulaire dans l'application., -DocType must be Submittable for the selected Doc Event,Le DocType doit être soumissible pour l'événement Doc sélectionné, +DocType must be Submittable for the selected Doc Event,Le DocType doit être validable pour l'événement Doc sélectionné, DocType on which this Workflow is applicable.,DocType pour lequel ce Flux de Travail est applicable., "DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores","Le nom du DocType doit commencer par une lettre et il peut uniquement se composer de lettres, des chiffres, d’espaces et du tiret bas (underscore)", Doctype required,Doctype requis, @@ -908,7 +908,7 @@ Document Restored,Document Restauré, Document Share Report,Rapport de Partage de Document, Document States,États du Document, Document Type is not importable,Le type de document n'est pas importable, -Document Type is not submittable,Le type de document n'est pas soumis, +Document Type is not submittable,Le type de document n'est pas valider, Document Type to Track,Type de document à suivre, Document Types,Types de documents, Document can't saved.,Le document ne peut pas être enregistré., @@ -1392,7 +1392,7 @@ Is Published Field must be a valid fieldname,Le Champ Publié doit-il être un n Is Single,Est Seul, Is Spam,Est Spam, Is Standard,Est Standard, -Is Submittable,Est Soumissible, +Is Submittable,Est Validable, Is Table,Est Table, Is Your Company Address,Est l'Adresse de votre Entreprise, It is risky to delete this file: {0}. Please contact your System Manager.,Il est risqué de supprimer ce fichier : {0}. Veuillez contactez votre Administrateur Système., @@ -1541,7 +1541,7 @@ Max Value,Valeur Max, Max width for type Currency is 100px in row {0},Largeur max pour le type Devise est 100px dans la ligne {0}, Maximum Attachment Limit for this record reached.,Taille maximale des Pièces Jointes pour cet enregistrement est atteint., Maximum {0} rows allowed,Maximum {0} lignes autorisés, -"Meaning of Submit, Cancel, Amend","Signification de Soumettre, Annuler, Modifier", +"Meaning of Submit, Cancel, Amend","Signification de Valider, Annuler, Modifier", Mention transaction completion page URL,Mentionnez la page URL de fin de transaction, Mentions,Mentions, Menu,Menu, @@ -1737,7 +1737,7 @@ Old Password,Ancien Mot De Passe, Old Password Required.,Ancien Mot de Passe Requis., Older backups will be automatically deleted,Les anciennes sauvegardes seront automatiquement supprimées, "On {0}, {1} wrote:","Sur {0}, {1} a écrit :", -"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.","Une fois soumis, les documents à soumettre ne peuvent plus être modifiés. Ils ne peuvent être annulés et amendés.", +"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.","Une fois validé, les documents à valider ne peuvent plus être modifiés. Ils ne peuvent être annulés et amendés.", "Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).","Une fois que vous avez défini ceci, les utilisateurs ne pourront accèder qu'aux documents (e.g. Article de Blog) où le lien existe (e.g. Blogger) .", One Last Step,Une Dernière Étape, One Time Password (OTP) Registration Code from {},Code de Mot de Passe Unique (OTP) à partir de {}, @@ -1829,7 +1829,7 @@ Percent Complete,Pourcentage d'Avancement, Perm Level,Niveau d'Autorisation, Permanent,Permanent, Permanently Cancel {0}?,Annuler de Manière Permanente {0} ?, -Permanently Submit {0}?,Soumettre de Manière Permanente {0} ?, +Permanently Submit {0}?,Valider de Manière Permanente {0} ?, Permanently delete {0}?,Supprimer de Manière Permanente {0} ?, Permission Error,Erreur d'autorisation, Permission Level,Niveau d'Autorisation, @@ -1837,7 +1837,7 @@ Permission Levels,Niveaux d'Autorisation, Permission Rules,Règles d'Autorisation, Permissions,Autorisations, Permissions are automatically applied to Standard Reports and searches.,Les autorisations sont automatiquement appliquées aux rapports standard et aux recherches., -"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.","Les Autorisations sont définies sur les Rôles et les Types de Documents (appelés DocTypes) en définissant des droits , tels que Lire, Écrire, Créer, Supprimer, Soumettre, Annuler, Modifier, Rapporter, Importer, Exporter, Imprimer, Envoyer un Email et Définir les Autorisations de l'Utilisateur .", +"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.","Les Autorisations sont définies sur les Rôles et les Types de Documents (appelés DocTypes) en définissant des droits , tels que Lire, Écrire, Créer, Supprimer, Valider, Annuler, Modifier, Rapporter, Importer, Exporter, Imprimer, Envoyer un Email et Définir les Autorisations de l'Utilisateur .", Permissions at higher levels are Field Level permissions. All Fields have a Permission Level set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only for certain Roles.,Les Autorisations aux niveaux supérieurs sont des permissions de Niveau Champ. Un Niveau d'Autorisation est défini pour chaque Champ et les règles définies pour ces Autorisations s’appliquent au Champ. Ceci est utile si vous voulez cacher ou mettre certains champs en lecture seule pour certains Rôles., "Permissions at level 0 are Document Level permissions, i.e. they are primary for access to the document.","Les Autorisations au niveau 0 sont les autorisations de Niveau Document, c’est à dire qu'elles sont nécessaires pour accéder au document.", Permissions get applied on Users based on what Roles they are assigned.,Autorisations sont appliqués aux utilisateurs en fonction des Rôles qui leurs sont affectés., @@ -2123,7 +2123,7 @@ Row No,Rangée No, Row Status,État de la ligne, Row Values Changed,Valeurs de Lignes Modifiées, Row {0}: Not allowed to disable Mandatory for standard fields,Ligne {0}: impossible de désactiver Obligatoire pour les champs standard, -Row {0}: Not allowed to enable Allow on Submit for standard fields,Ligne {0} : Il n’est pas autorisé d’activer Autoriser à la Soumission pour les champs standards, +Row {0}: Not allowed to enable Allow on Submit for standard fields,Ligne {0} : Il n’est pas autorisé d’activer Autoriser à la Validation pour les champs standards, Rows Added,Lignes Ajoutées, Rows Removed,Lignes Supprimées, Rule,Règle, @@ -2395,13 +2395,13 @@ Stylesheets for Print Formats,Feuilles de style pour les Formats d'Impression, Sub-domain provided by erpnext.com,Sous-domaine fourni par erpnext.com, Subdomain,Sous-domaine, Subject Field,Champ de sujet, -Submit after importing,Soumettre après l'import, -Submit an Issue,Soumettre un ticket, -Submit this document to confirm,Soumettre ce document pour confirmer, -Submit {0} documents?,Soumettre {0} documents ?, -Submiting {0},Soumission de {0}, -Submitted Document cannot be converted back to draft. Transition row {0},Document Soumis ne peut pas être reconvertis en Brouillon. Ligne de transition {0}, -Submitting,Soumission, +Submit after importing,Valider après l'import, +Submit an Issue,Valider un ticket, +Submit this document to confirm,Valider ce document pour confirmer, +Submit {0} documents?,Valider {0} documents ?, +Submiting {0},Validation de {0}, +Submitted Document cannot be converted back to draft. Transition row {0},Document Valider ne peut pas être reconvertis en Brouillon. Ligne de transition {0}, +Submitting,Validation, Subscription Notification,Notification d'abonnement, Subsidiary,Filiale, Success Action,Action de succès, @@ -2784,7 +2784,7 @@ You are not permitted to view the newsletter.,Vous n'êtes pas autorisé à You are now following this document. You will receive daily updates via email. You can change this in User Settings.,Vous suivez maintenant ce document. Vous recevrez des mises à jour quotidiennes par courrier électronique. Vous pouvez modifier cela dans les paramètres de l'utilisateur., You can add dynamic properties from the document by using Jinja templating.,Vous pouvez ajouter des propriétés dynamiques au document à l'aide des modèles Jinja., You can also copy-paste this ,Vous pouvez également copier-coller cette, -"You can change Submitted documents by cancelling them and then, amending them.","Vous pouvez modifier les documents Soumis en les annulant et ensuite, en les modifiant.", +"You can change Submitted documents by cancelling them and then, amending them.","Vous pouvez modifier les documents Validés en les annulant et ensuite, en les modifiant.", You can find things by asking 'find orange in customers',Vous pouvez trouver des choses en demandant 'trouver orange dans clients', You can only upload upto 5000 records in one go. (may be less in some cases),Vous pouvez seulement charger jusqu'à 5000 enregistrement en une seule fois. (peut-être moins dans certains cas), You can use Customize Form to set levels on fields.,Vous pouvez utiliser Personaliser le Formulaire pour définir les niveaux de champs., @@ -2807,7 +2807,7 @@ You gained {0} points,Vous avez gagné {0} points, You have a new message from: ,Vous avez un nouveau message de:, You have been successfully logged out,Vous avez été déconnecté avec succès, You have unsaved changes in this form. Please save before you continue.,Vous avez des modifications non enregistrées dans ce formulaire. Veuillez enregistrer avant de continuer., -You must login to submit this form,Vous devez vous connecter pour soumettre ce formulaire, +You must login to submit this form,Vous devez vous connecter pour valider ce formulaire, You need to be in developer mode to edit a Standard Web Form,Vous devez être en Mode Développeur pour modifier un Formulaire Web Standard, You need to be logged in and have System Manager Role to be able to access backups.,Vous devez être connecté et avoir le Role Responsable Système pour pouvoir accéder aux sauvegardes., You need to be logged in to access this {0}.,Vous devez être connecté pour accéder à ce(tte) {0}., @@ -2820,7 +2820,7 @@ Your Language,Votre Langue, Your Name,Votre Nom, Your account has been locked and will resume after {0} seconds,Votre compte a été verrouillé et reprendra après {0} secondes, Your connection request to Google Calendar was successfully accepted,Votre demande de connexion à Google Agenda a été acceptée avec succès, -Your information has been submitted,Vos informations ont été soumises, +Your information has been submitted,Vos informations ont été validées, Your login id is,Votre id de connexion est, Your organization name and address for the email footer.,Le nom de votre société et l'adresse pour le pied de l'email., Your payment has been successfully registered.,Votre paiement a été enregistré avec succès., @@ -2982,7 +2982,7 @@ star,étoile, star-empty,étoile-vide, step-backward,vers-larrière, step-forward,vers-l'avant, -submitted this document,a soumis ce document, +submitted this document,a validé ce document, text in document type,Texte dans le type de document, text-height,Hauteur-texte, text-width,largeur-text, @@ -3094,11 +3094,11 @@ zoom-out,Réduire, "{0}, Row {1}","{0}, Ligne {1}", "{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}",{0} : {1} '({3}) sera tronqué car le nombre de caractères max est {2}, {0}: Cannot set Amend without Cancel,{0} : Impossible de choisir Modifier sans Annuler, -{0}: Cannot set Assign Amend if not Submittable,{0} : Impossible de définir ‘Assigner Modifier’ si non Soumissible, -{0}: Cannot set Assign Submit if not Submittable,{0} : Impossible de définir ‘Assigner Soumettre’ si non Soumissible, -{0}: Cannot set Cancel without Submit,{0} : Impossible de choisir Annuler sans Soumettre, +{0}: Cannot set Assign Amend if not Submittable,{0} : Impossible de définir ‘Assigner Modifier’ si non Validable, +{0}: Cannot set Assign Submit if not Submittable,{0} : Impossible de définir ‘Assigner Valider’ si non Validable, +{0}: Cannot set Cancel without Submit,{0} : Impossible de choisir Annuler sans Valider, {0}: Cannot set Import without Create,{0} : Impossible de choisir Import sans Créer, -"{0}: Cannot set Submit, Cancel, Amend without Write","{0} : Vous ne pouvez pas choisir Envoyer, Annuler, Modifier sans Écrire", +"{0}: Cannot set Submit, Cancel, Amend without Write","{0} : Vous ne pouvez pas choisir Valider, Annuler, Modifier sans Écrire", {0}: Cannot set import as {1} is not importable,{0} : Impossible de choisir import car {1} n'est pas importable, {0}: No basic permissions set,{0} : Aucune autorisation de base définie, "{0}: Only one rule allowed with the same Role, Level and {1}","{0} : Une seule règle est permise avec le même Rôle, Niveau et {1}", @@ -3153,8 +3153,8 @@ Administration,Administration, After Cancel,Après annuler, After Delete,Après la suppression, After Save,Après l'enregistrement, -After Save (Submitted Document),Après l'enregistrement (document soumis), -After Submit,Après soumettre, +After Save (Submitted Document),Après l'enregistrement (document valider), +After Submit,Après validation, Aggregate Function Based On,Fonction d'agrégation basée sur, Aggregate Function field is required to create a dashboard chart,Le champ Fonction d'agrégation est requis pour créer un graphique de tableau de bord, All Records,Tous les enregistrements, @@ -3199,8 +3199,8 @@ Before Cancel,Avant d'annuler, Before Delete,Avant de supprimer, Before Insert,Avant l'insertion, Before Save,Avant de sauvegarder, -Before Save (Submitted Document),Avant de sauvegarder (document soumis), -Before Submit,Avant de soumettre, +Before Save (Submitted Document),Avant de sauvegarder (document valider), +Before Submit,Avant de valider, Blank Template,Modèle vierge, Callback URL,URL de rappel, Cancel All Documents,Annuler tous les documents, @@ -3556,11 +3556,11 @@ Skipping column {0},Colonne ignorée {0}, Social Home,Maison sociale, Some columns might get cut off when printing to PDF. Try to keep number of columns under 10.,Certaines colonnes peuvent être coupées lors de l'impression au format PDF. Essayez de garder le nombre de colonnes sous 10., Something went wrong during the token generation. Click on {0} to generate a new one.,Quelque chose s'est mal passé pendant la génération de jetons. Cliquez sur {0} pour en générer un nouveau., -Submit After Import,Soumettre après importation, -Submitting...,Soumission..., +Submit After Import,Validation après importation, +Submitting...,Validation..., Success! You are good to go 👍,Succès! Vous êtes bon pour aller, Successful Transactions,Transactions réussies, -Successfully Submitted!,Soumis avec succès!, +Successfully Submitted!,Validation avec succès!, Successfully imported {0} record.,{0} enregistrement importé avec succès., Successfully imported {0} records.,{0} enregistrements importés avec succès., Successfully updated {0} record.,{0} enregistrement mis à jour avec succès., @@ -3659,7 +3659,7 @@ choose an,choisir un, empty,vide, of,de, or attach a,ou attacher un, -submitted this document {0},a soumis ce document {0}, +submitted this document {0},a validé ce document {0}, "tag name..., e.g. #tag","nom de tag ..., par exemple #tag", uploaded file,fichier téléchargé, via Data Import,via importation de données, @@ -3678,7 +3678,7 @@ via Data Import,via importation de données, {0} shared a document {1} {2} with you,{0} a partagé un document {1} {2} avec vous, {0} should not be same as {1},{0} ne doit pas être identique à {1}, {0} translations pending,{0} traductions en attente, -{0} {1} is linked with the following submitted documents: {2},{0} {1} est lié aux documents soumis suivants: {2}, +{0} {1} is linked with the following submitted documents: {2},{0} {1} est lié aux documents validés suivants: {2}, "{0}: Failed to attach new recurring document. To enable attaching document in the auto repeat notification email, enable {1} in Print Settings","{0}: Impossible de joindre un nouveau document récurrent. Pour activer la pièce jointe dans l'e-mail de notification de répétition automatique, activez {1} dans Paramètres d'impression", {0}: Fieldname cannot be one of {1},{0}: le nom de champ ne peut pas être l'un des {1}, {} Complete,{} Achevée, @@ -3793,7 +3793,7 @@ Sr,Sr, Start,Démarrer, Start Time,Heure de Début, Status,Statut, -Submitted,Soumis, +Submitted,Validé, Tag,Étiquette, Template,Modèle, Thursday,Jeudi, @@ -4146,7 +4146,7 @@ Collapse,Réduire, "Invalid token, please provide a valid token with prefix 'Basic' or 'Token'.","Jeton non valide, veuillez fournir un jeton valide avec le préfixe «Basic» ou «Token».", {0} is not a valid Name,{0} n'est pas un nom valide, Your system is being updated. Please refresh again after a few moments.,Votre système est en cours de mise à jour. Veuillez actualiser à nouveau après quelques instants., -{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first.,{0} {1}: l'enregistrement soumis ne peut pas être supprimé. Vous devez d'abord {2} l'annuler {3}., +{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first.,{0} {1}: l'enregistrement validé ne peut pas être supprimé. Vous devez d'abord {2} l'annuler {3}., Invalid naming series (. missing) for {0},Série de noms non valide (. Manquante) pour {0}, Error has occurred in {0},Une erreur s'est produite dans {0}, Status Updated,Statut mis à jour, @@ -4510,7 +4510,7 @@ Oops,Oups, Skip Step,Passer l'étape, "You're doing great, let's take you back to the onboarding page.","Vous vous débrouillez très bien, revenons à la page d'intégration.", Good Work 🎉,Bon travail 🎉, -Submit this document to complete this step.,Soumettez ce document pour terminer cette étape., +Submit this document to complete this step.,Validez ce document pour terminer cette étape., Great,Génial, You may continue with onboarding,Vous pouvez continuer avec l'intégration, You seem good to go!,Vous semblez prêt à partir!,