Merge branch 'frappe:develop' into multiple_imap_folder
This commit is contained in:
commit
09ad4478a7
57 changed files with 854 additions and 599 deletions
14
.github/workflows/patch-mariadb-tests.yml
vendored
14
.github/workflows/patch-mariadb-tests.yml
vendored
|
|
@ -106,16 +106,14 @@ jobs:
|
|||
source env/bin/activate
|
||||
cd apps/frappe/
|
||||
git remote set-url upstream https://github.com/frappe/frappe.git
|
||||
git fetch --all --tags
|
||||
|
||||
taglist=$(git tag --sort version:refname | grep -v "beta")
|
||||
last_release=$(echo "$taglist" | tail -1 | cut -d . -f 1 | cut -c 2-)
|
||||
|
||||
for version in $(seq 12 "$last_release")
|
||||
for version in $(seq 12 13)
|
||||
do
|
||||
last_tag=$(echo "$taglist" | grep "v$version" | tail -1)
|
||||
echo "Updating to $last_tag"
|
||||
git checkout -q -f "$last_tag"
|
||||
echo "Updating to v$version"
|
||||
branch_name="version-$version-hotfix"
|
||||
git fetch --depth 1 upstream $branch_name:$branch_name
|
||||
|
||||
git checkout -q -f $branch_name
|
||||
pip install -q -r requirements.txt
|
||||
bench --site test_site migrate
|
||||
done
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
"adminPassword": "admin",
|
||||
"defaultCommandTimeout": 20000,
|
||||
"pageLoadTimeout": 15000,
|
||||
"video": true,
|
||||
"videoUploadOnPasses": false,
|
||||
"retries": {
|
||||
"runMode": 2,
|
||||
"openMode": 2
|
||||
|
|
|
|||
|
|
@ -33,12 +33,13 @@ context('Control Duration', () => {
|
|||
cy.get('@dialog').then(dialog => {
|
||||
let value = dialog.get_value('duration');
|
||||
expect(value).to.equal(3889800);
|
||||
cy.hide_dialog();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide days or seconds according to duration options', () => {
|
||||
get_dialog_with_duration(1, 1).as('dialog');
|
||||
cy.get('.frappe-control[data-fieldname=duration] input').first().click();
|
||||
cy.get('.frappe-control[data-fieldname=duration] input').first();
|
||||
cy.get('.duration-input[data-duration=days]').should('not.be.visible');
|
||||
cy.get('.duration-input[data-duration=seconds]').should('not.be.visible');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ context('Control Link', () => {
|
|||
it('should unset invalid value', () => {
|
||||
get_dialog_with_link().as('dialog');
|
||||
|
||||
cy.intercept('POST', '/api/method/frappe.client.validate_link*').as('validate_link');
|
||||
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=link] input')
|
||||
.type('invalid value', { delay: 100 })
|
||||
|
|
@ -61,7 +61,7 @@ context('Control Link', () => {
|
|||
it('should route to form on arrow click', () => {
|
||||
get_dialog_with_link().as('dialog');
|
||||
|
||||
cy.intercept('POST', '/api/method/frappe.client.validate_link*').as('validate_link');
|
||||
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
|
||||
|
||||
cy.get('@todos').then(todos => {
|
||||
|
|
@ -81,11 +81,11 @@ context('Control Link', () => {
|
|||
it('should fetch valid value', () => {
|
||||
cy.get('@todos').then(todos => {
|
||||
cy.visit(`/app/todo/${todos[0]}`);
|
||||
cy.intercept('GET', '/api/method/frappe.client.get_value*').as('get_value');
|
||||
cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=assigned_by] input').focus().as('input');
|
||||
cy.get('@input').type('Administrator', {delay: 100}).blur();
|
||||
cy.wait('@get_value');
|
||||
cy.wait('@validate_link');
|
||||
cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
|
||||
'contain', 'Administrator'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ context('Form', () => {
|
|||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/app/todo/new');
|
||||
cy.get('[data-fieldname="description"] .ql-editor')
|
||||
.first()
|
||||
.click()
|
||||
.type('this is a test todo');
|
||||
cy.wait(300);
|
||||
cy.get_field('description', 'Text Editor').type('this is a test todo', {force: true}).wait(200);
|
||||
cy.get('.page-title').should('contain', 'Not Saved');
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
|
|
@ -20,29 +16,34 @@ context('Form', () => {
|
|||
}).as('form_save');
|
||||
cy.get('.primary-action').click();
|
||||
cy.wait('@form_save').its('response.statusCode').should('eq', 200);
|
||||
|
||||
cy.visit('/app/todo');
|
||||
cy.wait(300);
|
||||
cy.get('.title-text').should('be.visible').and('contain', 'To Do');
|
||||
cy.get('.page-head').findByTitle('To Do').should('exist');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
});
|
||||
|
||||
it('navigates between documents with child table list filters applied', () => {
|
||||
cy.visit('/app/contact');
|
||||
cy.add_filter();
|
||||
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
|
||||
cy.findByRole('button', {name: 'Apply Filters'}).click({ force: true });
|
||||
cy.visit('/app/contact/Test Form Contact 3');
|
||||
|
||||
cy.clear_filters();
|
||||
cy.get('.standard-filter-section [data-fieldname="name"] input').type('Test Form Contact 3').blur();
|
||||
cy.click_listview_row_item(0);
|
||||
|
||||
cy.get('.prev-doc').should('be.visible').click();
|
||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
|
||||
cy.hide_dialog();
|
||||
cy.get('.next-doc').click();
|
||||
cy.wait(200);
|
||||
|
||||
cy.get('.next-doc').should('be.visible').click();
|
||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
|
||||
cy.hide_dialog();
|
||||
cy.contains('Test Form Contact 2').should('not.exist');
|
||||
cy.get('.title-text').should('contain', 'Test Form Contact 3');
|
||||
|
||||
cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
|
||||
|
||||
// clear filters
|
||||
cy.visit('/app/contact');
|
||||
cy.clear_filters();
|
||||
});
|
||||
|
||||
it('validates behaviour of Data options validations in child table', () => {
|
||||
// test email validations for set_invalid controller
|
||||
let website_input = 'website.in';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ context('Grid Pagination', () => {
|
|||
it('creates pages for child table', () => {
|
||||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '1');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
|
||||
});
|
||||
|
|
@ -21,10 +21,10 @@ context('Grid Pagination', () => {
|
|||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.next-page').click();
|
||||
cy.get('@table').find('.current-page-number').should('contain', '2');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '2');
|
||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51');
|
||||
cy.get('@table').find('.prev-page').click();
|
||||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '1');
|
||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
|
||||
});
|
||||
it('adds and deletes rows and changes page', () => {
|
||||
|
|
@ -32,14 +32,35 @@ context('Grid Pagination', () => {
|
|||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').findByRole('button', {name: 'Add Row'}).click();
|
||||
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '21');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true });
|
||||
cy.get('@table').findByRole('button', {name: 'Delete'}).click();
|
||||
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '20');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
});
|
||||
it('go to specific page, use up and down arrow, type characters, 0 page and more than existing page', () => {
|
||||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.current-page-number').focus().clear().type('17').blur();
|
||||
cy.get('@table').find('.grid-body .row-index').should('contain', 801);
|
||||
|
||||
cy.get('@table').find('.current-page-number').focus().type('{uparrow}{uparrow}');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '19');
|
||||
|
||||
cy.get('@table').find('.current-page-number').focus().type('{downarrow}{downarrow}');
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '17');
|
||||
|
||||
cy.get('@table').find('.current-page-number').focus().clear().type('700').blur();
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '20');
|
||||
|
||||
cy.get('@table').find('.current-page-number').focus().clear().type('0').blur();
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '1');
|
||||
|
||||
cy.get('@table').find('.current-page-number').focus().clear().type('abc').blur();
|
||||
cy.get('@table').find('.current-page-number').should('have.value', '1');
|
||||
});
|
||||
// it('deletes all rows', ()=> {
|
||||
// cy.visit('/app/contact/Test Contact');
|
||||
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ context('List View', () => {
|
|||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
|
||||
cy.get('.dropdown-menu li:visible .dropdown-item .menu-item-label[data-label="Edit"]').click();
|
||||
|
||||
cy.get('.modal-body .form-control[data-fieldname="field"]').first().select('Due Date').wait(200);
|
||||
cy.fill_field('value', '09-28-21', 'Date');
|
||||
cy.get('.modal-body .form-control[data-fieldname="field"]').first().select('Priority').wait(200);
|
||||
|
||||
cy.get('.modal-footer .standard-actions .btn-primary').click();
|
||||
cy.wait(500);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ context('Navigation', () => {
|
|||
|
||||
it.only('Navigate to previous page after login', () => {
|
||||
cy.visit('/app/todo');
|
||||
cy.findByTitle('To Do').should('be.visible');
|
||||
cy.get('.page-head').findByTitle('To Do').should('be.visible');
|
||||
cy.request('/api/method/logout');
|
||||
cy.reload();
|
||||
cy.get('.btn-primary').contains('Login').click();
|
||||
cy.reload().as('reload');
|
||||
cy.get('@reload').get('.page-card .btn-primary').contains('Login').click();
|
||||
cy.location('pathname').should('eq', '/login');
|
||||
cy.login();
|
||||
cy.visit('/app');
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ context('Query Report', () => {
|
|||
'report_type': 'Query Report',
|
||||
'query': 'select * from tabToDo'
|
||||
}, true).as('doc');
|
||||
cy.create_records({
|
||||
doctype: 'ToDo',
|
||||
description: 'this is a test todo for query report'
|
||||
}).as('todos');
|
||||
});
|
||||
|
||||
it('add custom column in report', () => {
|
||||
|
|
|
|||
|
|
@ -14,48 +14,51 @@ context('Recorder', () => {
|
|||
});
|
||||
|
||||
it('Recorder Empty State', () => {
|
||||
cy.findByTitle('Recorder').should('exist');
|
||||
cy.get('.page-head').findByTitle('Recorder').should('exist');
|
||||
|
||||
cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red');
|
||||
|
||||
cy.findByRole('button', {name: 'Start'}).should('exist');
|
||||
cy.findByRole('button', {name: 'Clear'}).should('exist');
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Start'}).should('exist');
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Clear'}).should('exist');
|
||||
|
||||
cy.get('.msg-box').should('contain', 'Inactive');
|
||||
cy.findByRole('button', {name: 'Start Recording'}).should('exist');
|
||||
cy.get('.msg-box').should('contain', 'Recorder is Inactive');
|
||||
cy.get('.msg-box').findByRole('button', {name: 'Start Recording'}).should('exist');
|
||||
});
|
||||
|
||||
it('Recorder Start', () => {
|
||||
cy.findByRole('button', {name: 'Start'}).click();
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Start'}).click();
|
||||
cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green');
|
||||
|
||||
cy.get('.msg-box').should('contain', 'No Requests');
|
||||
cy.get('.msg-box').should('contain', 'No Requests found');
|
||||
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.wait('@list_refresh');
|
||||
|
||||
cy.get('.title-text').should('contain', 'DocType');
|
||||
cy.get('.page-head').findByTitle('DocType').should('exist');
|
||||
cy.get('.list-count').should('contain', '20 of ');
|
||||
|
||||
cy.visit('/app/recorder');
|
||||
cy.findByTitle('Recorder').should('exist');
|
||||
cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get');
|
||||
cy.get('.page-head').findByTitle('Recorder').should('exist');
|
||||
cy.get('.frappe-list .result-list').should('contain', '/api/method/frappe.desk.reportview.get');
|
||||
});
|
||||
|
||||
it('Recorder View Request', () => {
|
||||
cy.findByRole('button', {name: 'Start'}).click();
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Start'}).click();
|
||||
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.wait('@list_refresh');
|
||||
|
||||
cy.get('.title-text').should('contain', 'DocType');
|
||||
cy.get('.page-head').findByTitle('DocType').should('exist');
|
||||
cy.get('.list-count').should('contain', '20 of ');
|
||||
|
||||
cy.visit('/app/recorder');
|
||||
|
||||
cy.get('.list-row-container span').contains('/api/method/frappe').click();
|
||||
cy.get('.frappe-list .list-row-container span')
|
||||
.contains('/api/method/frappe')
|
||||
.should('be.visible')
|
||||
.click({force: true});
|
||||
|
||||
cy.url().should('include', '/recorder/request');
|
||||
cy.get('form').should('contain', '/api/method/frappe');
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ context('Report View', () => {
|
|||
let cell = cy.get('.dt-row-0 > .dt-cell--col-4');
|
||||
// select the cell
|
||||
cell.dblclick();
|
||||
cell.findByRole('checkbox').check({ force: true });
|
||||
cy.get('.dt-row-0 > .dt-cell--col-5').click();
|
||||
cell.get('.dt-cell__edit--col-4').findByRole('checkbox').check({ force: true });
|
||||
cy.wait('@value-update');
|
||||
cy.get('@doc').then(doc => {
|
||||
cy.call('frappe.client.get_value', {
|
||||
|
|
|
|||
|
|
@ -8,22 +8,18 @@ context('Timeline', () => {
|
|||
|
||||
it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => {
|
||||
//Adding new ToDo
|
||||
cy.visit('/app/todo/new-todo-1');
|
||||
cy.get('[data-fieldname="description"] .ql-editor.ql-blank').type('Test ToDo', {force: true}).wait(200);
|
||||
cy.get('.page-head .page-actions').findByRole('button', {name: 'Save'}).click();
|
||||
|
||||
cy.visit('/app/todo');
|
||||
cy.click_listview_primary_button('Add ToDo');
|
||||
cy.findByRole('button', {name: 'Edit in full page'}).click();
|
||||
cy.findByTitle('New ToDo').should('be.visible');
|
||||
cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true});
|
||||
cy.wait(200);
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.wait(700);
|
||||
cy.visit('/app/todo');
|
||||
cy.get('.level-item.ellipsis').eq(0).click();
|
||||
cy.click_listview_row_item(0);
|
||||
|
||||
//To check if the comment box is initially empty and tying some text into it
|
||||
cy.get('[data-fieldname="comment"] .ql-editor').should('contain', '').type('Testing Timeline');
|
||||
|
||||
//Adding new comment
|
||||
cy.findByRole('button', {name: 'Comment'}).click();
|
||||
cy.get('.comment-box').findByRole('button', {name: 'Comment'}).click();
|
||||
|
||||
//To check if the commented text is visible in the timeline content
|
||||
cy.get('.timeline-content').should('contain', 'Testing Timeline');
|
||||
|
|
@ -38,21 +34,17 @@ context('Timeline', () => {
|
|||
|
||||
//Discarding comment
|
||||
cy.click_timeline_action_btn("Edit");
|
||||
cy.findByRole('button', {name: 'Dismiss'}).click();
|
||||
cy.click_timeline_action_btn("Dismiss");
|
||||
|
||||
//To check if after discarding the timeline content is same as previous
|
||||
cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
|
||||
|
||||
//Deleting the added comment
|
||||
cy.get('.more-actions > .action-btn').click();
|
||||
cy.get('.more-actions .dropdown-item').contains('Delete').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
cy.click_modal_primary_button('Yes');
|
||||
cy.get('.timeline-message-box .more-actions > .action-btn').click(); //Menu button in timeline item
|
||||
cy.get('.timeline-message-box .more-actions .dropdown-item').contains('Delete').click({ force: true });
|
||||
cy.get_open_dialog().findByRole('button', {name: 'Yes'}).click({ force: true });
|
||||
|
||||
//Deleting the added ToDo
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group [data-original-title="Menu"]').click();
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group .dropdown-item').contains('Delete').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
cy.get('.timeline-content').should('not.contain', 'Testing Timeline 123');
|
||||
});
|
||||
|
||||
it('Timeline should have submit and cancel activity information', () => {
|
||||
|
|
@ -66,31 +58,32 @@ context('Timeline', () => {
|
|||
|
||||
//Adding a new entry for the created custom doctype
|
||||
cy.fill_field('title', 'Test');
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.findByRole('button', {name: 'Submit'}).click();
|
||||
cy.click_modal_primary_button('Save');
|
||||
cy.click_modal_primary_button('Submit');
|
||||
|
||||
cy.visit('/app/custom-submittable-doctype');
|
||||
cy.get('.list-subject > .bold > .ellipsis').eq(0).click();
|
||||
cy.click_listview_row_item(0);
|
||||
|
||||
//To check if the submission of the documemt is visible in the timeline content
|
||||
cy.get('.timeline-content').should('contain', 'Administrator submitted this document');
|
||||
cy.findByRole('button', {name: 'Cancel'}).click({delay: 900});
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
cy.get('[id="page-Custom Submittable DocType"] .page-actions').findByRole('button', {name: 'Cancel'}).click();
|
||||
cy.get_open_dialog().findByRole('button', {name: 'Yes'}).click();
|
||||
|
||||
//To check if the cancellation of the documemt is visible in the timeline content
|
||||
cy.get('.timeline-content').should('contain', 'Administrator cancelled this document');
|
||||
|
||||
//Deleting the document
|
||||
cy.visit('/app/custom-submittable-doctype');
|
||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click();
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group > .dropdown-menu > li > .dropdown-item').contains("Delete").click();
|
||||
cy.click_modal_primary_button('Yes', {force: true, delay: 700});
|
||||
cy.select_listview_row_checkbox(0);
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.page-actions .actions-btn-group [data-label="Delete"]').click();
|
||||
cy.click_modal_primary_button('Yes');
|
||||
|
||||
//Deleting the custom doctype
|
||||
cy.visit('/app/doctype');
|
||||
cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click();
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.actions-btn-group [data-label="Delete"]').click();
|
||||
cy.select_listview_row_checkbox(0);
|
||||
cy.get('.page-actions').findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get('.page-actions .actions-btn-group [data-label="Delete"]').click();
|
||||
cy.click_modal_primary_button('Yes');
|
||||
});
|
||||
});
|
||||
|
|
@ -341,7 +341,7 @@ Cypress.Commands.add('click_sidebar_button', (btn_name) => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('click_listview_row_item', (row_no) => {
|
||||
cy.get('.list-row > .level-left > .list-subject > .bold > .ellipsis').eq(row_no).click({force: true});
|
||||
cy.get('.list-row > .level-left > .list-subject > .level-item > .ellipsis').eq(row_no).click({force: true});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_filter_button', () => {
|
||||
|
|
@ -353,5 +353,9 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => {
|
||||
cy.get('.timeline-message-box .custom-actions > .btn').contains(btn_name).click();
|
||||
cy.get('.timeline-message-box .actions .action-btn').contains(btn_name).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('select_listview_row_checkbox', (row_no) => {
|
||||
cy.get('.frappe-list .select-like > .list-row-checkbox').eq(row_no).click();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
coverage==5.5
|
||||
Faker~=8.1.0
|
||||
pyngrok~=5.0.5
|
||||
unittest-xml-reporting~=3.0.4
|
||||
|
|
|
|||
38
esbuild/build-cleanup.js
Normal file
38
esbuild/build-cleanup.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* eslint-disable no-console */
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const glob = require("fast-glob");
|
||||
|
||||
module.exports = {
|
||||
name: 'build_cleanup',
|
||||
setup(build) {
|
||||
build.onEnd(result => {
|
||||
if (result.errors.length) return;
|
||||
clean_dist_files(Object.keys(result.metafile.outputs));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function clean_dist_files(new_files) {
|
||||
new_files.forEach(
|
||||
file => {
|
||||
if (file.endsWith(".map")) return;
|
||||
|
||||
const pattern = file.split(".").slice(0, -2).join(".") + "*";
|
||||
glob.sync(pattern).forEach(
|
||||
file_to_delete => {
|
||||
if (file_to_delete.startsWith(file)) return;
|
||||
|
||||
fs.unlink(path.resolve(file_to_delete), err => {
|
||||
if (!err) return;
|
||||
|
||||
console.error(
|
||||
`Error deleting ${file.split(path.sep).pop()}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
/* eslint-disable no-console */
|
||||
let path = require("path");
|
||||
let fs = require("fs");
|
||||
let glob = require("fast-glob");
|
||||
let esbuild = require("esbuild");
|
||||
let vue = require("esbuild-vue");
|
||||
let yargs = require("yargs");
|
||||
let cliui = require("cliui")();
|
||||
let chalk = require("chalk");
|
||||
let html_plugin = require("./frappe-html");
|
||||
let rtlcss = require('rtlcss');
|
||||
let postCssPlugin = require("esbuild-plugin-postcss2").default;
|
||||
let ignore_assets = require("./ignore-assets");
|
||||
let sass_options = require("./sass_options");
|
||||
let {
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const glob = require("fast-glob");
|
||||
const esbuild = require("esbuild");
|
||||
const vue = require("esbuild-vue");
|
||||
const yargs = require("yargs");
|
||||
const cliui = require("cliui")();
|
||||
const chalk = require("chalk");
|
||||
const html_plugin = require("./frappe-html");
|
||||
const rtlcss = require('rtlcss');
|
||||
const postCssPlugin = require("esbuild-plugin-postcss2").default;
|
||||
const ignore_assets = require("./ignore-assets");
|
||||
const sass_options = require("./sass_options");
|
||||
const build_cleanup_plugin = require("./build-cleanup");
|
||||
|
||||
const {
|
||||
app_list,
|
||||
assets_path,
|
||||
apps_path,
|
||||
|
|
@ -26,7 +28,7 @@ let {
|
|||
get_redis_subscriber
|
||||
} = require("./utils");
|
||||
|
||||
let argv = yargs
|
||||
const argv = yargs
|
||||
.usage("Usage: node esbuild [options]")
|
||||
.option("apps", {
|
||||
type: "string",
|
||||
|
|
@ -98,9 +100,6 @@ if (WATCH_MODE) {
|
|||
|
||||
async function execute() {
|
||||
console.time(TOTAL_BUILD_TIME);
|
||||
if (!FILES_TO_BUILD.length) {
|
||||
await clean_dist_folders(APPS);
|
||||
}
|
||||
|
||||
let results;
|
||||
try {
|
||||
|
|
@ -231,12 +230,13 @@ function get_files_to_build(files) {
|
|||
function build_files({ files, outdir }) {
|
||||
let build_plugins = [
|
||||
html_plugin,
|
||||
build_cleanup_plugin,
|
||||
vue(),
|
||||
];
|
||||
return esbuild.build(get_build_options(files, outdir, build_plugins));
|
||||
}
|
||||
|
||||
function build_style_files({ files, outdir, rtl_style=false }) {
|
||||
function build_style_files({ files, outdir, rtl_style = false }) {
|
||||
let plugins = [];
|
||||
if (rtl_style) {
|
||||
plugins.push(rtlcss);
|
||||
|
|
@ -244,6 +244,7 @@ function build_style_files({ files, outdir, rtl_style=false }) {
|
|||
|
||||
let build_plugins = [
|
||||
ignore_assets,
|
||||
build_cleanup_plugin,
|
||||
postCssPlugin({
|
||||
plugins: plugins,
|
||||
sassOptions: sass_options
|
||||
|
|
@ -313,24 +314,6 @@ function get_watch_config() {
|
|||
return null;
|
||||
}
|
||||
|
||||
async function clean_dist_folders(apps) {
|
||||
for (let app of apps) {
|
||||
let public_path = get_public_path(app);
|
||||
let paths = [
|
||||
path.resolve(public_path, "dist", "js"),
|
||||
path.resolve(public_path, "dist", "css"),
|
||||
path.resolve(public_path, "dist", "css-rtl")
|
||||
];
|
||||
for (let target of paths) {
|
||||
if (fs.existsSync(target)) {
|
||||
// rmdir is deprecated in node 16, this will work in both node 14 and 16
|
||||
let rmdir = fs.promises.rm || fs.promises.rmdir;
|
||||
await rmdir(target, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function log_built_assets(results) {
|
||||
let outputs = {};
|
||||
for (const result of results) {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
|
|||
filters = {"name": filters}
|
||||
|
||||
try:
|
||||
fields = json.loads(fieldname)
|
||||
fields = frappe.parse_json(fieldname)
|
||||
except (TypeError, ValueError):
|
||||
# name passed, not json
|
||||
fields = [fieldname]
|
||||
|
|
@ -407,7 +407,7 @@ def is_document_amended(doctype, docname):
|
|||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_link(doctype: str, docname: str):
|
||||
def validate_link(doctype: str, docname: str, fields=None):
|
||||
if not isinstance(doctype, str):
|
||||
frappe.throw(_("DocType must be a string"))
|
||||
|
||||
|
|
@ -424,4 +424,26 @@ def validate_link(doctype: str, docname: str):
|
|||
frappe.PermissionError
|
||||
)
|
||||
|
||||
return frappe.db.get_value(doctype, docname, cache=True)
|
||||
values = frappe._dict()
|
||||
values.name = frappe.db.get_value(doctype, docname, cache=True)
|
||||
|
||||
fields = frappe.parse_json(fields)
|
||||
if not values.name or not fields:
|
||||
return values
|
||||
|
||||
try:
|
||||
values.update(get_value(doctype, fields, docname))
|
||||
except frappe.PermissionError:
|
||||
frappe.clear_last_message()
|
||||
frappe.msgprint(
|
||||
_("You need {0} permission to fetch values from {1} {2}")
|
||||
.format(
|
||||
frappe.bold(_("Read")),
|
||||
frappe.bold(doctype),
|
||||
frappe.bold(docname)
|
||||
),
|
||||
title=_("Cannot Fetch Values"),
|
||||
indicator="orange"
|
||||
)
|
||||
|
||||
return values
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ def run_ui_tests(context, app, headless=False, parallel=True, with_coverage=Fals
|
|||
frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 @cypress/code-coverage@^3 --no-lockfile")
|
||||
|
||||
# run for headless mode
|
||||
run_or_open = 'run --browser firefox --record' if headless else 'open'
|
||||
run_or_open = 'run --browser chrome --record' if headless else 'open'
|
||||
formatted_command = f'{site_env} {password_env} {coverage_env} {cypress_path} {run_or_open}'
|
||||
|
||||
if parallel:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ import frappe, json
|
|||
import unittest
|
||||
|
||||
class TestComment(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
frappe.form_dict.comment = None
|
||||
frappe.form_dict.comment_email = None
|
||||
frappe.form_dict.comment_by = None
|
||||
frappe.form_dict.reference_doctype = None
|
||||
frappe.form_dict.reference_name = None
|
||||
frappe.form_dict.route = None
|
||||
frappe.local.request_ip = None
|
||||
|
||||
def test_comment_creation(self):
|
||||
test_doc = frappe.get_doc(dict(doctype = 'ToDo', description = 'test'))
|
||||
test_doc.insert()
|
||||
|
|
@ -33,8 +42,16 @@ class TestComment(unittest.TestCase):
|
|||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"})
|
||||
|
||||
from frappe.templates.includes.comments.comments import add_comment
|
||||
add_comment('Good comment with 10 chars', 'test@test.com', 'Good Tester',
|
||||
'Blog Post', test_blog.name, test_blog.route)
|
||||
|
||||
frappe.form_dict.comment = 'Good comment with 10 chars'
|
||||
frappe.form_dict.comment_email = 'test@test.com'
|
||||
frappe.form_dict.comment_by = 'Good Tester'
|
||||
frappe.form_dict.reference_doctype = 'Blog Post'
|
||||
frappe.form_dict.reference_name = test_blog.name
|
||||
frappe.form_dict.route = test_blog.route
|
||||
frappe.local.request_ip = '127.0.0.1'
|
||||
|
||||
add_comment()
|
||||
|
||||
self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = test_blog.doctype,
|
||||
|
|
@ -43,8 +60,10 @@ class TestComment(unittest.TestCase):
|
|||
|
||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"})
|
||||
|
||||
add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor',
|
||||
'Blog Post', test_blog.name, test_blog.route)
|
||||
frappe.form_dict.comment = 'pleez vizits my site http://mysite.com'
|
||||
frappe.form_dict.comment_by = 'bad commentor'
|
||||
|
||||
add_comment()
|
||||
|
||||
self.assertEqual(len(frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = test_blog.doctype,
|
||||
|
|
|
|||
|
|
@ -8,34 +8,14 @@
|
|||
"reference_doctype",
|
||||
"reference_name",
|
||||
"column_break_3",
|
||||
"rating",
|
||||
"ip_address",
|
||||
"section_break_6",
|
||||
"feedback"
|
||||
"like",
|
||||
"ip_address"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Rating",
|
||||
"precision": "1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Feedback",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Select",
|
||||
|
|
@ -57,11 +37,17 @@
|
|||
"hidden": 1,
|
||||
"label": "IP Address",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "like",
|
||||
"fieldtype": "Check",
|
||||
"label": "Like"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-23 12:45:42.045696",
|
||||
"modified": "2021-11-10 20:53:21.255593",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Feedback",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ class TestFeedback(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
frappe.form_dict.reference_doctype = None
|
||||
frappe.form_dict.reference_name = None
|
||||
frappe.form_dict.rating = None
|
||||
frappe.form_dict.feedback = None
|
||||
frappe.form_dict.like = None
|
||||
frappe.local.request_ip = None
|
||||
|
||||
def test_feedback_creation_updation(self):
|
||||
|
|
@ -18,23 +17,22 @@ class TestFeedback(unittest.TestCase):
|
|||
|
||||
frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"})
|
||||
|
||||
from frappe.templates.includes.feedback.feedback import add_feedback, update_feedback
|
||||
from frappe.templates.includes.feedback.feedback import give_feedback
|
||||
|
||||
frappe.form_dict.reference_doctype = 'Blog Post'
|
||||
frappe.form_dict.reference_name = test_blog.name
|
||||
frappe.form_dict.rating = 5
|
||||
frappe.form_dict.feedback = 'New feedback'
|
||||
frappe.form_dict.like = True
|
||||
frappe.local.request_ip = '127.0.0.1'
|
||||
|
||||
feedback = add_feedback()
|
||||
feedback = give_feedback()
|
||||
|
||||
self.assertEqual(feedback.feedback, 'New feedback')
|
||||
self.assertEqual(feedback.rating, 5)
|
||||
self.assertEqual(feedback.like, True)
|
||||
|
||||
updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback')
|
||||
frappe.form_dict.like = False
|
||||
|
||||
self.assertEqual(updated_feedback.feedback, 'Updated feedback')
|
||||
self.assertEqual(updated_feedback.rating, 6)
|
||||
updated_feedback = give_feedback()
|
||||
|
||||
self.assertEqual(updated_feedback.like, False)
|
||||
|
||||
frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"})
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import frappe
|
|||
from ..role import desk_properties
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('user')
|
||||
frappe.reload_doctype('role')
|
||||
for role in frappe.get_all('Role', ['name', 'desk_access']):
|
||||
role_doc = frappe.get_doc('Role', role.name)
|
||||
for key in desk_properties:
|
||||
role_doc.set(key, role_doc.desk_access)
|
||||
role_doc.save()
|
||||
role_doc.save()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import frappe
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "Client Script"):
|
||||
return
|
||||
|
||||
frappe.rename_doc("DocType", "Custom Script", "Client Script")
|
||||
frappe.flags.ignore_route_conflict_validation = True
|
||||
rename_doc("DocType", "Custom Script", "Client Script")
|
||||
frappe.flags.ignore_route_conflict_validation = False
|
||||
|
||||
frappe.reload_doctype("Client Script", force=True)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66659 9.77761C2.66659 10.0919 2.79145 10.3934 3.01372 10.6157C3.23598 10.8379 3.53744 10.9628 3.85177 10.9628H10.9629L13.3333 13.3332V3.85169C13.3333 3.53736 13.2084 3.2359 12.9861 3.01364C12.7639 2.79137 12.4624 2.6665 12.1481 2.6665H3.85177C3.53744 2.6665 3.23598 2.79137 3.01372 3.01364C2.79145 3.2359 2.66659 3.53736 2.66659 3.85169V9.77761Z" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6647 3.5L9.75437 3.5H5.4885L5.00008 3.5C4.17165 3.50001 3.50008 4.17158 3.50008 5V7.82297V7.82297L3.50006 8.99615C3.50005 9.82459 4.17162 10.4962 5.00006 10.4962H6.35239C6.87269 10.4962 7.35582 10.7658 7.629 11.2086L8.33241 12.3488L9.18233 10.9711C9.36445 10.6759 9.68654 10.4962 10.0334 10.4962H11.6647C12.4932 10.4962 13.1647 9.82461 13.1647 8.99618V7.82297V5C13.1647 4.17157 12.4932 3.5 11.6647 3.5ZM9.75437 2.5L11.6647 2.5C13.0455 2.5 14.1647 3.61929 14.1647 5V7.82297V8.99618C14.1647 10.3769 13.0455 11.4962 11.6647 11.4962L10.0334 11.4962L9.18349 12.8738C8.79281 13.5071 7.87202 13.5071 7.48134 12.8738L6.77793 11.7337C6.68687 11.5861 6.52583 11.4962 6.35239 11.4962H5.00006C3.61933 11.4962 2.50004 10.3769 2.50006 8.99614L2.50008 7.82297V7.82296V5C2.50008 3.61929 3.61937 2.50001 5.00008 2.5L5.4885 2.5H9.75437ZM5.34386 5.84168C5.34386 5.56554 5.56772 5.34168 5.84386 5.34168H10.8207C11.0968 5.34168 11.3207 5.56554 11.3207 5.84168C11.3207 6.11782 11.0968 6.34168 10.8207 6.34168H5.84386C5.56772 6.34168 5.34386 6.11782 5.34386 5.84168ZM5.84386 7.47294C5.56772 7.47294 5.34386 7.69679 5.34386 7.97294C5.34386 8.24908 5.56772 8.47294 5.84386 8.47294H8.68777C8.96391 8.47294 9.18777 8.24908 9.18777 7.97294C9.18777 7.69679 8.96391 7.47294 8.68777 7.47294H5.84386Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 1.4 KiB |
|
|
@ -167,10 +167,12 @@ frappe.ui.form.Control = class BaseControl {
|
|||
}
|
||||
|
||||
this.inside_change_event = true;
|
||||
var set = function(value) {
|
||||
function set(value) {
|
||||
me.inside_change_event = false;
|
||||
return frappe.run_serially([
|
||||
() => me._validated = true,
|
||||
() => me.set_model_value(value),
|
||||
() => delete me._validated,
|
||||
() => {
|
||||
me.set_mandatory && me.set_mandatory(value);
|
||||
|
||||
|
|
|
|||
|
|
@ -444,7 +444,11 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
}
|
||||
validate(value) {
|
||||
// validate the value just entered
|
||||
if(this.df.options=="[Select]" || this.df.ignore_link_validation) {
|
||||
if (
|
||||
this._validated
|
||||
|| this.df.options=="[Select]"
|
||||
|| this.df.ignore_link_validation
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -454,39 +458,33 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
validate_link_and_fetch(df, options, docname, value) {
|
||||
if (!value) return;
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
const fetch_map = this.fetch_map;
|
||||
const columns_to_fetch = Object.values(fetch_map);
|
||||
const fetch_map = this.fetch_map;
|
||||
const columns_to_fetch = Object.values(fetch_map);
|
||||
|
||||
// if default and no fetch, no need to validate
|
||||
if (!columns_to_fetch.length && df.__default_value === value) {
|
||||
return resolve(value);
|
||||
// if default and no fetch, no need to validate
|
||||
if (!columns_to_fetch.length && df.__default_value === value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return frappe.xcall("frappe.client.validate_link", {
|
||||
doctype: options,
|
||||
docname: value,
|
||||
fields: columns_to_fetch,
|
||||
}).then((response) => {
|
||||
if (!response || !response.name) return "";
|
||||
if (!docname || !columns_to_fetch.length) return response.name;
|
||||
|
||||
for (const [target_field, source_field] of Object.entries(fetch_map)) {
|
||||
frappe.model.set_value(
|
||||
df.parent,
|
||||
docname,
|
||||
target_field,
|
||||
response[source_field],
|
||||
df.fieldtype,
|
||||
);
|
||||
}
|
||||
|
||||
const name = await frappe.xcall("frappe.client.validate_link", {
|
||||
doctype: options,
|
||||
docname: value
|
||||
});
|
||||
|
||||
if (!name) return resolve("");
|
||||
if (!docname || !columns_to_fetch.length) return resolve(name);
|
||||
|
||||
frappe.db.get_value(
|
||||
options,
|
||||
value,
|
||||
columns_to_fetch,
|
||||
(response) => {
|
||||
for (const [target_field, source_field] of Object.entries(fetch_map)) {
|
||||
frappe.model.set_value(
|
||||
df.parent,
|
||||
docname,
|
||||
target_field,
|
||||
response[source_field],
|
||||
df.fieldtype,
|
||||
);
|
||||
}
|
||||
}
|
||||
).always(() => resolve(name));
|
||||
return response.name;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,55 @@ export default class GridPagination {
|
|||
this.last_page_button.on('click', () => {
|
||||
this.go_to_page(this.total_pages);
|
||||
});
|
||||
|
||||
this.$page_number.on('keyup', (e) => {
|
||||
e.currentTarget.style.width = ((e.currentTarget.value.length + 1) * 8) + 'px';
|
||||
});
|
||||
|
||||
this.$page_number.on('keydown', (e) => {
|
||||
e = (e) ? e : window.event;
|
||||
var charCode = (e.which) ? e.which : e.keyCode;
|
||||
let arrow = { up: 38, down: 40 };
|
||||
|
||||
switch (charCode) {
|
||||
case arrow.up:
|
||||
this.inc_dec_number(true);
|
||||
break;
|
||||
case arrow.down:
|
||||
this.inc_dec_number(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// only allow numbers from 0-9 and up, down, left, right arrow keys
|
||||
if (charCode > 31 && (charCode < 48 || charCode > 57) &&
|
||||
![37, 38, 39, 40].includes(charCode)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this.$page_number.on('focusout', (e) => {
|
||||
if (this.page_index == e.currentTarget.value) return;
|
||||
this.page_index = e.currentTarget.value;
|
||||
|
||||
if (this.page_index < 1) {
|
||||
this.page_index = 1;
|
||||
} else if (this.page_index > this.total_pages) {
|
||||
this.page_index = this.total_pages;
|
||||
}
|
||||
|
||||
this.go_to_page();
|
||||
});
|
||||
}
|
||||
|
||||
inc_dec_number(increment) {
|
||||
let new_value = parseInt(this.$page_number.val());
|
||||
increment ? new_value++ : new_value--;
|
||||
|
||||
if (new_value < 1 || new_value > this.total_pages) return;
|
||||
|
||||
this.$page_number.val(new_value);
|
||||
}
|
||||
|
||||
update_page_numbers() {
|
||||
let total_pages = Math.ceil(this.grid.data.length/this.page_length);
|
||||
|
|
@ -65,7 +112,7 @@ export default class GridPagination {
|
|||
|
||||
get_pagination_html() {
|
||||
let page_text_html = `<div class="page-text">
|
||||
<span class="current-page-number page-number">${__(this.page_index)}</span>
|
||||
<input class="current-page-number page-number" type="text" value="${__(this.page_index)}"/>
|
||||
<span>${__('of')}</span>
|
||||
<span class="total-page-number page-number"> ${__(this.total_pages)} </span>
|
||||
</div>`;
|
||||
|
|
@ -104,7 +151,8 @@ export default class GridPagination {
|
|||
let $rows = $(this.grid.parent).find(".rows").empty();
|
||||
this.grid.render_result_rows($rows, true);
|
||||
if (this.$page_number) {
|
||||
this.$page_number.text(index);
|
||||
this.$page_number.val(index);
|
||||
this.$page_number.css('width', ((index.toString().length + 1) * 8) + 'px');
|
||||
}
|
||||
|
||||
this.update_page_numbers();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ frappe.user_info = function(uid) {
|
|||
|
||||
if(!(frappe.boot.user_info && frappe.boot.user_info[uid])) {
|
||||
var user_info = {
|
||||
fullname: frappe.utils.capitalize(uid.split("@")[0]) || "Unknown"
|
||||
fullname: frappe.utils.to_title_case(uid.split("@")[0]) || "Unknown"
|
||||
};
|
||||
} else {
|
||||
var user_info = frappe.boot.user_info[uid];
|
||||
|
|
|
|||
|
|
@ -224,6 +224,9 @@ frappe.views.InteractionComposer = class InteractionComposer {
|
|||
if (!("owner" in interaction_values)){
|
||||
interaction_values["owner"] = frappe.session.user;
|
||||
}
|
||||
if (!("assigned_by" in interaction_values) && interaction_values["doctype"] == "ToDo") {
|
||||
interaction_values["assigned_by"] = frappe.session.user;
|
||||
}
|
||||
return frappe.call({
|
||||
method:"frappe.client.insert",
|
||||
args: { doc: interaction_values},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,18 @@
|
|||
|
||||
.page-text {
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.current-page-number {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
border: none;
|
||||
cursor: text;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.prev-page,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
:root {
|
||||
--comment-timeline-bottom: 60px;
|
||||
--comment-timeline-top: 8px;
|
||||
}
|
||||
|
||||
.blog-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -96,4 +101,124 @@
|
|||
margin-top: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.feedback-item svg {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.blog-feedback {
|
||||
display: flex;
|
||||
|
||||
.like-icon {
|
||||
cursor: pointer;
|
||||
|
||||
&.gray use {
|
||||
fill: var(--gray-600);
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-comment-button {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 22px;
|
||||
background-color: var(--fg-color);
|
||||
border: 1px solid var(--dark-border-color);
|
||||
|
||||
&:before {
|
||||
content: ' ';
|
||||
background: var(--gray-600);
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
border-radius: 50%;
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-comments {
|
||||
.comment-form-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.add-comment-section {
|
||||
.login-required {
|
||||
padding: var(--padding-sm);
|
||||
border-radius: var(--border-radius-sm);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
.new-comment {
|
||||
display: flex;
|
||||
padding: var(--padding-lg);
|
||||
box-shadow: var(--card-shadow);
|
||||
border-radius: var(--border-radius-md);
|
||||
|
||||
.new-comment-fields {
|
||||
flex: 1;
|
||||
|
||||
.form-label {
|
||||
font-weight: var(--text-bold);
|
||||
}
|
||||
|
||||
.comment-text-area textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.comment-by {
|
||||
padding-right: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#comment-list {
|
||||
position: relative;
|
||||
padding-left: var(--padding-xl);
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: var(--comment-timeline-top);
|
||||
bottom: var(--comment-timeline-bottom);
|
||||
border-left: 1px solid var(--dark-border-color);
|
||||
}
|
||||
|
||||
.comment-row {
|
||||
position: relative;
|
||||
|
||||
.comment-avatar {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: -17px;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
box-shadow: var(--card-shadow);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: var(--padding-md);
|
||||
margin-left: 35px;
|
||||
flex: 1;
|
||||
|
||||
.content p{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.page-header-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.25rem;
|
||||
|
|
@ -256,3 +262,13 @@ h5.modal-title {
|
|||
.about-footer {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.login-content.container {
|
||||
background-color: var(--fg-color);
|
||||
padding: 45px 0px;
|
||||
box-shadow: var(--shadow-base);
|
||||
border-radius: var(--border-radius-md);
|
||||
max-width: 400px;
|
||||
margin: 70px auto;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
{% if frappe.session.user != "Guest" and
|
||||
(condition is not defined or (condition is defined and condition )) %}
|
||||
<span class="button is-secondary reply">
|
||||
<span class="btn btn-md btn-default reply">
|
||||
{{ _(cta_title) }}
|
||||
<svg class="icon icon-sm ml-1"><use href="#icon-add" style="stroke: var(--gray-700)"></use></svg>
|
||||
<!-- Below svg is not a part of the current design. Hence it is commented.
|
||||
The comment will be removed after all design changes are implemented. -->
|
||||
<!-- <svg class="icon icon-sm ml-1"><use href="#icon-add" style="stroke: var(--gray-700)"></use></svg> -->
|
||||
</span>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
|
||||
<a class="dark-links cancel-comment hide"> {{ _("Cancel") }} </a>
|
||||
<div class="button is-default submit-discussion pull-right mb-1">
|
||||
<div class="btn btn-md btn-default submit-discussion pull-right mb-1">
|
||||
{{ _("Post") }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@
|
|||
{% include "frappe/templates/discussions/topic_modal.html" %}
|
||||
|
||||
<div class="discussions-header">
|
||||
<span class="course-home-headings">{{ _(title) }}</span>
|
||||
<span class="discussion-heading">{{ _(title) }}</span>
|
||||
{% if topics and not single_thread %}
|
||||
{% include "frappe/templates/discussions/button.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="common-card-style thread-card {% if topics | length and not single_thread %} discussions-card {% endif %} ">
|
||||
<div class="card-style thread-card {% if topics | length and not single_thread %} discussions-card {% endif %}
|
||||
{% if not topics | length %} empty-state {% endif %}">
|
||||
{% if topics and not single_thread %}
|
||||
|
||||
|
||||
<div class="discussions-sidebar">
|
||||
{% include "frappe/templates/discussions/search.html" %}
|
||||
|
||||
|
|
@ -38,24 +38,23 @@
|
|||
{% include "frappe/templates/discussions/reply_section.html" %}
|
||||
|
||||
{% else %}
|
||||
<div class="no-discussions" id="no-discussions">
|
||||
<div class="font-weight-bold">No {{ title }}</div>
|
||||
<div class="small mt-3 mb-3">There are no {{ title | lower }} for this {{ doctype | lower }}, why don't you start
|
||||
one! </div>
|
||||
<div class="no-discussions">
|
||||
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg">
|
||||
<div class="discussion-heading mt-4 mb-0" style="color: inherit;"> {{ empty_state_title }} </div>
|
||||
<div class="small mb-6"> {{ empty_state_subtitle }} </div>
|
||||
{% if frappe.session.user == "Guest" %}
|
||||
<div class="button is-primary mt-3" id="login-from-discussion"> {{ _("Log In") }} </div>
|
||||
<div class="btn btn-default btn-md mt-3" id="login-from-discussion"> {{ _("Login") }} </div>
|
||||
{% elif condition is defined and not condition %}
|
||||
<a class="button is-primary mt-3" id="login-from-discussion" href="" data-redirect="{{ redirect_to }}">
|
||||
<div class="btn btn-default btn-md mt-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">
|
||||
{{ button_name }}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "frappe/templates/discussions/button.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% block script %}
|
||||
<script> {% include "frappe/templates/discussions/discussions.js" %} </script>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
{% if topic %} id="t{{ topic.name }}" data-topic="{{ topic.name }}" {% endif %}>
|
||||
|
||||
{% if not single_thread %}
|
||||
<div class="button is-default back">
|
||||
<div class="btn btn-md btn-default ellipsis back">
|
||||
{{ _("Back") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if topic and topic.title %}
|
||||
<div class="course-home-headings p-0">{{ topic.title }}</div>
|
||||
<div class="discussion-heading p-0">{{ topic.title }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% for reply in replies %}
|
||||
|
|
@ -30,9 +30,9 @@
|
|||
<div class="d-flex flex-column align-items-center small">
|
||||
{{ _("Want to join the discussion?") }}
|
||||
{% if frappe.session.user == "Guest" %}
|
||||
<div class="button is-primary mt-3 mb-3" id="login-from-discussion">{{ _("Log In") }}</div>
|
||||
<div class="btn btn-default btn-md mt-3 mb-3" id="login-from-discussion">{{ _("Login") }}</div>
|
||||
{% elif not condition %}
|
||||
<div class="button is-primary mt-3 mb-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}
|
||||
<div class="btn btn-default btn-md mt-3 mb-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<div class="discussion-topic-title">{{ topic.title }}</div>
|
||||
<div class="sidebar-info">
|
||||
{% set creator = frappe.get_doc("User", topic.owner) %}
|
||||
<span class="course-instructor ml-0">
|
||||
<span class="reply-author ml-0">
|
||||
{{ creator.full_name }}
|
||||
</span>
|
||||
<span class="small">
|
||||
<span class="mr-2">
|
||||
<img src="/assets/frappe/icons/timeless/message.svg">
|
||||
<span class="small d-flex">
|
||||
<span class="mr-2 d-flex align-items-center">
|
||||
<img class="mr-1" src="/assets/frappe/icons/timeless/message.svg">
|
||||
<span class="reply-count">{{ replies | length }}</span>
|
||||
</span>
|
||||
<span> {{ frappe.utils.format_date(topic.creation, "dd MMM YYYY") }} </span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% macro avatar(user_id=None, css_style=None) %}
|
||||
{% macro avatar(user_id=None, css_style=None, size="avatar-small") %}
|
||||
{% set user_info = frappe.utils.get_user_info_for_avatar(user_id) %}
|
||||
<span class="avatar avatar-small" title="{{ user_info.name }}" style="{{ css_style or '' }}">
|
||||
<span class="avatar {{ size }}" title="{{ user_info.name }}" style="{{ css_style or '' }}">
|
||||
{% if user_info.image %}
|
||||
<img
|
||||
class="avatar-frame standard-image"
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<span
|
||||
class="avatar-frame standard-image"
|
||||
title="{{ user_info.name }}">
|
||||
{{ frappe.utils.get_abbr(user_info.name) }}
|
||||
{{ frappe.utils.get_abbr(user_info.name).upper() }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %}
|
||||
{% from "frappe/templates/includes/avatar_macro.html" import avatar %}
|
||||
|
||||
<div class="comment-row media">
|
||||
{{ square_image_with_fallback(src=frappe.get_gravatar(comment.comment_email or comment.sender), size='extra-small', alt=comment.sender_full_name, class='align-self-start mr-4') }}
|
||||
<div class="media-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<span class="font-weight-bold text-muted">
|
||||
{{ comment.sender_full_name or comment.comment_by }}
|
||||
</span>
|
||||
<span class="text-muted small">
|
||||
{{ comment.creation | global_date_format }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
{{ comment.content | markdown }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-row media my-5">
|
||||
<div class="comment-avatar">
|
||||
{{ avatar(user_id=(comment.comment_email or comment.sender), size='avatar-medium') }}
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="head mb-2">
|
||||
<span class="title font-weight-bold mr-2">{{ comment.sender_full_name or comment.comment_by }}</span>
|
||||
<span class="time small text-muted">{{ frappe.utils.pretty_date(comment.creation) }}</span>
|
||||
</div>
|
||||
<div class="content">{{ comment.content | markdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,86 +1,146 @@
|
|||
<div class="comment-view mb-6">
|
||||
{% if comment_text %}
|
||||
<div class="comment-header mb-6">{{ comment_text }}</div>
|
||||
{% endif %}
|
||||
{% if not comment_list %}
|
||||
<div class="no-comment">
|
||||
<p class="text-muted small">{{ _("No comments yet. Start a new discussion.") }}</p>
|
||||
</div>
|
||||
<div class="no-comment">
|
||||
<p class="text-muted small">{{ _("No comments yet. ") }}
|
||||
<span class="hidden login-required">
|
||||
<a href="/login?redirect-to={{ pathname }}">{{ _("Login to start a new discussion") }}</a>
|
||||
</span>
|
||||
<span class="hidden start-discussion">{{ _("Start a new discussion") }}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not is_communication %}
|
||||
<div class="add-comment-section mb-5">
|
||||
<div class="comment-form-wrapper">
|
||||
<div id="comment-form">
|
||||
<form class="new-comment">
|
||||
<fieldset class="new-comment-fields">
|
||||
<div class="user-details row" style="margin-bottom: 15px; display:none;">
|
||||
<div class="comment-by col-sm-6 pb-4">
|
||||
<div class="form-label mb-1">{{ _("Your Name") }}</div>
|
||||
<input class="form-control comment_by" name="comment_by" type="text">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-label mb-1">{{ _("Email") }}</div>
|
||||
<input class="form-control comment_email" name="comment_email" type="email">
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-text-area">
|
||||
<div class="form-label mb-1">{{ _("Add a comment") }}</div>
|
||||
<textarea class="form-control" name="comment" rows=5 ></textarea>
|
||||
<div class="text-muted small mt-1 mb-4">{{ _("Ctrl+Enter to add comment") }}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm small" id="submit-comment">{{ _("Comment") }}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<hr class="add-comment-hr my-5">
|
||||
|
||||
<div itemscope itemtype="http://schema.org/UserComments" id="comment-list">
|
||||
{% for comment in comment_list %}
|
||||
<div class="my-3">
|
||||
{% include "templates/includes/comments/comment.html" %}
|
||||
<div class="add-comment mb-5">
|
||||
<div class="timeline-dot"></div>
|
||||
<button class="btn btn-sm small add-comment-button">{{ _("Add a comment") }}</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not is_communication %}
|
||||
<div class="add-comment-section">
|
||||
<div class="text-muted hidden login-required">
|
||||
<a href="/login?redirect-to={{ pathname }}">{{ _("Login to comment") }}</a>
|
||||
</div>
|
||||
|
||||
<div class="comment-form-wrapper">
|
||||
<a class="add-comment btn btn-light btn-sm">{{ _("Add Comment") }}</a>
|
||||
<div style="display: none;" id="comment-form">
|
||||
<p>{{ _("Leave a Comment") }}</p>
|
||||
<div class="alert" style="display:none;"></div>
|
||||
<form>
|
||||
<fieldset>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control comment_by" name="comment_by" placeholder="{{ _("Your Name") }}" type="text">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control comment_email" name="comment_email" placeholder="{{ _("Your Email Address") }}" type="email">
|
||||
</div>
|
||||
</div>
|
||||
<p><textarea class="form-control" name="comment" rows=10
|
||||
placeholder="{{ _("Comment") }}"></textarea></p>
|
||||
<button class="btn btn-primary btn-sm" id="submit-comment" style="margin-top:10px">{{ _("Submit") }}</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
<div class="comment-list">
|
||||
{% for comment in comment_list %}
|
||||
{% include "templates/includes/comments/comment.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
frappe.ready(function() {
|
||||
let guest_allowed = "{{ guest_allowed or ''}}";
|
||||
let guest_allowed = parseInt("{{ guest_allowed or 0}}");
|
||||
let comment_count = "{{ comment_text }}";
|
||||
let full_name = ""
|
||||
let user_id = "";
|
||||
|
||||
let update_timeline_line_length = function(direction, size) {
|
||||
if (direction == 'top') {
|
||||
$('.blog-container')[0].style.setProperty('--comment-timeline-top', size);
|
||||
} else {
|
||||
let comment_timeline_bottom = $('.comment-list .comment-row:last-child').height() - 10;
|
||||
$('.blog-container')[0].style.setProperty('--comment-timeline-bottom', comment_timeline_bottom +'px');
|
||||
}
|
||||
}
|
||||
|
||||
let show_comment_box = function() {
|
||||
$('.comment-form-wrapper').show();
|
||||
update_timeline_line_length('top', '-20px');
|
||||
$('.add-comment-hr').hide();
|
||||
$('.add-comment').hide();
|
||||
}
|
||||
|
||||
let hide_comment_box = function() {
|
||||
$('.comment-form-wrapper').hide();
|
||||
update_timeline_line_length('top', '8px');
|
||||
update_timeline_line_length('bottom');
|
||||
$('.add-comment-hr').show();
|
||||
$('.add-comment').show();
|
||||
}
|
||||
|
||||
let $comment_count = $(`
|
||||
<div class="feedback-item">
|
||||
<span class="comment-icon">${frappe.utils.icon('small-message', 'md')}</span>
|
||||
<span class="comment-count"></span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$('form').keydown(function(event) {
|
||||
if (event.ctrlKey && event.keyCode === 13) {
|
||||
$(this).find('#submit-comment').trigger('click');
|
||||
}
|
||||
})
|
||||
|
||||
if (!frappe.is_user_logged_in()) {
|
||||
!guest_allowed && $(".login-required, .comment-form-wrapper").toggleClass("hidden");
|
||||
$(".user-details").toggle('hide');
|
||||
if (guest_allowed) {
|
||||
$('.start-discussion').removeClass('hidden');
|
||||
} else {
|
||||
$(".login-required, .comment-form-wrapper").toggleClass("hidden");
|
||||
|
||||
$('.add-comment-button').text('{{ _("Login to comment") }}');
|
||||
$('.add-comment-button').click(() => {
|
||||
window.location.href = '/login?redirect-to={{ pathname }}';
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$('input.comment_by').prop("disabled", true);
|
||||
$('input.comment_email').prop("disabled", true);
|
||||
|
||||
full_name = frappe.get_cookie("full_name");
|
||||
user_id = frappe.get_cookie("user_id");
|
||||
if(user_id != "Guest") {
|
||||
$("[name='comment_email']").val(user_id);
|
||||
$("[name='comment_by']").val(full_name);
|
||||
}
|
||||
|
||||
$('.start-discussion').removeClass('hidden');
|
||||
}
|
||||
|
||||
var n_comments = $(".comment-row").length;
|
||||
$('.blog-feedback').append($comment_count);
|
||||
$('.comment-count').text(comment_count);
|
||||
$("#comment-form textarea").val("");
|
||||
|
||||
update_timeline_line_length('bottom');
|
||||
|
||||
let n_comments = $(".comment-row").length;
|
||||
n_comments ? $(".no_comment").toggle(false) : show_comment_box();
|
||||
|
||||
if(n_comments) {
|
||||
$(".no_comment").toggle(false);
|
||||
}
|
||||
if(n_comments > 50) {
|
||||
$(".add-comment").toggle(false)
|
||||
.parent().append("<div class='text-muted'>Comments are closed.</div>")
|
||||
}
|
||||
$(".add-comment").click(function() {
|
||||
$(this).toggle(false);
|
||||
$("#comment-form").toggle();
|
||||
var full_name = "", user_id = "";
|
||||
if(frappe.is_user_logged_in()) {
|
||||
full_name = frappe.get_cookie("full_name");
|
||||
user_id = frappe.get_cookie("user_id");
|
||||
if(user_id != "Guest") {
|
||||
$("[name='comment_email']").val(user_id);
|
||||
$("[name='comment_by']").val(full_name);
|
||||
}
|
||||
}
|
||||
$("#comment-form textarea").val("");
|
||||
})
|
||||
|
||||
$('.add-comment-button').click(() => {
|
||||
show_comment_box();
|
||||
});
|
||||
|
||||
$("#submit-comment").click(function() {
|
||||
var args = {
|
||||
|
|
@ -94,17 +154,17 @@
|
|||
}
|
||||
|
||||
if(!args.comment_by || !args.comment_email || !args.comment) {
|
||||
frappe.msgprint("{{ _("All fields are necessary to submit the comment.") }}");
|
||||
frappe.msgprint('{{ _("All fields are necessary to submit the comment.") }}');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.comment_email!=='Administrator' && !validate_email(args.comment_email)) {
|
||||
frappe.msgprint("{{ _("Please enter a valid email address.") }}");
|
||||
frappe.msgprint('{{ _("Please enter a valid email address.") }}');
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!args.comment || !args.comment.trim()) {
|
||||
frappe.msgprint("{{ _("Please add a valid comment.") }}");
|
||||
frappe.msgprint('{{ _("Please add a valid comment.") }}');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -119,17 +179,18 @@
|
|||
frappe.msgprint(r._server_messages);
|
||||
} else {
|
||||
if (r.message) {
|
||||
$(r.message).appendTo("#comment-list");
|
||||
$(".add-comment").text(__("Add Another Comment"));
|
||||
$(r.message).prependTo(".comment-list");
|
||||
comment_count = cint(comment_count) + 1;
|
||||
$('.comment-count').text(comment_count);
|
||||
}
|
||||
$(".no-comment, .add-comment").toggle(false);
|
||||
$("#comment-form").toggle();
|
||||
$(".add-comment").toggle();
|
||||
$(".no-comment").toggle(false);
|
||||
$("#comment-form textarea").val("");
|
||||
hide_comment_box();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return false;
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
import frappe
|
||||
import re
|
||||
from frappe.website.utils import clear_cache
|
||||
from frappe.rate_limiter import rate_limit
|
||||
from frappe.utils import add_to_date, now
|
||||
from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit
|
||||
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@rate_limit(key='reference_name', limit=get_comment_limit, seconds=60*60)
|
||||
def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route):
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,160 +1,43 @@
|
|||
<div class="add-feedback-section">
|
||||
<div class="feedback-form-wrapper">
|
||||
<a class="give-feedback btn btn-light btn-sm">{{ _("How would you rate the blog?") }}</a>
|
||||
<div style="display: none;" id="feedback-form">
|
||||
<p>{{ _("How would you rate the blog?") }}</p>
|
||||
<div class="alert" style="display:none;"></div>
|
||||
<form>
|
||||
<fieldset>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-sm-6">
|
||||
<div class="rating">
|
||||
{% for rating in [1, 2, 3, 4, 5 ,6, 7, 8, 9, 10] %}
|
||||
<div class="icon rating-box" data-rating="{{ rating }}">{{ rating }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<textarea class="form-control" name="feedback" rows=10 placeholder="{{ _("Feedback") }}"></textarea>
|
||||
</p>
|
||||
<button class="btn btn-sm" id="toggle-feedback" style="margin-top:10px; margin-right:2px;">
|
||||
{{ _("Back") }}
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" id="submit-feedback" style="margin-top:10px">
|
||||
{{ _("Submit") }}
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feedback-item mr-3">
|
||||
<span class="like-icon"></span>
|
||||
<span class="like-count"></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
frappe.ready(() => {
|
||||
let feedback = "{{ user_feedback.feedback or ''}}"
|
||||
let user_rating = parseInt("{{ user_feedback.rating or 0 }}")
|
||||
let rating = user_rating;
|
||||
feedback && $("#submit-feedback").html(__("Update"));
|
||||
let like = parseInt("{{ user_feedback.like or 0 }}");
|
||||
let like_count = parseInt("{{ like_count or 0 }}");
|
||||
|
||||
if (frappe.is_user_logged_in()) {
|
||||
if (feedback) {
|
||||
$("[name='feedback']").val(feedback);
|
||||
toggle_feedback();
|
||||
set_rating(rating);
|
||||
}
|
||||
let update_like = function() {
|
||||
like = !like;
|
||||
like ? like_count++ : like_count--;
|
||||
toggle_like_icon(like);
|
||||
$('.like-count').text(like_count);
|
||||
}
|
||||
|
||||
$('.give-feedback').click(() => toggle_feedback());
|
||||
let toggle_like_icon = function(active) {
|
||||
active ? $('.like-icon').addClass('gray') : $('.like-icon').removeClass('gray');
|
||||
}
|
||||
|
||||
$('.like-icon').append(frappe.utils.icon('heart', 'md'))
|
||||
toggle_like_icon(like);
|
||||
|
||||
$('.rating').find('.rating-box').hover((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
rating = el.data('rating');
|
||||
el.parent().children('.rating-box').each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-hover');
|
||||
} else {
|
||||
$(this).removeClass('rating-hover');
|
||||
}
|
||||
});
|
||||
}, (ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
el.parent().children('.rating-box').each( function() {
|
||||
$(this).removeClass('rating-hover');
|
||||
});
|
||||
});
|
||||
$('.like-count').text(like_count);
|
||||
|
||||
$('.rating').find('.rating-box').click((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
rating = el.data('rating');
|
||||
el.parent().children('.rating-box').each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-click');
|
||||
} else {
|
||||
$(this).removeClass('rating-click');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#submit-feedback').click((ev) => {
|
||||
let update = ev.target.innerText !== __("Submit");
|
||||
let rating = $('.rating').find('.rating-click').length;
|
||||
let args = {
|
||||
reference_doctype: "{{ reference_doctype or doctype }}",
|
||||
reference_name: "{{ reference_name or name }}",
|
||||
rating: rating,
|
||||
feedback: $("[name='feedback']").val()
|
||||
}
|
||||
|
||||
if (args.rating == 0) {
|
||||
frappe.msgprint("{{ _("Rating is required!") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args.feedback || !args.feedback.trim()) {
|
||||
frappe.msgprint("{{ _("Please add a valid feedback.") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
frappe.call({
|
||||
method: "frappe.templates.includes.feedback.feedback.add_feedback",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if (!r.message) {
|
||||
return
|
||||
}
|
||||
toggle_feedback();
|
||||
if (!frappe.is_user_logged_in()) {
|
||||
$("[name='feedback']").val('');
|
||||
set_rating(0);
|
||||
} else {
|
||||
feedback = $("[name='feedback']").val();
|
||||
user_rating = rating;
|
||||
$("#submit-feedback").html(__("Update"));
|
||||
}
|
||||
frappe.msgprint({message:__("Thank you for your valuable feedback!"), indicator:'green'});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (feedback == $("[name='feedback']").val() && rating == user_rating) {
|
||||
frappe.msgprint({message:__("Please update rating or feedback before saving."), indicator:'red'});
|
||||
return false;
|
||||
}
|
||||
frappe.call({
|
||||
method: "frappe.templates.includes.feedback.feedback.update_feedback",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
toggle_feedback();
|
||||
feedback = $("[name='feedback']").val();
|
||||
user_rating = rating;
|
||||
frappe.msgprint({message:__("Feedback updated successfully!"), indicator:'green'});
|
||||
}
|
||||
})
|
||||
}
|
||||
return false;
|
||||
$('.like-icon').click(() => {
|
||||
update_like();
|
||||
update_feedback();
|
||||
})
|
||||
|
||||
$('#toggle-feedback').click(() => {
|
||||
toggle_feedback();
|
||||
return false;
|
||||
})
|
||||
|
||||
function set_rating(rating) {
|
||||
let el = $('.rating').find('.rating-box');
|
||||
el.children('.rating-box').prevObject.each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-click');
|
||||
} else {
|
||||
$(this).removeClass('rating-click');
|
||||
let update_feedback = function() {
|
||||
return frappe.call({
|
||||
method: "frappe.templates.includes.feedback.feedback.give_feedback",
|
||||
args: {
|
||||
reference_doctype: "{{ reference_doctype or doctype }}",
|
||||
reference_name: "{{ reference_name or name }}",
|
||||
like
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggle_feedback() {
|
||||
$(".give-feedback").toggle();
|
||||
$("#feedback-form").toggle();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -10,25 +10,8 @@ from frappe.website.doctype.blog_settings.blog_settings import get_feedback_limi
|
|||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@rate_limit(key='reference_name', limit=get_feedback_limit, seconds=60*60)
|
||||
def add_feedback(reference_doctype, reference_name, rating, feedback):
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if doc.disable_feedback == 1:
|
||||
return
|
||||
|
||||
doc = frappe.new_doc('Feedback')
|
||||
doc.reference_doctype = reference_doctype
|
||||
doc.reference_name = reference_name
|
||||
doc.rating = rating
|
||||
doc.feedback = feedback
|
||||
doc.ip_address = frappe.local.request_ip
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
subject = _('New Feedback on {0}: {1}').format(reference_doctype, reference_name)
|
||||
send_mail(doc, subject)
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_feedback(reference_doctype, reference_name, rating, feedback):
|
||||
def give_feedback(reference_doctype, reference_name, like):
|
||||
like = frappe.parse_json(like)
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if doc.disable_feedback == 1:
|
||||
return
|
||||
|
|
@ -39,22 +22,26 @@ def update_feedback(reference_doctype, reference_name, rating, feedback):
|
|||
"reference_name": reference_name
|
||||
}
|
||||
d = frappe.get_all('Feedback', filters=filters, limit=1)
|
||||
doc = frappe.get_doc('Feedback', d[0].name)
|
||||
doc.rating = rating
|
||||
doc.feedback = feedback
|
||||
if d:
|
||||
doc = frappe.get_doc('Feedback', d[0].name)
|
||||
else:
|
||||
doc = doc = frappe.new_doc('Feedback')
|
||||
doc.reference_doctype = reference_doctype
|
||||
doc.reference_name = reference_name
|
||||
doc.ip_address = frappe.local.request_ip
|
||||
doc.like = like
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
subject = _('Feedback updated on {0}: {1}').format(reference_doctype, reference_name)
|
||||
subject = _('Feedback on {0}: {1}').format(reference_doctype, reference_name)
|
||||
send_mail(doc, subject)
|
||||
return doc
|
||||
|
||||
def send_mail(feedback, subject):
|
||||
doc = frappe.get_doc(feedback.reference_doctype, feedback.reference_name)
|
||||
|
||||
message = ("<p>{0} ({1})</p>".format(feedback.feedback, feedback.rating)
|
||||
+ "<p><a href='{0}/app/feedback/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
|
||||
feedback.name,
|
||||
_("View Feedback")))
|
||||
if feedback.like:
|
||||
message = "<p>Hey, </p><p>You have received a ❤️ heart on your blog post <b>{0}</b></p>".format(feedback.reference_name)
|
||||
else:
|
||||
return
|
||||
|
||||
# notify creator
|
||||
frappe.sendmail(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
}
|
||||
|
||||
.no-discussions {
|
||||
width: 250px;
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
}
|
||||
|
||||
.discussion-topic-title {
|
||||
color: var(--text-color);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.discussion-on-page .topic-title {
|
||||
|
|
@ -137,65 +137,24 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.course-home-headings {
|
||||
.discussion-heading {
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
line-height: 146%;
|
||||
letter-spacing: -0.0175em;
|
||||
color: #192734;
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 1rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.common-card-style {
|
||||
.card-style {
|
||||
display: flex;
|
||||
background: #FFFFFF;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
border: 1px solid #EEF0F2;
|
||||
box-shadow: 0 0px 4px 2px #19273405;
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.button {
|
||||
box-shadow: var(--btn-shadow);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
padding: 8px 12px 8px;
|
||||
font-size: 12px;
|
||||
line-height: 135%;
|
||||
letter-spacing: -0.011em;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.is-primary {
|
||||
background: var(--primary-color);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.is-primary:hover {
|
||||
text-decoration: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.is-secondary {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.is-secondary:hover {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.is-default {
|
||||
background: #F4F5F6;
|
||||
color: #1F272E;
|
||||
}
|
||||
|
||||
|
||||
.discussions-card {
|
||||
display: grid;
|
||||
grid-gap: 2rem;
|
||||
|
|
@ -233,19 +192,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
.course-instructor {
|
||||
.reply-author {
|
||||
margin: 0px 8px;
|
||||
font-size: 12px;
|
||||
line-height: 135%;
|
||||
color: var(--text-color);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
border: 1px solid var(--gray-200);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-divider-dark {
|
||||
border: 1px solid var(--gray-300);
|
||||
border-top: 1px solid var(--gray-300);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: var(--gray-200);
|
||||
border: 1px dashed var(--gray-400);
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
padding: 2.5rem;
|
||||
}
|
||||
|
||||
.discussions-parent .btn-default {
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.discussions-header .btn {
|
||||
float: right;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
{% block page_container %}
|
||||
<main class="{% if not full_width %}container my-4{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="page-header-wrapper">
|
||||
<div class="page-header">
|
||||
{% block header %}{% endblock %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
# 3. call update_nsm(doc_obj) in the on_upate method
|
||||
|
||||
# ------------------------------------------
|
||||
from typing import Iterator
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -271,6 +273,19 @@ class NestedSet(Document):
|
|||
def get_ancestors(self):
|
||||
return get_ancestors_of(self.doctype, self.name)
|
||||
|
||||
def get_parent(self) -> "NestedSet":
|
||||
"""Return the parent Document."""
|
||||
parent_name = self.get(self.nsm_parent_field)
|
||||
if parent_name:
|
||||
return frappe.get_doc(self.doctype, parent_name)
|
||||
|
||||
def get_children(self) -> Iterator["NestedSet"]:
|
||||
"""Return a generator that yields child Documents."""
|
||||
child_names = frappe.get_list(self.doctype, filters={self.nsm_parent_field: self.name}, pluck="name")
|
||||
for name in child_names:
|
||||
yield frappe.get_doc(self.doctype, name)
|
||||
|
||||
|
||||
def get_root_of(doctype):
|
||||
"""Get root element of a DocType with a tree structure"""
|
||||
result = frappe.db.sql("""select t1.name from `tab{0}` t1 where
|
||||
|
|
|
|||
|
|
@ -5,18 +5,6 @@ import click
|
|||
|
||||
import frappe
|
||||
|
||||
try:
|
||||
from weasyprint import HTML, CSS
|
||||
except OSError:
|
||||
click.secho(
|
||||
"\n".join(["WeasyPrint depdends on additional system dependencies.",
|
||||
"Follow instructions specific to your operating system:",
|
||||
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html"]),
|
||||
fg="yellow"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_pdf(doctype, name, print_format, letterhead=None):
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
|
|
@ -121,6 +109,8 @@ class PrintFormatGenerator:
|
|||
pdf: a bytes sequence
|
||||
The rendered PDF.
|
||||
"""
|
||||
HTML, CSS = import_weasyprint()
|
||||
|
||||
self._make_header_footer()
|
||||
|
||||
self.context.update(
|
||||
|
|
@ -151,6 +141,8 @@ class PrintFormatGenerator:
|
|||
element_height: float
|
||||
The height of this element, which will be then translated in a html height
|
||||
"""
|
||||
HTML, CSS = import_weasyprint()
|
||||
|
||||
html = HTML(string=getattr(self, f"{element}_html"), base_url=self.base_url,)
|
||||
element_doc = html.render(
|
||||
stylesheets=[CSS(string="@page {size: A4 portrait; margin: 0;}")]
|
||||
|
|
@ -254,3 +246,20 @@ class PrintFormatGenerator:
|
|||
if box.element_tag == element:
|
||||
return box
|
||||
return PrintFormatGenerator.get_element(box.all_children(), element)
|
||||
|
||||
|
||||
def import_weasyprint():
|
||||
try:
|
||||
from weasyprint import HTML, CSS
|
||||
return HTML, CSS
|
||||
except OSError:
|
||||
message = "\n".join([
|
||||
"WeasyPrint depdends on additional system dependencies.",
|
||||
"Follow instructions specific to your operating system:",
|
||||
"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html"
|
||||
])
|
||||
click.secho(
|
||||
message,
|
||||
fg="yellow"
|
||||
)
|
||||
frappe.throw(message)
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class BlogPost(WebsiteGenerator):
|
|||
context.parents = [{"name": _("Home"), "route":"/"},
|
||||
{"name": "Blog", "route": "/blog"},
|
||||
{"label": context.category.title, "route":context.category.route}]
|
||||
context.guest_allowed = True
|
||||
context.guest_allowed = frappe.db.get_single_value("Blog Settings", "allow_guest_to_comment", cache=True)
|
||||
|
||||
def fetch_cta(self):
|
||||
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True):
|
||||
|
|
@ -139,26 +139,36 @@ class BlogPost(WebsiteGenerator):
|
|||
context.comment_list = get_comment_list(self.doctype, self.name)
|
||||
|
||||
if not context.comment_list:
|
||||
context.comment_text = _('No comments yet')
|
||||
context.comment_text = 0
|
||||
else:
|
||||
if(len(context.comment_list)) == 1:
|
||||
context.comment_text = _('1 comment')
|
||||
else:
|
||||
context.comment_text = _('{0} comments').format(len(context.comment_list))
|
||||
context.comment_text = len(context.comment_list)
|
||||
|
||||
def load_feedback(self, context):
|
||||
user = frappe.session.user
|
||||
if user == 'Guest':
|
||||
user = ''
|
||||
|
||||
feedback = frappe.get_all('Feedback',
|
||||
fields=['feedback', 'rating'],
|
||||
fields=['like'],
|
||||
filters=dict(
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
ip_address=frappe.local.request_ip,
|
||||
owner=user
|
||||
)
|
||||
)
|
||||
|
||||
like_count = 0
|
||||
|
||||
if frappe.db.count('Feedback'):
|
||||
like_count = frappe.db.count('Feedback',
|
||||
filters = dict(
|
||||
reference_doctype = self.doctype,
|
||||
reference_name = self.name,
|
||||
like = True
|
||||
)
|
||||
)
|
||||
|
||||
context.user_feedback = feedback[0] if feedback else ''
|
||||
context.like_count = like_count
|
||||
|
||||
def set_read_time(self):
|
||||
content = self.content or self.content_html or ''
|
||||
|
|
|
|||
|
|
@ -43,21 +43,26 @@
|
|||
) }}
|
||||
{%- endif -%}
|
||||
<div class="blog-footer">
|
||||
<div>
|
||||
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
|
||||
<div class="blog-feedback">
|
||||
{% if not disable_feedback %}
|
||||
{% include 'templates/includes/feedback/feedback.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{% if social_links %}
|
||||
{% if social_links %}
|
||||
<div>
|
||||
{% for link in social_links %}
|
||||
<a href="{{ link.link }}" class="text-muted ml-2 fa fa-{{ link.icon }}" target="_blank"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if blogger_info %}
|
||||
<hr class="my-5">
|
||||
{% include "templates/includes/blog/blogger.html" %}
|
||||
<hr class="mt-2 mb-5">
|
||||
{% include "templates/includes/blog/blogger.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if not disable_comments %}
|
||||
|
|
@ -65,11 +70,6 @@
|
|||
{% include 'templates/includes/comments/comments.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not disable_feedback %}
|
||||
<div class="blog-feedback">
|
||||
{% include 'templates/includes/feedback/feedback.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"column_break",
|
||||
"enable_social_sharing",
|
||||
"show_cta_in_blog",
|
||||
"allow_guest_to_comment",
|
||||
"cta_section",
|
||||
"title",
|
||||
"subtitle",
|
||||
|
|
@ -17,7 +18,9 @@
|
|||
"cta_label",
|
||||
"cta_url",
|
||||
"section_break_12",
|
||||
"feedback_limit"
|
||||
"feedback_limit",
|
||||
"column_break_14",
|
||||
"comment_limit"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -86,18 +89,35 @@
|
|||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"default": "5",
|
||||
"description": "Feedback limit per hour",
|
||||
"fieldname": "feedback_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Feedback limit"
|
||||
},
|
||||
{
|
||||
"default": "5",
|
||||
"description": "Comment limit per hour",
|
||||
"fieldname": "comment_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Comment limit"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "allow_guest_to_comment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow guest to comment"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-30 13:00:18.887103",
|
||||
"modified": "2021-10-28 20:44:44.143193",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blog Settings",
|
||||
|
|
|
|||
|
|
@ -15,4 +15,7 @@ class BlogSettings(Document):
|
|||
clear_cache("writers")
|
||||
|
||||
def get_feedback_limit():
|
||||
return frappe.db.get_single_value("Blog Settings", "feedback_limit") or 0
|
||||
return frappe.db.get_single_value("Blog Settings", "feedback_limit") or 5
|
||||
|
||||
def get_comment_limit():
|
||||
return frappe.db.get_single_value("Blog Settings", "comment_limit") or 5
|
||||
|
|
@ -43,5 +43,12 @@ body {
|
|||
--text-color: #{$body-text-color};
|
||||
--text-light: #{$body-text-color};
|
||||
{% endif -%}
|
||||
{% if not button_rounded_corners %}
|
||||
--border-radius-sm: 0px;
|
||||
--border-radius: 0px;
|
||||
--border-radius-md: 0px;
|
||||
--border-radius-lg: 0px;
|
||||
--border-radius-full: 0px;
|
||||
{% endif -%}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
{% set doctype = "Web Page" %}
|
||||
{% set empty_state_title = _("No {0}").format(title) %}
|
||||
{% set empty_state_subtitle = _("There are no {0} for this {1}, why don't you start one!").format(title | lower , doctype | lower) %}
|
||||
|
||||
{% include "frappe/templates/discussions/discussions_section.html" %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
{% include "templates/includes/login/login.css" %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="login-content container" style="max-width: 800px;">
|
||||
<div class="text-center login-content container page-card">
|
||||
<form role="form form-signin" method="POST"
|
||||
action="/api/method/frappe.templates.pages.login.login_oauth_user">
|
||||
<h2 class="form-signin-heading">{{ _("One Last Step") }}</h2>
|
||||
<h3 class="form-signin-heading mb-4">{{ _("One Last Step") }}</h3>
|
||||
<input type="hidden" name="key" value="{{ frappe.form_dict.key }}">
|
||||
<input type="email" name="email_id"
|
||||
class="form-control" placeholder="{{ _('Email Address') }}" required autofocus><br>
|
||||
<input name="first_name"
|
||||
class="form-control" placeholder="{{ _('First Name') }}" required><br>
|
||||
<input name="last_name"
|
||||
class="form-control" placeholder="{{ _('Last Name') }}">
|
||||
<br>
|
||||
<button class="btn btn-primary btn-complete-signup" type="submit">{{ _("Complete") }}</button>
|
||||
<div class="form-group">
|
||||
<input type="email" name="email_id"
|
||||
class="form-control" placeholder="{{ _('Email Address') }}" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input name="first_name"
|
||||
class="form-control" placeholder="{{ _('First Name') }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input name="last_name"
|
||||
class="form-control" placeholder="{{ _('Last Name') }}">
|
||||
</div>
|
||||
<button class="btn btn-primary mt-2 btn-sm btn-complete-signup w-100" type="submit">{{ _("Complete") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"fuse.js": "^3.4.6",
|
||||
"highlight.js": "^10.4.1",
|
||||
"html5-qrcode": "^2.0.11",
|
||||
"jquery": "3.5.0",
|
||||
"jquery": "3.6.0",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jsbarcode": "^3.9.0",
|
||||
"localforage": "^1.9.0",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ boto3~=1.17.53
|
|||
braintree~=4.8.0
|
||||
chardet~=4.0.0
|
||||
Click~=7.1.2
|
||||
colorama~=0.4.4
|
||||
coverage==5.5
|
||||
croniter~=1.0.11
|
||||
cryptography~=3.4.7
|
||||
dropbox~=11.7.0
|
||||
|
|
@ -28,7 +26,6 @@ Jinja2~=3.0.1
|
|||
ldap3~=2.9
|
||||
markdown2~=2.4.0
|
||||
maxminddb-geolite2==2018.703
|
||||
ndg-httpsclient~=0.5.1
|
||||
num2words~=0.5.10
|
||||
oauthlib~=3.1.0
|
||||
openpyxl~=3.0.7
|
||||
|
|
|
|||
|
|
@ -2320,7 +2320,12 @@ jpeg-js@^0.3.2:
|
|||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.6.tgz#c40382aac9506e7d1f2d856eb02f6c7b2a98b37c"
|
||||
integrity sha512-MUj2XlMB8kpe+8DJUGH/3UJm4XpI8XEgZQ+CiHDeyrGoKPdW/8FJv6ku+3UiYm5Fz3CWaL+iXmD8Q4Ap6aC1Jw==
|
||||
|
||||
jquery@3.5.0, "jquery@>=2.0.0 <4.0.0":
|
||||
jquery@3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
|
||||
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
|
||||
|
||||
"jquery@>=2.0.0 <4.0.0":
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9"
|
||||
integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue