Merge branch 'develop' into mariadb-client-refactor
This commit is contained in:
commit
43f378d848
45 changed files with 261 additions and 2561 deletions
|
|
@ -134,7 +134,6 @@
|
|||
"Webcam": true,
|
||||
"PhotoSwipe": true,
|
||||
"PhotoSwipeUI_Default": true,
|
||||
"fluxify": true,
|
||||
"io": true,
|
||||
"JsBarcode": true,
|
||||
"L": true,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ The following 3rd-party software packages may be used by or distributed with <ht
|
|||
- Leaflet.Locate - (c) 2016 Dominik Moritz
|
||||
- Leaflet.draw - (c) 2012-2017, Jacob Toye, Jon West, Smartrak
|
||||
- Leaflet.EasyButton - MIT License, (C) 2014 Daniel Montague
|
||||
- Fluxify - GNU GENERAL PUBLIC LICENSE Version 2 (C) 1989 - 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
|
||||
### Icon Fonts
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ context('Workspace Blocks', () => {
|
|||
// test filter-list
|
||||
cy.get('@todo-quick-list').realHover().find('.widget-control .filter-list').click();
|
||||
|
||||
cy.get_open_dialog().find('.filter-field .input-with-feedback').clear().type('Approved');
|
||||
cy.get_open_dialog().find('.filter-field .input-with-feedback').type('{selectall}Approved');
|
||||
cy.get_open_dialog().find('.modal-header').click();
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ class HTTPRequest:
|
|||
|
||||
|
||||
class LoginManager:
|
||||
|
||||
__slots__ = ("user", "info", "full_name", "user_type", "resume")
|
||||
|
||||
def __init__(self):
|
||||
self.user = None
|
||||
self.info = None
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from frappe.core.doctype.navbar_settings.navbar_settings import get_app_logo, ge
|
|||
from frappe.desk.doctype.route_history.route_history import frequently_visited_links
|
||||
from frappe.desk.form.load import get_meta_bundle
|
||||
from frappe.email.inbox import get_email_accounts
|
||||
from frappe.geo.country_info import get_all
|
||||
from frappe.model.base_document import get_controller
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import Count
|
||||
|
|
@ -21,7 +20,7 @@ from frappe.social.doctype.energy_point_settings.energy_point_settings import (
|
|||
is_energy_point_enabled,
|
||||
)
|
||||
from frappe.translate import get_lang_dict
|
||||
from frappe.utils import add_user_info, get_time_zone
|
||||
from frappe.utils import add_user_info, cstr, get_time_zone
|
||||
from frappe.utils.change_log import get_versions
|
||||
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
|
||||
|
||||
|
|
@ -68,7 +67,6 @@ def get_bootinfo():
|
|||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})
|
||||
bootinfo.navbar_settings = get_navbar_settings()
|
||||
bootinfo.notification_settings = get_notification_settings()
|
||||
get_country_codes(bootinfo)
|
||||
set_time_zone(bootinfo)
|
||||
|
||||
# ipinfo
|
||||
|
|
@ -142,6 +140,10 @@ def get_allowed_reports(cache=False):
|
|||
return get_user_pages_or_reports("Report", cache=cache)
|
||||
|
||||
|
||||
def get_allowed_report_names(cache=False) -> set[str]:
|
||||
return {cstr(report) for report in get_allowed_reports(cache).keys() if report}
|
||||
|
||||
|
||||
def get_user_pages_or_reports(parent, cache=False):
|
||||
_cache = frappe.cache()
|
||||
|
||||
|
|
@ -389,11 +391,6 @@ def get_notification_settings():
|
|||
return frappe.get_cached_doc("Notification Settings", frappe.session.user)
|
||||
|
||||
|
||||
def get_country_codes(bootinfo):
|
||||
country_codes = get_all()
|
||||
bootinfo.country_codes = frappe._dict(country_codes)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_link_title_doctypes():
|
||||
dts = frappe.get_all("DocType", {"show_title_field_in_link": 1})
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from frappe.core.doctype.doctype.doctype import validate_series
|
|||
from frappe.model.document import Document
|
||||
from frappe.model.naming import NamingSeries
|
||||
from frappe.permissions import get_doctypes_with_read
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
class NamingSeriesNotSetError(frappe.ValidationError):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
from markdownify import markdownify as md
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
|
|
@ -86,3 +88,8 @@ def ljust_list(_list, length, fill_word=None):
|
|||
_list.extend([fill_word] * fill_length)
|
||||
|
||||
return _list
|
||||
|
||||
|
||||
def html2text(html, strip_links=False, wrap=True):
|
||||
strip = ["a"] if strip_links else None
|
||||
return md(html, heading_style="ATX", strip=strip, wrap=wrap)
|
||||
|
|
|
|||
|
|
@ -116,5 +116,6 @@ def drop_user_and_database(db_name, root_login, root_password):
|
|||
"SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = %s",
|
||||
(db_name,),
|
||||
)
|
||||
root_conn.sql("end")
|
||||
root_conn.sql(f"DROP DATABASE IF EXISTS {db_name}")
|
||||
root_conn.sql(f"DROP USER IF EXISTS {db_name}")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import json
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.boot import get_allowed_reports
|
||||
from frappe.boot import get_allowed_report_names
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
|
|
@ -40,10 +40,7 @@ def get_permission_query_conditions(user):
|
|||
allowed_doctypes = [
|
||||
frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()
|
||||
]
|
||||
allowed_reports = [
|
||||
frappe.db.escape(key) if type(key) == str else key.encode("UTF8")
|
||||
for key in get_allowed_reports()
|
||||
]
|
||||
allowed_reports = [frappe.db.escape(report) for report in get_allowed_report_names()]
|
||||
allowed_modules = [
|
||||
frappe.db.escape(module.get("module_name")) for module in get_modules_from_all_apps_for_user()
|
||||
]
|
||||
|
|
@ -83,10 +80,7 @@ def has_permission(doc, ptype, user):
|
|||
return True
|
||||
|
||||
if doc.chart_type == "Report":
|
||||
allowed_reports = [
|
||||
key if type(key) == str else key.encode("UTF8") for key in get_allowed_reports()
|
||||
]
|
||||
if doc.report_name in allowed_reports:
|
||||
if doc.report_name in get_allowed_report_names():
|
||||
return True
|
||||
else:
|
||||
allowed_doctypes = frappe.permissions.get_doctypes_with_read()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.boot import get_allowed_reports
|
||||
from frappe.boot import get_allowed_report_names
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
|
|
@ -91,10 +91,7 @@ def has_permission(doc, ptype, user):
|
|||
return True
|
||||
|
||||
if doc.type == "Report":
|
||||
allowed_reports = [
|
||||
key if type(key) == str else key.encode("UTF8") for key in get_allowed_reports()
|
||||
]
|
||||
if doc.report_name in allowed_reports:
|
||||
if doc.report_name in get_allowed_report_names():
|
||||
return True
|
||||
else:
|
||||
allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ def get_version(doctype, doc_name, frequency, user):
|
|||
|
||||
|
||||
def get_comments(doctype, doc_name, frequency, user):
|
||||
from html2text import html2text
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
timeline = []
|
||||
filters = get_filters("reference_name", doc_name, frequency, user)
|
||||
|
|
@ -225,7 +225,7 @@ def get_follow_users(doctype, doc_name):
|
|||
|
||||
|
||||
def get_row_changed(row_changed, time, doctype, doc_name, v):
|
||||
from html2text import html2text
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
items = []
|
||||
for d in row_changed:
|
||||
|
|
@ -269,7 +269,7 @@ def get_added_row(added, time, doctype, doc_name, v):
|
|||
|
||||
|
||||
def get_field_changed(changed, time, doctype, doc_name, v):
|
||||
from html2text import html2text
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
items = []
|
||||
for d in changed:
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=F
|
|||
continue
|
||||
label = column.get("label")
|
||||
fieldname = column.get("fieldname")
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
cell_value = cstr(row.get(fieldname, row.get(label, "")))
|
||||
if cint(include_indentation) and "indent" in row and col_idx == 0:
|
||||
cell_value = (" " * cint(row["indent"])) + cstr(cell_value)
|
||||
row_data.append(cell_value)
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import traceback
|
|||
from email.parser import Parser
|
||||
from email.policy import SMTPUTF8
|
||||
|
||||
from html2text import html2text
|
||||
from rq.timeouts import JobTimeoutException
|
||||
|
||||
import frappe
|
||||
from frappe import _, safe_encode, task
|
||||
from frappe.core.utils import html2text
|
||||
from frappe.email.doctype.email_account.email_account import EmailAccount
|
||||
from frappe.email.email_body import add_attachment, get_email, get_formatted_html
|
||||
from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message
|
||||
|
|
|
|||
|
|
@ -332,12 +332,12 @@ class EMail:
|
|||
|
||||
def set_header(self, key, value):
|
||||
if key in self.msg_root:
|
||||
# delete key if found
|
||||
# this is done because adding the same key doesn't override
|
||||
# the existing key, rather appends another header with same key.
|
||||
del self.msg_root[key]
|
||||
|
||||
try:
|
||||
self.msg_root[key] = value
|
||||
except ValueError:
|
||||
self.msg_root[key] = sanitize_email_header(value)
|
||||
self.msg_root[key] = sanitize_email_header(value)
|
||||
|
||||
def as_string(self):
|
||||
"""validate, build message and convert to string"""
|
||||
|
|
@ -580,8 +580,17 @@ def get_header(header=None):
|
|||
return email_header
|
||||
|
||||
|
||||
def sanitize_email_header(str):
|
||||
return str.replace("\r", "").replace("\n", "")
|
||||
def sanitize_email_header(header: str):
|
||||
"""
|
||||
Removes all line boundaries in the headers.
|
||||
|
||||
Email Policy (python's std) has some bugs in it which uses splitlines
|
||||
and raises ValueError (ref: https://github.com/python/cpython/blob/main/Lib/email/policy.py#L143).
|
||||
Hence removing all line boundaries while sanitization of headers to prevent such faliures.
|
||||
The line boundaries which are removed can be found here: https://docs.python.org/3/library/stdtypes.html#str.splitlines
|
||||
"""
|
||||
|
||||
return "".join(header.splitlines())
|
||||
|
||||
|
||||
def get_brand_logo(email_account):
|
||||
|
|
|
|||
|
|
@ -152,20 +152,19 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|||
<h3>Hey John Doe!</h3>
|
||||
<p>This is embedded image you asked for</p>
|
||||
"""
|
||||
email_string = (
|
||||
get_email(
|
||||
recipients=["test@example.com"],
|
||||
sender="me@example.com",
|
||||
subject="Test Subject",
|
||||
content=email_html,
|
||||
header=["Email Title", "green"],
|
||||
)
|
||||
.as_string()
|
||||
.replace("\r\n", "\n")
|
||||
)
|
||||
email_string = get_email(
|
||||
recipients=["test@example.com"],
|
||||
sender="me@example.com",
|
||||
subject="Test Subject\u2028, with line break, \nand Line feed \rand carriage return.",
|
||||
content=email_html,
|
||||
header=["Email Title", "green"],
|
||||
).as_string()
|
||||
# REDESIGN-TODO: Add style for indicators in email
|
||||
self.assertTrue("""<span class=3D"indicator indicator-green"></span>""" in email_string)
|
||||
self.assertTrue("<span>Email Title</span>" in email_string)
|
||||
self.assertIn(
|
||||
"Subject: Test Subject, with line break, and Line feed and carriage return.", email_string
|
||||
)
|
||||
|
||||
def test_get_email_header(self):
|
||||
html = get_header(["This is test", "orange"])
|
||||
|
|
|
|||
|
|
@ -842,7 +842,7 @@
|
|||
"El Salvador": {
|
||||
"code": "sv",
|
||||
"currency": "USD",
|
||||
"currency_fraction": "Centavo",
|
||||
"currency_fraction": "Cent",
|
||||
"currency_fraction_units": 100,
|
||||
"smallest_currency_fraction_value": 0.01,
|
||||
"currency_name": "Dolar estadounidense",
|
||||
|
|
|
|||
|
|
@ -4,5 +4,10 @@
|
|||
# pre loaded
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
test_records = frappe.get_test_records("Currency")
|
||||
|
||||
class TestUser(FrappeTestCase):
|
||||
def test_default_currency_on_setup(self):
|
||||
usd = frappe.get_doc("Currency", "USD")
|
||||
self.assertDocumentEqual({"enabled": 1, "fraction": "Cent"}, usd)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import click
|
||||
import requests
|
||||
from html2text import html2text
|
||||
|
||||
import frappe
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
|
||||
def frappecloud_migrator(local_site):
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ def log_file():
|
|||
|
||||
|
||||
class Monitor:
|
||||
__slots__ = ("data",)
|
||||
|
||||
def __init__(self, transaction_type, method, kwargs):
|
||||
try:
|
||||
self.data = frappe._dict(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import frappe
|
|||
import frappe.share
|
||||
from frappe import _, msgprint
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint
|
||||
from frappe.utils import cint, cstr
|
||||
|
||||
rights = (
|
||||
"select",
|
||||
|
|
@ -360,9 +360,7 @@ def has_controller_permissions(doc, ptype, user=None):
|
|||
|
||||
|
||||
def get_doctypes_with_read():
|
||||
return list(
|
||||
{p.parent if type(p.parent) == str else p.parent.encode("UTF8") for p in get_valid_perms()}
|
||||
)
|
||||
return list({cstr(p.parent) for p in get_valid_perms() if p.parent})
|
||||
|
||||
|
||||
def get_valid_perms(doctype=None, user=None):
|
||||
|
|
|
|||
2337
frappe/public/css/font-awesome.css
vendored
2337
frappe/public/css/font-awesome.css
vendored
File diff suppressed because it is too large
Load diff
4
frappe/public/css/fonts/fontawesome/LICENSE
Normal file
4
frappe/public/css/fonts/fontawesome/LICENSE
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
The Font Awesome font is licensed under the SIL OFL 1.1:
|
||||
http://scripts.sil.org/OFL
|
||||
Font Awesome CSS, LESS, and Sass files are licensed under the MIT License:
|
||||
https://opensource.org/licenses/mit-license.html
|
||||
|
|
@ -13,10 +13,3 @@
|
|||
visibility: visible;
|
||||
}
|
||||
|
||||
.toggle.expand {
|
||||
background: url("/assets/frappe/js/lib/slickgrid/images/expand.gif") no-repeat center center;
|
||||
}
|
||||
|
||||
.toggle.collapse {
|
||||
background: url("/assets/frappe/js/lib/slickgrid/images/collapse.gif") no-repeat center center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
|
||||
import localforage from "localforage";
|
||||
import PhonePicker from '../../phone_picker/phone_picker';
|
||||
|
||||
frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlData {
|
||||
|
||||
make_input() {
|
||||
async make_input() {
|
||||
await this.setup_country_codes();
|
||||
super.make_input();
|
||||
this.setup_country_code_picker();
|
||||
this.input_events();
|
||||
}
|
||||
|
||||
async setup_country_codes() {
|
||||
const key = "country_code_info"
|
||||
let data = await localforage.getItem(key);
|
||||
if (data) {
|
||||
this.country_codes = data;
|
||||
} else {
|
||||
const data = await frappe.xcall("frappe.geo.country_info.get_country_timezone_info");
|
||||
this.country_codes = data?.country_info;
|
||||
localforage.setItem(key, this.country_codes);
|
||||
}
|
||||
}
|
||||
|
||||
input_events() {
|
||||
this.$input.keydown((e) => {
|
||||
const key_code = e.keyCode;
|
||||
|
|
@ -22,10 +35,10 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
// Replaces code when selected and removes previously selected.
|
||||
this.country_code_picker.on_change = (country) => {
|
||||
if (!country) {
|
||||
return this.reset_inputx();
|
||||
return this.reset_input();
|
||||
}
|
||||
const country_code = frappe.boot.country_codes[country].code;
|
||||
const country_isd = frappe.boot.country_codes[country].isd;
|
||||
const country_code = this.country_codes[country].code;
|
||||
const country_isd = this.country_codes[country].isd;
|
||||
this.set_flag(country_code);
|
||||
this.$icon = this.selected_icon.find('svg');
|
||||
this.$flag = this.selected_icon.find('img');
|
||||
|
|
@ -69,7 +82,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
let picker_wrapper = $('<div>');
|
||||
this.country_code_picker = new PhonePicker({
|
||||
parent: picker_wrapper,
|
||||
countries: frappe.boot.country_codes
|
||||
countries: this.country_codes
|
||||
});
|
||||
|
||||
this.$wrapper.popover({
|
||||
|
|
@ -119,6 +132,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
}
|
||||
|
||||
reset_input() {
|
||||
if (!this.$input) return;
|
||||
this.$input.val("");
|
||||
this.$wrapper.find('.country').text("");
|
||||
if (this.selected_icon.find('svg').hasClass('hide')) {
|
||||
|
|
@ -128,7 +142,10 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
this.$input.css("padding-left", 30);
|
||||
}
|
||||
|
||||
set_formatted_input(value) {
|
||||
async set_formatted_input(value) {
|
||||
if (!this.country_codes) {
|
||||
await this.setup_country_codes();
|
||||
}
|
||||
if (value && value.includes('-') && value.split('-').length == 2) {
|
||||
let isd = this.value.split("-")[0];
|
||||
this.get_country_code_and_change_flag(isd);
|
||||
|
|
@ -158,7 +175,7 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
|
||||
// country_code for India is 'in'
|
||||
get_country_code_and_change_flag(isd) {
|
||||
let country_data = frappe.boot.country_codes;
|
||||
let country_data = this.country_codes;
|
||||
let flag = this.selected_icon.find('img');
|
||||
for (const country in country_data) {
|
||||
if (Object.values(country_data[country]).includes(isd)) {
|
||||
|
|
@ -175,12 +192,12 @@ frappe.ui.form.ControlPhone = class ControlPhone extends frappe.ui.form.ControlD
|
|||
}
|
||||
|
||||
get_country(country) {
|
||||
const country_codes = frappe.boot.country_codes;
|
||||
const country_codes = this.country_codes;
|
||||
return country_codes[country].isd;
|
||||
}
|
||||
|
||||
get_country_flag(country) {
|
||||
const country_codes = frappe.boot.country_codes;
|
||||
const country_codes = this.country_codes;
|
||||
let code = country_codes[country].code;
|
||||
return frappe.utils.flag(code);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export default class Tab {
|
|||
set_active() {
|
||||
this.parent.find('.nav-link').tab('show');
|
||||
this.wrapper.addClass('active');
|
||||
this.frm?.set_active_tab?.(this);
|
||||
}
|
||||
|
||||
is_active() {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default class ListSettings {
|
|||
<div class="control-input-wrapper">
|
||||
${fields}
|
||||
</div>
|
||||
<p class="help-box small text-muted hidden-xs">
|
||||
<p class="help-box small text-muted">
|
||||
<a class="add-new-fields text-muted">
|
||||
+ Add / Remove Fields
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1456,7 +1456,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
on_update() {}
|
||||
|
||||
update_url_with_filters() {
|
||||
window.history.replaceState(null, null, this.get_url_with_filters());
|
||||
if (frappe.get_route_str() == this.page_name && !this.report_name) {
|
||||
// only update URL if the route still matches current page.
|
||||
// do not update if current list is a "saved report".
|
||||
window.history.replaceState(null, null, this.get_url_with_filters());
|
||||
}
|
||||
}
|
||||
|
||||
get_url_with_filters() {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
// TODO: Refactor for better UX
|
||||
|
||||
import Vuex from 'vuex';
|
||||
|
||||
frappe.provide("frappe.views");
|
||||
|
||||
(function() {
|
||||
|
||||
var method_prefix = 'frappe.desk.doctype.kanban_board.kanban_board.';
|
||||
|
||||
var store = fluxify.createStore({
|
||||
id: 'store',
|
||||
initialState: {
|
||||
let columns_unwatcher = null;
|
||||
|
||||
var store = new Vuex.Store({
|
||||
state: {
|
||||
doctype: '',
|
||||
board: {},
|
||||
card_meta: {},
|
||||
|
|
@ -18,12 +21,16 @@ frappe.provide("frappe.views");
|
|||
cur_list: {},
|
||||
empty_state: true
|
||||
},
|
||||
actionCallbacks: {
|
||||
init: function(updater, opts) {
|
||||
updater.set({
|
||||
mutations: {
|
||||
update_state(state, obj) {
|
||||
Object.assign(state, obj);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
init: function(context, opts) {
|
||||
context.commit("update_state", {
|
||||
empty_state: true
|
||||
});
|
||||
|
||||
var board = opts.board;
|
||||
var card_meta = opts.card_meta;
|
||||
opts.card_meta = card_meta;
|
||||
|
|
@ -32,8 +39,7 @@ frappe.provide("frappe.views");
|
|||
return prepare_card(card, opts);
|
||||
});
|
||||
var columns = prepare_columns(board.columns);
|
||||
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
doctype: opts.doctype,
|
||||
board: board,
|
||||
card_meta: card_meta,
|
||||
|
|
@ -44,20 +50,20 @@ frappe.provide("frappe.views");
|
|||
wrapper: opts.wrapper
|
||||
});
|
||||
},
|
||||
update_cards: function(updater, cards) {
|
||||
var state = this;
|
||||
update_cards: function(context, cards) {
|
||||
var state = context.state;
|
||||
var _cards = cards
|
||||
.map(card => prepare_card(card, state))
|
||||
.concat(this.cards)
|
||||
.concat(state.cards)
|
||||
.uniqBy(card => card.name);
|
||||
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
cards: _cards
|
||||
});
|
||||
},
|
||||
add_column: function(updater, col) {
|
||||
if(frappe.model.can_create('Custom Field')) {
|
||||
fluxify.doAction('update_column', col, 'add');
|
||||
add_column: function(context, col) {
|
||||
if (frappe.model.can_create('Custom Field')) {
|
||||
store.dispatch('update_column', {col, action: 'add'});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
title: __('Not permitted'),
|
||||
|
|
@ -66,15 +72,15 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
}
|
||||
},
|
||||
archive_column: function(updater, col) {
|
||||
fluxify.doAction('update_column', col, 'archive');
|
||||
archive_column: function(context, col) {
|
||||
store.dispatch('update_column', {col, action: 'archive'});
|
||||
},
|
||||
restore_column: function(updater, col) {
|
||||
fluxify.doAction('update_column', col, 'restore');
|
||||
restore_column: function(context, col) {
|
||||
store.dispatch('update_column', {col, action: 'restore'});
|
||||
},
|
||||
update_column: function(updater, col, action) {
|
||||
var doctype = this.doctype;
|
||||
var board = this.board;
|
||||
update_column: function(context, {col, action}) {
|
||||
var doctype = context.state.doctype;
|
||||
var board = context.state.board;
|
||||
fetch_customization(doctype)
|
||||
.then(function(doc) {
|
||||
return modify_column_field_in_c11n(doc, board, col.title, action);
|
||||
|
|
@ -84,23 +90,23 @@ frappe.provide("frappe.views");
|
|||
return update_kanban_board(board.name, col.title, action);
|
||||
}).then(function(r) {
|
||||
var cols = r.message;
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
columns: prepare_columns(cols)
|
||||
});
|
||||
}, function(err) {
|
||||
console.error(err); // eslint-disable-line
|
||||
});
|
||||
},
|
||||
add_card: function(updater, card_title, column_title) {
|
||||
var doc = frappe.model.get_new_doc(this.doctype);
|
||||
var field = this.card_meta.title_field;
|
||||
var quick_entry = this.card_meta.quick_entry;
|
||||
var state = this;
|
||||
add_card: function(context, {card_title, column_title}) {
|
||||
var state = context.state;
|
||||
var doc = frappe.model.get_new_doc(state.doctype);
|
||||
var field = state.card_meta.title_field;
|
||||
var quick_entry = state.card_meta.quick_entry;
|
||||
|
||||
var doc_fields = {};
|
||||
doc_fields[field.fieldname] = card_title;
|
||||
doc_fields[this.board.field_name] = column_title;
|
||||
this.cur_list.filter_area.get().forEach(function(f) {
|
||||
doc_fields[state.board.field_name] = column_title;
|
||||
state.cur_list.filter_area.get().forEach(function(f) {
|
||||
if (f[2] !== "=") return;
|
||||
doc_fields[f[1]] = f[3];
|
||||
});
|
||||
|
|
@ -114,7 +120,7 @@ frappe.provide("frappe.views");
|
|||
const cards = [...state.cards, card];
|
||||
// remember the name which we will override later
|
||||
const old_name = doc.name;
|
||||
updater.set({ cards });
|
||||
context.commit("update_state", { cards });
|
||||
|
||||
if (field && !quick_entry) {
|
||||
return insert_doc(doc)
|
||||
|
|
@ -125,49 +131,49 @@ frappe.provide("frappe.views");
|
|||
const card = prepare_card(updated_doc, state);
|
||||
const new_cards = state.cards.slice();
|
||||
new_cards[index] = card;
|
||||
updater.set({ cards: new_cards });
|
||||
context.commit("update_state", { cards: new_cards });
|
||||
const args = {
|
||||
new: 1,
|
||||
name: card.name,
|
||||
colname: updated_doc[state.board.field_name],
|
||||
};
|
||||
fluxify.doAction('update_order_for_single_card', args);
|
||||
store.dispatch('update_order_for_single_card', args);
|
||||
});
|
||||
} else {
|
||||
frappe.new_doc(this.doctype, doc);
|
||||
frappe.new_doc(state.doctype, doc);
|
||||
}
|
||||
},
|
||||
update_card: function(updater, card) {
|
||||
update_card: function(context, card) {
|
||||
var index = -1;
|
||||
this.cards.forEach(function(c, i) {
|
||||
context.state.cards.forEach(function(c, i) {
|
||||
if (c.name === card.name) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
var cards = this.cards.slice();
|
||||
var cards = context.state.cards.slice();
|
||||
if (index !== -1) {
|
||||
cards.splice(index, 1, card);
|
||||
}
|
||||
updater.set({ cards: cards });
|
||||
context.commit("update_state", { cards: cards });
|
||||
},
|
||||
update_order_for_single_card: function(updater, card) {
|
||||
update_order_for_single_card: function(context, card) {
|
||||
// cache original order
|
||||
const _cards = this.cards.slice();
|
||||
const _columns = this.columns.slice();
|
||||
const _cards = context.state.cards.slice();
|
||||
const _columns = context.state.columns.slice();
|
||||
let args = {};
|
||||
let method_name = "";
|
||||
|
||||
if (card.new) {
|
||||
method_name = "add_card";
|
||||
args = {
|
||||
board_name: this.board.name,
|
||||
board_name: context.state.board.name,
|
||||
docname: card.name,
|
||||
colname: card.colname,
|
||||
};
|
||||
} else {
|
||||
method_name = "update_order_for_single_card";
|
||||
args = {
|
||||
board_name: this.board.name,
|
||||
board_name: context.state.board.name,
|
||||
docname: card.name,
|
||||
from_colname: card.from_colname,
|
||||
to_colname: card.to_colname,
|
||||
|
|
@ -184,7 +190,7 @@ frappe.provide("frappe.views");
|
|||
let updated_cards = [{'name': card.name, 'column': card.to_colname || card.colname}];
|
||||
let cards = update_cards_column(updated_cards);
|
||||
let columns = prepare_columns(board.columns);
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
cards: cards,
|
||||
columns: columns
|
||||
});
|
||||
|
|
@ -192,20 +198,20 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
}).fail(function() {
|
||||
// revert original order
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
cards: _cards,
|
||||
columns: _columns
|
||||
});
|
||||
frappe.dom.unfreeze();
|
||||
});
|
||||
},
|
||||
update_order: function(updater) {
|
||||
update_order: function(context) {
|
||||
// cache original order
|
||||
const _cards = this.cards.slice();
|
||||
const _columns = this.columns.slice();
|
||||
const _cards = context.state.cards.slice();
|
||||
const _columns = context.state.columns.slice();
|
||||
|
||||
const order = {};
|
||||
this.wrapper.find('.kanban-column[data-column-value]')
|
||||
context.state.wrapper.find('.kanban-column[data-column-value]')
|
||||
.each(function() {
|
||||
var col_name = $(this).data().columnValue;
|
||||
order[col_name] = [];
|
||||
|
|
@ -218,7 +224,7 @@ frappe.provide("frappe.views");
|
|||
frappe.call({
|
||||
method: method_prefix + "update_order",
|
||||
args: {
|
||||
board_name: this.board.name,
|
||||
board_name: context.state.board.name,
|
||||
order: order
|
||||
},
|
||||
callback: (r) => {
|
||||
|
|
@ -226,46 +232,46 @@ frappe.provide("frappe.views");
|
|||
var updated_cards = r.message[1];
|
||||
var cards = update_cards_column(updated_cards);
|
||||
var columns = prepare_columns(board.columns);
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
cards: cards,
|
||||
columns: columns
|
||||
});
|
||||
}
|
||||
}).fail(function() {
|
||||
// revert original order
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
cards: _cards,
|
||||
columns: _columns
|
||||
});
|
||||
});
|
||||
},
|
||||
update_column_order: function(updater, order) {
|
||||
update_column_order: function(context, order) {
|
||||
return frappe.call({
|
||||
method: method_prefix + "update_column_order",
|
||||
args: {
|
||||
board_name: this.board.name,
|
||||
board_name: context.state.board.name,
|
||||
order: order
|
||||
}
|
||||
}).then(function(r) {
|
||||
var board = r.message;
|
||||
var columns = prepare_columns(board.columns);
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
columns: columns
|
||||
});
|
||||
});
|
||||
},
|
||||
set_indicator: function(updater, column, color) {
|
||||
set_indicator: function(context, {column, color}) {
|
||||
return frappe.call({
|
||||
method: method_prefix + "set_indicator",
|
||||
args: {
|
||||
board_name: this.board.name,
|
||||
board_name: context.state.board.name,
|
||||
column_name: column.title,
|
||||
indicator: color
|
||||
}
|
||||
}).then(function(r) {
|
||||
var board = r.message;
|
||||
var columns = prepare_columns(board.columns);
|
||||
updater.set({
|
||||
context.commit("update_state", {
|
||||
columns: columns
|
||||
});
|
||||
});
|
||||
|
|
@ -285,20 +291,29 @@ frappe.provide("frappe.views");
|
|||
opts.cards = cards;
|
||||
|
||||
if(self.wrapper.find('.kanban').length > 0 && self.cur_list.start !== 0) {
|
||||
fluxify.doAction('update_cards', cards);
|
||||
store.dispatch('update_cards', cards);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
fluxify.doAction('init', opts);
|
||||
store.off('change:columns').on('change:columns', make_columns);
|
||||
store.dispatch('init', opts);
|
||||
columns_unwatcher && columns_unwatcher();
|
||||
store.watch((state, getters) => {
|
||||
return state.columns;
|
||||
}, make_columns);
|
||||
prepare();
|
||||
store.on('change:cur_list', setup_restore_columns);
|
||||
store.on('change:columns', setup_restore_columns);
|
||||
store.on('change:empty_state', show_empty_state);
|
||||
fluxify.doAction('update_order');
|
||||
store.watch((state, getters) => {
|
||||
return state.cur_list;
|
||||
}, setup_restore_columns);
|
||||
columns_unwatcher = store.watch((state, getters) => {
|
||||
return state.columns;
|
||||
}, setup_restore_columns);
|
||||
store.watch((state, getters) => {
|
||||
return state.empty_state;
|
||||
}, show_empty_state);
|
||||
store.dispatch('update_order');
|
||||
}
|
||||
|
||||
function prepare() {
|
||||
|
|
@ -316,7 +331,7 @@ frappe.provide("frappe.views");
|
|||
|
||||
function make_columns() {
|
||||
self.$kanban_board.find(".kanban-column").not(".add-new-column").remove();
|
||||
var columns = store.getState().columns;
|
||||
var columns = store.state.columns;
|
||||
|
||||
columns.filter(is_active_column).map(function(col) {
|
||||
frappe.views.KanbanBoardColumn(col, self.$kanban_board);
|
||||
|
|
@ -338,7 +353,7 @@ frappe.provide("frappe.views");
|
|||
onEnd: function() {
|
||||
var order = sortable.toArray();
|
||||
order = order.slice(1);
|
||||
fluxify.doAction('update_column_order', order);
|
||||
store.dispatch('update_column_order', order);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -365,7 +380,7 @@ frappe.provide("frappe.views");
|
|||
var col = {
|
||||
title: title.trim()
|
||||
};
|
||||
fluxify.doAction('add_column', col);
|
||||
store.dispatch('add_column', col);
|
||||
$compose_column_form.find('input').val('');
|
||||
$compose_column.show();
|
||||
$compose_column_form.hide();
|
||||
|
|
@ -421,8 +436,8 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function setup_restore_columns() {
|
||||
var cur_list = store.getState().cur_list;
|
||||
var columns = store.getState().columns;
|
||||
var cur_list = store.state.cur_list;
|
||||
var columns = store.state.columns;
|
||||
var list_row_right = cur_list.$page
|
||||
.find(`[data-list-renderer='Kanban'] .list-row-right`)
|
||||
.css('margin-right', '15px');
|
||||
|
|
@ -456,12 +471,12 @@ frappe.provide("frappe.views");
|
|||
title: column_title,
|
||||
status: 'Archived'
|
||||
};
|
||||
fluxify.doAction('restore_column', col);
|
||||
store.dispatch('restore_column', col);
|
||||
});
|
||||
}
|
||||
|
||||
function show_empty_state() {
|
||||
var empty_state = store.getState().empty_state;
|
||||
var empty_state = store.state.empty_state;
|
||||
|
||||
if(empty_state) {
|
||||
self.$kanban_board.find('.kanban-column').hide();
|
||||
|
|
@ -485,7 +500,9 @@ frappe.provide("frappe.views");
|
|||
make_dom();
|
||||
setup_sortable();
|
||||
make_cards();
|
||||
store.on('change:cards', make_cards);
|
||||
store.watch((state, getters) => {
|
||||
return state.cards;
|
||||
}, make_cards);
|
||||
bind_add_card();
|
||||
bind_options();
|
||||
}
|
||||
|
|
@ -494,7 +511,7 @@ frappe.provide("frappe.views");
|
|||
self.$kanban_column = $(frappe.render_template(
|
||||
'kanban_column', {
|
||||
title: column.title,
|
||||
doctype: store.getState().doctype,
|
||||
doctype: store.state.doctype,
|
||||
indicator: frappe.scrub(column.indicator, '-')
|
||||
})).appendTo(wrapper);
|
||||
self.$kanban_cards = self.$kanban_column.find('.kanban-cards');
|
||||
|
|
@ -502,7 +519,7 @@ frappe.provide("frappe.views");
|
|||
|
||||
function make_cards() {
|
||||
self.$kanban_cards.empty();
|
||||
var cards = store.getState().cards;
|
||||
var cards = store.state.cards;
|
||||
filtered_cards = get_cards_for_column(cards, column);
|
||||
var filtered_cards_names = filtered_cards.map(card => card.name);
|
||||
|
||||
|
|
@ -548,7 +565,7 @@ frappe.provide("frappe.views");
|
|||
old_index: e.oldIndex,
|
||||
new_index: e.newIndex,
|
||||
};
|
||||
fluxify.doAction('update_order_for_single_card', args);
|
||||
store.dispatch('update_order_for_single_card', args);
|
||||
},
|
||||
onAdd: function() {
|
||||
},
|
||||
|
|
@ -579,10 +596,12 @@ frappe.provide("frappe.views");
|
|||
var card_title = $textarea.val();
|
||||
$new_card_area.hide();
|
||||
$textarea.val('');
|
||||
fluxify.doAction('add_card', card_title, column.title)
|
||||
.then(() => {
|
||||
$btn_add.show();
|
||||
});
|
||||
store.dispatch('add_card', {
|
||||
card_title,
|
||||
column_title: column.title
|
||||
}).then(() => {
|
||||
$btn_add.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -602,10 +621,10 @@ frappe.provide("frappe.views");
|
|||
var action = $btn.data().action;
|
||||
|
||||
if (action === "archive") {
|
||||
fluxify.doAction('archive_column', column);
|
||||
store.dispatch('archive_column', column);
|
||||
} else if (action === "indicator") {
|
||||
var color = $btn.data().indicator;
|
||||
fluxify.doAction('set_indicator', column, color);
|
||||
store.dispatch('set_indicator', {column, color});
|
||||
}
|
||||
});
|
||||
get_column_indicators(function(indicators) {
|
||||
|
|
@ -728,7 +747,7 @@ frappe.provide("frappe.views");
|
|||
callback: function() {
|
||||
const users = self.assign_to_dialog.get_values().assign_to;
|
||||
card.assigned_list = [...new Set(card.assigned_list.concat(users))];
|
||||
fluxify.doAction('update_card', card);
|
||||
store.dispatch('update_card', card);
|
||||
}
|
||||
});
|
||||
self.assign_to_dialog = self.assign_to.dialog;
|
||||
|
|
@ -850,21 +869,6 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
}
|
||||
|
||||
function is_filters_modified(board, cur_list) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var list_filters = JSON.stringify(cur_list.filter_area.get());
|
||||
resolve(list_filters !== board.filters);
|
||||
} catch(e) {
|
||||
// sometimes the filter_list is not initiated
|
||||
resolve(false);
|
||||
}
|
||||
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function is_active_column(col) {
|
||||
return col.status !== 'Archived';
|
||||
}
|
||||
|
|
@ -876,13 +880,13 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function get_card(name) {
|
||||
return store.getState().cards.find(function(c) {
|
||||
return store.state.cards.find(function(c) {
|
||||
return c.name === name;
|
||||
});
|
||||
}
|
||||
|
||||
function update_cards_column(updated_cards) {
|
||||
var cards = store.getState().cards;
|
||||
var cards = store.state.cards;
|
||||
cards.forEach(function(c) {
|
||||
updated_cards.forEach(function(uc) {
|
||||
if(uc.name === c.name) {
|
||||
|
|
@ -909,22 +913,4 @@ frappe.provide("frappe.views");
|
|||
callback(indicators);
|
||||
});
|
||||
}
|
||||
|
||||
function isBound(el, event, fn) {
|
||||
var events = $._data(el[0], 'events');
|
||||
if(!events) return false;
|
||||
var handlers = events[event];
|
||||
var flag = false;
|
||||
handlers.forEach(function(h) {
|
||||
if(h.handler.name === fn.name)
|
||||
flag = true;
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
|
||||
function remove_img_tags(html) {
|
||||
const $temp = $(`<div>${html}</div>`);
|
||||
$temp.find('img').remove();
|
||||
return $temp.html();
|
||||
}
|
||||
})();
|
||||
|
|
@ -209,10 +209,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
}
|
||||
|
||||
get required_libs() {
|
||||
return [
|
||||
'assets/frappe/js/lib/fluxify.min.js',
|
||||
'assets/frappe/js/frappe/views/kanban/kanban_board.js'
|
||||
];
|
||||
return 'kanban_board.bundle.js';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<title>{{ title }}</title>
|
||||
<link href="{{ base_url }}/assets/frappe/css/bootstrap.css" rel="stylesheet">
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="{{ base_url }}/assets/frappe/css/font-awesome.css">
|
||||
href="{{ base_url }}/assets/frappe/css/fonts/fontawesome/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ base_url }}/assets/frappe/css/tree.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ base_url }}{{ print_format_css_path }}">
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
update_url_with_filters() {
|
||||
window.history.replaceState(null, null, this.get_url_with_filters());
|
||||
if (frappe.get_route_str() == this.page_name) {
|
||||
window.history.replaceState(null, null, this.get_url_with_filters());
|
||||
}
|
||||
}
|
||||
|
||||
get_url_with_filters() {
|
||||
|
|
|
|||
6
frappe/public/js/lib/fluxify.min.js
vendored
6
frappe/public/js/lib/fluxify.min.js
vendored
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
fluxify v0.2.3
|
||||
https://github.com/arqex/fluxify
|
||||
GNU-2: https://github.com/arqex/fluxify/raw/master/LICENSE
|
||||
*/
|
||||
!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.fluxify=e()}(this,function(){"use strict";var t={_extend:function(t){for(var e,i,r=0;r<arguments.length;r++){e=arguments[r];for(i in e)t[i]=e[i]}return t}},e=function(){Object.defineProperty(this,"_events",{value:{}}),"function"==typeof this.initialize&&this.initialize.apply(this,arguments)},i={on:function(t,e,i){var r=this._events[t]||[];return r.push({callback:e,once:i}),this._events[t]=r,this},once:function(t,e){this.on(t,e,!0)},off:function(t,e){if("undefined"==typeof t)this._events={};else if("undefined"==typeof e)this._events[t]=[];else{var i,r=this._events[t]||[];for(i=r.length-1;i>=0;i--)r[i].callback===e&&r.splice(i,1)}return this},trigger:function(t){var e,i,r=[].slice.call(arguments,1),n=this._events[t]||[],s=[];for(e=0;e<n.length;e++)i=n[e],i.callback?i.callback.apply(null,r):i.once=!0,i.once&&s.push(e);for(e=s.length-1;e>=0;e--)n.splice(s[e],1);return this}};t._extend(i,{addListener:i.on,removeListener:i.off,removeAllListeners:i.off,emit:i.trigger}),e.prototype={};for(var r in i)Object.defineProperty(e.prototype,r,{value:i[r]});Object.defineProperty(e,"_extend",{value:function(e){var i,r=this;i=e&&e.hasOwnProperty(constructor)?e.constructor:function(){return r.apply(this,arguments)},t._extend(i,r);var n=function(){Object.defineProperty(this,"constructor",{value:i})};if(n.prototype=r.prototype,i.prototype=new n,e)for(var s in e)"constructor"!=s&&Object.defineProperty(i.prototype,s,{value:e[s]});return i.__super__=r.prototype,i}});var n=e._extend({initialize:function(t){if(!t)return this.props={};this.props={};for(var e in t)this.props[e]=t[e]},get:function(t){return this.props[t]},set:function(t,e){var i,r,n=t,s=[];"undefined"!=typeof e&&(n={},n[t]=e);for(r in n)this.props[r]!=n[r]&&(i=this.props[r],this.props[r]=n[r],s.push({prop:r,previousValue:i,value:n[r]}));s.length&&this.emit("change",s)}}),s=e._extend({initialize:function(t){var e,i,r=this,s=t||{},o=new n(s.initialState);t.id&&Object.defineProperty(this,"_id",{value:t.id}),Object.defineProperties(this,{_callbacks:{writable:!0,configurable:!0,value:{}},addActionCallbacks:{value:function(t){for(e in t)r._callbacks[e]=t[e].bind(this,o)}},callback:{value:function(){var t=arguments[0],e=[].slice.call(arguments,1);return this._callbacks[t]?this._callbacks[t].apply(this,e):!0}.bind(this)}}),this.addActionCallbacks(s.actionCallbacks||{});var a=function(t){Object.defineProperty(r,t,{enumerable:!0,configurable:!1,get:function(){return o.get(t)}})};if(s.initialState)for(i in s.initialState)a(i,s.initialState[i]);o.on("change",function(t){var e,i,n=t.length;for(i=0;n>i;i++)e=t[i],r.hasOwnProperty(e.prop)||a(e.prop,e.value),r.emit("change:"+e.prop,e.value,e.previousValue);r.emit("change",t)})},getState:function(){return t._extend({},this)},waitFor:function(t){return this._dispatcher.waitFor(t)}}),o=function(){this._callbacks={},this._dispatchQueue=[],this._currentDispatch=!1,this._ID=1,"undefined"!=typeof Promise&&(this._Promise=Promise)};o.prototype={register:function(t,e){var i=t;return"function"==typeof t&&(i="ID_"+this._ID,e=t),this._callbacks[i]=e,this._ID++,i},registerStore:function(t,e){return Object.defineProperty(e,"_dispatcher",{value:this}),this.register(t,e.callback)},unregister:function(t){delete this._callbacks[t]},waitFor:function(t){var e=[],i=0;for(Array.isArray(t)||(t=[t]);i<t.length;i++)this._promises[t[i]]&&e.push(this._promises[t[i]]);return e.length?this._Promise.all(e):this._Promise.resolve()},dispatch:function(){var t,e=this,i=arguments;if(!this._Promise)throw new TypeError("No promises.");return this._currentDispatch?(t=this._currentDispatch.then(function(){return e._dispatch.apply(e,i)}),this._dispatchQueue.push(t),this._currentDispatch=t):this._currentDispatch=this._dispatch.apply(e,i)},_dispatch:function(){var t=this,e=arguments,i=[];this._promises=[],Object.keys(this._callbacks).forEach(function(r){t._promises[r]=t._Promise.resolve().then(function(){return t._callbacks[r].apply(t,e)}).catch(function(t){console.error(t.stack||t)}),i.push(t._promises[r])});var r=function(){t._dispatchQueue.shift(),t._dispatchQueue.length||(t._currentDispatch=!1)};return this._Promise.all(i).then(r,r)},isDispatching:function(){return!!this._dispatchQueue.length}};var a=function(){Object.defineProperty(this,"dispatcher",{value:new o}),this.stores={},"undefined"!=typeof Promise&&this.promisify(Promise)};return a.prototype={createStore:function(t){var e=new s(t);return e._id&&(this.stores[e._id]=e,this.dispatcher.registerStore(e._id,e)),e},doAction:function(){return this.dispatcher.dispatch.apply(this.dispatcher,arguments)},promisify:function(t){this._Promise=t,this.dispatcher._Promise=t}},new a});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@import "frappe/public/css/font-awesome.css";
|
||||
@import "frappe/public/css/fonts/fontawesome/font-awesome.min.css";
|
||||
@import "frappe/public/css/octicons/octicons.css";
|
||||
@import "frappe/public/css/fonts/inter/inter.css";
|
||||
@import "~frappe-charts/dist/frappe-charts.min";
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@ def generate_csrf_token():
|
|||
|
||||
|
||||
class Session:
|
||||
__slots__ = ("user", "device", "user_type", "full_name", "data", "time_diff", "sid")
|
||||
|
||||
def __init__(self, user, resume=False, full_name=None, user_type=None):
|
||||
self.sid = cstr(
|
||||
frappe.form_dict.get("sid") or unquote(frappe.request.cookies.get("sid", "Guest"))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import unittest
|
|||
import frappe
|
||||
import frappe.utils
|
||||
from frappe.desk.query_report import build_xlsx_data
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
|
||||
|
||||
class TestQueryReport(unittest.TestCase):
|
||||
|
|
@ -39,3 +40,28 @@ class TestQueryReport(unittest.TestCase):
|
|||
|
||||
for row in xlsx_data:
|
||||
self.assertEqual(type(row), list)
|
||||
|
||||
def test_xlsx_export_with_composite_cell_value(self):
|
||||
"""Test excel export using rows with composite cell value"""
|
||||
|
||||
data = frappe._dict()
|
||||
data.columns = [
|
||||
{"label": "Column A", "fieldname": "column_a", "fieldtype": "Float"},
|
||||
{"label": "Column B", "fieldname": "column_b", "width": 150, "fieldtype": "Data"},
|
||||
]
|
||||
data.result = [
|
||||
[1.0, "Dummy 1"],
|
||||
{"column_a": 22.1, "column_b": ["Dummy 1", "Dummy 2"]}, # composite value in column_b
|
||||
]
|
||||
|
||||
# Define the visible rows
|
||||
visible_idx = [0, 1]
|
||||
|
||||
# Build the result
|
||||
xlsx_data, column_widths = build_xlsx_data(data, visible_idx, include_indentation=0)
|
||||
# Export to excel
|
||||
make_xlsx(xlsx_data, "Query Report", column_widths=column_widths)
|
||||
|
||||
for row in xlsx_data:
|
||||
# column_b should be 'str' even with composite cell value
|
||||
self.assertEqual(type(row[1]), str)
|
||||
|
|
|
|||
|
|
@ -1911,7 +1911,7 @@ def get_string_between(start: str, string: str, end: str) -> str:
|
|||
def to_markdown(html: str) -> str:
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from html2text import html2text
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
try:
|
||||
return html2text(html or "")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import json
|
|||
import mimetypes
|
||||
|
||||
import RestrictedPython.Guards
|
||||
from html2text import html2text
|
||||
from RestrictedPython import compile_restricted, safe_globals
|
||||
|
||||
import frappe
|
||||
|
|
@ -13,6 +12,7 @@ import frappe.integrations.utils
|
|||
import frappe.utils
|
||||
import frappe.utils.data
|
||||
from frappe import _
|
||||
from frappe.core.utils import html2text
|
||||
from frappe.frappeclient import FrappeClient
|
||||
from frappe.handler import execute_cmd
|
||||
from frappe.model.delete_doc import delete_doc
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def make_xlsx(data, sheet_name, wb=None, column_widths=None):
|
|||
|
||||
|
||||
def handle_html(data):
|
||||
from html2text import HTML2Text
|
||||
from frappe.core.utils import html2text
|
||||
|
||||
# return if no html tags found
|
||||
data = frappe.as_unicode(data)
|
||||
|
|
@ -62,12 +62,8 @@ def handle_html(data):
|
|||
|
||||
h = unescape_html(data or "")
|
||||
|
||||
obj = HTML2Text()
|
||||
obj.ignore_links = True
|
||||
obj.body_width = 0
|
||||
|
||||
try:
|
||||
value = obj.handle(h)
|
||||
value = html2text(h, strip_links=True, wrap=False)
|
||||
except Exception:
|
||||
# unable to parse html, send it raw
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class TestBlogPost(FrappeTestCase):
|
|||
blog_page_html = frappe.safe_decode(blog_page_response.get_data())
|
||||
|
||||
# On blog post page find link to the category page
|
||||
soup = BeautifulSoup(blog_page_html, "lxml")
|
||||
soup = BeautifulSoup(blog_page_html, "html.parser")
|
||||
category_page_link = list(soup.find_all("a", href=re.compile(blog.blog_category)))[0]
|
||||
category_page_url = category_page_link["href"]
|
||||
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ def add_preload_headers(response):
|
|||
try:
|
||||
preload = []
|
||||
strainer = SoupStrainer(re.compile("script|link"))
|
||||
soup = BeautifulSoup(response.data, "lxml", parse_only=strainer)
|
||||
soup = BeautifulSoup(response.data, "html.parser", parse_only=strainer)
|
||||
for elem in soup.find_all("script", src=re.compile(".*")):
|
||||
preload.append(("script", elem.get("src")))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from html2text import html2text
|
||||
from jinja2 import utils
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.core.utils import html2text
|
||||
from frappe.utils import sanitize_html
|
||||
from frappe.utils.global_search import web_search
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"js-sha256": "^0.9.0",
|
||||
"jsbarcode": "^3.9.0",
|
||||
"localforage": "^1.9.0",
|
||||
"moment": "^2.29.2",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.28",
|
||||
"plyr": "^3.7.2",
|
||||
"popper.js": "^1.16.0",
|
||||
|
|
@ -60,7 +60,8 @@
|
|||
"touch": "^3.1.0",
|
||||
"vue": "2.6.14",
|
||||
"vue-router": "^2.0.0",
|
||||
"vuedraggable": "^2.24.3"
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@frappe/esbuild-plugin-postcss2": "^0.1.3",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ dependencies = [
|
|||
"git-url-parse~=1.2.2",
|
||||
"gitdb~=4.0.7",
|
||||
"gunicorn~=20.1.0",
|
||||
"html2text==2020.1.16",
|
||||
"html5lib~=1.1",
|
||||
"ipython~=8.4.0",
|
||||
"ldap3~=2.9",
|
||||
|
|
@ -74,6 +73,7 @@ dependencies = [
|
|||
"urllib3~=1.26.4",
|
||||
"xlrd~=2.0.1",
|
||||
"zxcvbn-python~=4.4.24",
|
||||
"markdownify~=0.11.2",
|
||||
|
||||
# integration dependencies
|
||||
"boto3~=1.17.53",
|
||||
|
|
|
|||
13
yarn.lock
13
yarn.lock
|
|
@ -2005,10 +2005,10 @@ moment-timezone@^0.5.28:
|
|||
dependencies:
|
||||
moment ">= 2.9.0"
|
||||
|
||||
"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==
|
||||
"moment@>= 2.9.0", moment@^2.29.4:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
|
|
@ -3457,6 +3457,11 @@ vuedraggable@^2.24.3:
|
|||
dependencies:
|
||||
sortablejs "1.10.2"
|
||||
|
||||
vuex@3:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
|
||||
integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
|
||||
|
||||
which-boxed-primitive@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue