Merge branch 'frappe:develop' into use-other-wording-for-quick-entry
This commit is contained in:
commit
ecda12402c
176 changed files with 3519 additions and 2328 deletions
|
|
@ -35,7 +35,7 @@ repos:
|
|||
rev: v2.7.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript]
|
||||
types_or: [javascript, vue, scss]
|
||||
# Ignore any files that might contain jinja / bundles
|
||||
exclude: |
|
||||
(?x)^(
|
||||
|
|
@ -44,7 +44,8 @@ repos:
|
|||
.*boilerplate.*|
|
||||
frappe/www/website_script.js|
|
||||
frappe/templates/includes/.*|
|
||||
frappe/public/js/lib/.*
|
||||
frappe/public/js/lib/.*|
|
||||
frappe/website/doctype/website_theme/website_theme_template.scss
|
||||
)$
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ export default {
|
|||
fieldtype: "Data",
|
||||
label: "Data 3",
|
||||
},
|
||||
{
|
||||
fieldname: "gender",
|
||||
fieldtype: "Link",
|
||||
label: "Gender",
|
||||
options: "Gender",
|
||||
},
|
||||
{
|
||||
fieldname: "tab",
|
||||
fieldtype: "Tab Break",
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ context("Control Float", () => {
|
|||
cy.wait(200);
|
||||
cy.fill_field("float_number", d.input, "Float").blur();
|
||||
cy.get_field("float_number", "Float").should("have.value", d.blur_expected);
|
||||
|
||||
cy.wait(100);
|
||||
cy.get_field("float_number", "Float").focus();
|
||||
cy.wait(100);
|
||||
cy.get_field("float_number", "Float").blur();
|
||||
cy.wait(100);
|
||||
cy.get_field("float_number", "Float").focus();
|
||||
cy.wait(100);
|
||||
cy.get_field("float_number", "Float").should("have.value", d.focus_expected);
|
||||
});
|
||||
});
|
||||
|
|
@ -49,17 +52,17 @@ context("Control Float", () => {
|
|||
{
|
||||
input: "364.87,334",
|
||||
blur_expected: "36.487,334",
|
||||
focus_expected: "36487.334",
|
||||
focus_expected: "36.487,334",
|
||||
},
|
||||
{
|
||||
input: "36487,334",
|
||||
blur_expected: "36.487,334",
|
||||
focus_expected: "36487.334",
|
||||
input: "36487,335",
|
||||
blur_expected: "36.487,335",
|
||||
focus_expected: "36.487,335",
|
||||
},
|
||||
{
|
||||
input: "100",
|
||||
blur_expected: "100,000",
|
||||
focus_expected: "100",
|
||||
input: "2*(2+47)+1,5+1",
|
||||
blur_expected: "100,500",
|
||||
focus_expected: "100,500",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -67,19 +70,19 @@ context("Control Float", () => {
|
|||
number_format: "#,###.##",
|
||||
values: [
|
||||
{
|
||||
input: "364,87.334",
|
||||
blur_expected: "36,487.334",
|
||||
focus_expected: "36487.334",
|
||||
input: "464,87.334",
|
||||
blur_expected: "46,487.334",
|
||||
focus_expected: "46,487.334",
|
||||
},
|
||||
{
|
||||
input: "36487.334",
|
||||
blur_expected: "36,487.334",
|
||||
focus_expected: "36487.334",
|
||||
input: "46487.335",
|
||||
blur_expected: "46,487.335",
|
||||
focus_expected: "46,487.335",
|
||||
},
|
||||
{
|
||||
input: "100",
|
||||
blur_expected: "100.000",
|
||||
focus_expected: "100",
|
||||
input: "3*(2+47)+1.5+1",
|
||||
blur_expected: "149.500",
|
||||
focus_expected: "149.500",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -90,13 +93,13 @@ context("Control Float", () => {
|
|||
{
|
||||
input: "12.345",
|
||||
blur_expected: "12.345,000",
|
||||
focus_expected: "12345",
|
||||
focus_expected: "12.345,000",
|
||||
},
|
||||
{
|
||||
// parseFloat would reduce 12,340 to 12,34 if this string was ever to be parsed
|
||||
input: "12.340",
|
||||
blur_expected: "12.340,000",
|
||||
focus_expected: "12340",
|
||||
focus_expected: "12.340,000",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ context("Customize Form", () => {
|
|||
});
|
||||
it("Changing to naming rule should update autoname", () => {
|
||||
cy.fill_field("doc_type", "ToDo", "Link").blur();
|
||||
cy.wait(2000);
|
||||
cy.findByRole("tab", { name: "Details" }).click();
|
||||
cy.click_form_section("Naming");
|
||||
const naming_rule_default_autoname_map = {
|
||||
"Set by user": "prompt",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,40 @@ context("Form Builder", () => {
|
|||
cy.get(".title-area .indicator-pill.orange").should("have.text", "Not Saved");
|
||||
});
|
||||
|
||||
it("Check if Filters are applied to the link field", () => {
|
||||
// Visit the Form Builder
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
||||
cy.get("[data-fieldname='gender']").click();
|
||||
|
||||
// click on filter action button
|
||||
cy.get('[data-fieldname="gender"] .field-actions button:first').click();
|
||||
|
||||
// add filter
|
||||
cy.get(".modal-body .clear-filters").click();
|
||||
cy.get(".modal-body .filter-action-buttons .add-filter").click();
|
||||
cy.wait(100);
|
||||
cy.get(".modal-body .filter-box .list_filter .filter-field .link-field input").type(
|
||||
"Male"
|
||||
);
|
||||
cy.get(".btn-modal-primary").click();
|
||||
|
||||
// Save the document
|
||||
cy.click_doc_primary_button("Save");
|
||||
|
||||
// Open a new Form
|
||||
cy.new_form(doctype_name);
|
||||
// Click on the "salutation" field
|
||||
cy.get_field("gender").clear().click();
|
||||
|
||||
cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
|
||||
cy.wait("@search_link").then((data) => {
|
||||
expect(data.response.body.message.length).to.eq(1);
|
||||
expect(data.response.body.message[0].value).to.eq("Male");
|
||||
});
|
||||
});
|
||||
|
||||
it("Add empty section and save", () => {
|
||||
cy.visit(`/app/doctype/${doctype_name}`);
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
|
|
@ -43,7 +77,8 @@ context("Form Builder", () => {
|
|||
|
||||
// add new section
|
||||
cy.get(first_section).click(15, 10);
|
||||
cy.get(first_section).find(".section-actions button:first").click();
|
||||
cy.get(first_section).find(".dropdown-btn:first").click();
|
||||
cy.get(".dropdown-options:visible .dropdown-item:first").click();
|
||||
|
||||
// save
|
||||
cy.click_doc_primary_button("Save");
|
||||
|
|
@ -184,12 +219,14 @@ context("Form Builder", () => {
|
|||
|
||||
// add new section
|
||||
cy.get(first_section).click(15, 10);
|
||||
cy.get(first_section).find(".section-actions button:first").click();
|
||||
cy.get(first_section).find(".dropdown-btn:first").click();
|
||||
cy.get(".dropdown-options:visible .dropdown-item:first").click();
|
||||
cy.get(".tab-content.active .form-section-container").should("have.length", 2);
|
||||
|
||||
// add new column
|
||||
cy.get(first_section).find(".column:first").click(15, 10);
|
||||
cy.get(first_section).find(".column:first .column-actions button:first").click();
|
||||
cy.get(first_section).click(15, 10);
|
||||
cy.get(first_section).find(".dropdown-btn:first").click();
|
||||
cy.get(".dropdown-options:visible .dropdown-item:last").click();
|
||||
cy.get(first_section).find(".column").should("have.length", 2);
|
||||
});
|
||||
|
||||
|
|
@ -197,13 +234,15 @@ context("Form Builder", () => {
|
|||
let first_section = ".tab-content.active .form-section-container:first";
|
||||
|
||||
// remove column
|
||||
cy.get(first_section).find(".column:first").click(15, 10);
|
||||
cy.get(first_section).find(".column:first .column-actions button:last").click();
|
||||
cy.get(first_section).click(15, 10);
|
||||
cy.get(first_section).find(".dropdown-btn:first").click();
|
||||
cy.get(".dropdown-options:visible .dropdown-item:last").click();
|
||||
cy.get(first_section).find(".column").should("have.length", 1);
|
||||
|
||||
// remove section
|
||||
cy.get(first_section).click(15, 10);
|
||||
cy.get(first_section).find(".section-actions button:last").click();
|
||||
cy.get(first_section).find(".dropdown-btn:first").click();
|
||||
cy.get(".dropdown-options:visible .dropdown-item").eq(1).click();
|
||||
cy.get(".tab-content.active .form-section-container").should("have.length", 1);
|
||||
|
||||
// remove tab
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ context("Grid Configuration", () => {
|
|||
cy.visit("/app/doctype/User");
|
||||
});
|
||||
it("Set user wise grid settings", () => {
|
||||
cy.findByRole("tab", { name: "Form" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get('.form-section[data-fieldname="fields_section"]').click();
|
||||
cy.wait(100);
|
||||
cy.get('.frappe-control[data-fieldname="fields"]').as("table");
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ context("Web Form", () => {
|
|||
|
||||
cy.findByRole("tab", { name: "Customization" }).click();
|
||||
cy.fill_field("breadcrumbs", '[{"label": _("Notes"), "route":"note"}]', "Code");
|
||||
cy.wait(2000);
|
||||
cy.get(".form-tabs .nav-item .nav-link").contains("Customization").click();
|
||||
cy.save();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ context("Workspace 2.0", () => {
|
|||
cy.get(".codex-editor__redactor .ce-block");
|
||||
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
|
||||
cy.fill_field("title", "Test Private Page", "Data");
|
||||
cy.fill_field("icon", "edit", "Icon");
|
||||
cy.get_open_dialog().find(".modal-header").click();
|
||||
cy.get_open_dialog().find(".btn-primary").click();
|
||||
|
||||
|
|
@ -52,7 +51,6 @@ context("Workspace 2.0", () => {
|
|||
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
|
||||
cy.fill_field("title", "Test Child Page", "Data");
|
||||
cy.fill_field("parent", "Test Private Page", "Select");
|
||||
cy.fill_field("icon", "edit", "Icon");
|
||||
cy.get_open_dialog().find(".modal-header").click();
|
||||
cy.get_open_dialog().find(".btn-primary").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ context("Workspace Blocks", () => {
|
|||
cy.get(".codex-editor__redactor .ce-block");
|
||||
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
|
||||
cy.fill_field("title", "Test Block Page", "Data");
|
||||
cy.fill_field("icon", "edit", "Icon");
|
||||
cy.get_open_dialog().find(".modal-header").click();
|
||||
cy.get_open_dialog().find(".btn-primary").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -37,22 +37,16 @@ Cypress.Commands.add("login", (email, password) => {
|
|||
// cy.session clears all localStorage on new login, so we need to retain the last route
|
||||
const session_last_route = window.localStorage.getItem("session_last_route");
|
||||
return cy
|
||||
.session(
|
||||
[email, password] || "",
|
||||
() => {
|
||||
return cy.request({
|
||||
url: "/api/method/login",
|
||||
method: "POST",
|
||||
body: {
|
||||
usr: email,
|
||||
pwd: password,
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
cacheAcrossSpecs: true,
|
||||
}
|
||||
)
|
||||
.session([email, password] || "", () => {
|
||||
return cy.request({
|
||||
url: "/api/method/login",
|
||||
method: "POST",
|
||||
body: {
|
||||
usr: email,
|
||||
pwd: password,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (session_last_route) {
|
||||
window.localStorage.setItem("session_last_route", session_last_route);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ from frappe.exceptions import SiteNotSpecifiedError
|
|||
@click.option(
|
||||
"--set-default", is_flag=True, default=False, help="Set the new site as default site"
|
||||
)
|
||||
@click.option(
|
||||
"--setup-db/--no-setup-db",
|
||||
default=True,
|
||||
help="Create user and database in mariadb/postgres; only bootstrap if false",
|
||||
)
|
||||
def new_site(
|
||||
site,
|
||||
db_root_username=None,
|
||||
|
|
@ -64,6 +69,7 @@ def new_site(
|
|||
db_host=None,
|
||||
db_port=None,
|
||||
set_default=False,
|
||||
setup_db=True,
|
||||
):
|
||||
"Create a new site"
|
||||
from frappe.installer import _new_site, extract_sql_from_archive
|
||||
|
|
@ -88,6 +94,7 @@ def new_site(
|
|||
db_type=db_type,
|
||||
db_host=db_host,
|
||||
db_port=db_port,
|
||||
setup_db=setup_db,
|
||||
)
|
||||
|
||||
if set_default:
|
||||
|
|
|
|||
|
|
@ -162,8 +162,6 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
self.seen = 1
|
||||
self.sent_or_received = "Sent"
|
||||
|
||||
self.set_status()
|
||||
|
||||
validate_email(self)
|
||||
|
||||
if self.communication_medium == "Email":
|
||||
|
|
@ -173,6 +171,10 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
|
||||
self.set_sender_full_name()
|
||||
|
||||
if self.is_new():
|
||||
self.set_status()
|
||||
self.mark_email_as_spam()
|
||||
|
||||
def validate_reference(self):
|
||||
if self.reference_doctype and self.reference_name:
|
||||
if not self.reference_owner:
|
||||
|
|
@ -333,9 +335,6 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
)
|
||||
|
||||
def set_status(self):
|
||||
if not self.is_new():
|
||||
return
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
self.status = "Linked"
|
||||
elif self.communication_type == "Communication":
|
||||
|
|
@ -343,15 +342,13 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
else:
|
||||
self.status = "Closed"
|
||||
|
||||
# set email status to spam
|
||||
email_rule = frappe.db.get_value("Email Rule", {"email_id": self.sender, "is_spam": 1})
|
||||
def mark_email_as_spam(self):
|
||||
if (
|
||||
self.communication_type == "Communication"
|
||||
and self.communication_medium == "Email"
|
||||
and self.sent_or_received == "Sent"
|
||||
and email_rule
|
||||
and self.sent_or_received == "Received"
|
||||
and frappe.db.exists("Email Rule", {"email_id": self.sender, "is_spam": 1})
|
||||
):
|
||||
|
||||
self.email_status = "Spam"
|
||||
|
||||
@classmethod
|
||||
|
|
@ -433,7 +430,18 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
frappe.db.commit()
|
||||
|
||||
def parse_email_for_timeline_links(self):
|
||||
parse_email(self, [self.recipients, self.cc, self.bcc])
|
||||
if not frappe.db.get_value("Email Account", self.email_account, "enable_automatic_linking"):
|
||||
return
|
||||
|
||||
for doctype, docname in parse_email([self.recipients, self.cc, self.bcc]):
|
||||
if not frappe.db.get_value(doctype, docname, ignore=True):
|
||||
continue
|
||||
|
||||
self.add_link(doctype, docname)
|
||||
|
||||
if not self.reference_doctype:
|
||||
self.reference_doctype = doctype
|
||||
self.reference_name = docname
|
||||
|
||||
# Timeline Links
|
||||
def set_timeline_links(self):
|
||||
|
|
@ -452,20 +460,13 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
add_contact_links_to_communication(self, contact_name)
|
||||
|
||||
def deduplicate_timeline_links(self):
|
||||
if self.timeline_links:
|
||||
links, duplicate = [], False
|
||||
if not self.timeline_links:
|
||||
return
|
||||
|
||||
for l in self.timeline_links:
|
||||
t = (l.link_doctype, l.link_name)
|
||||
if not t in links:
|
||||
links.append(t)
|
||||
else:
|
||||
duplicate = True
|
||||
|
||||
if duplicate:
|
||||
self.timeline_links.clear()
|
||||
for l in links:
|
||||
self.add_link(link_doctype=l[0], link_name=l[1])
|
||||
unique_links = {(link.link_doctype, link.link_name) for link in self.timeline_links}
|
||||
self.timeline_links = []
|
||||
for doctype, name in unique_links:
|
||||
self.add_link(doctype, name)
|
||||
|
||||
def add_link(self, link_doctype, link_name, autosave=False):
|
||||
self.append("timeline_links", {"link_doctype": link_doctype, "link_name": link_name})
|
||||
|
|
@ -574,36 +575,35 @@ def add_contact_links_to_communication(communication, contact_name):
|
|||
communication.add_link(contact_link.link_doctype, contact_link.link_name)
|
||||
|
||||
|
||||
def parse_email(communication, email_strings):
|
||||
def parse_email(email_strings):
|
||||
"""
|
||||
Parse email to add timeline links.
|
||||
When automatic email linking is enabled, an email from email_strings can contain
|
||||
a doctype and docname ie in the format `admin+doctype+docname@example.com` or `admin+doctype=docname@example.com`,
|
||||
the email is parsed and doctype and docname is extracted and timeline link is added.
|
||||
the email is parsed and doctype and docname is extracted.
|
||||
"""
|
||||
if not frappe.db.get_value("Email Account", filters={"enable_automatic_linking": 1}):
|
||||
return
|
||||
|
||||
for email_string in email_strings:
|
||||
if email_string:
|
||||
for email in email_string.split(","):
|
||||
email_username = email.split("@", 1)[0]
|
||||
email_local_parts = email_username.split("+")
|
||||
docname = doctype = None
|
||||
if len(email_local_parts) == 3:
|
||||
doctype = unquote(email_local_parts[1])
|
||||
docname = unquote(email_local_parts[2])
|
||||
if not email_string:
|
||||
continue
|
||||
|
||||
elif len(email_local_parts) == 2:
|
||||
document_parts = email_local_parts[1].split("=", 1)
|
||||
if len(document_parts) != 2:
|
||||
continue
|
||||
for email in email_string.split(","):
|
||||
email_username = email.split("@", 1)[0]
|
||||
email_local_parts = email_username.split("+")
|
||||
docname = doctype = None
|
||||
if len(email_local_parts) == 3:
|
||||
doctype = unquote(email_local_parts[1])
|
||||
docname = unquote(email_local_parts[2])
|
||||
|
||||
doctype = unquote(document_parts[0])
|
||||
docname = unquote(document_parts[1])
|
||||
elif len(email_local_parts) == 2:
|
||||
document_parts = email_local_parts[1].split("=", 1)
|
||||
if len(document_parts) != 2:
|
||||
continue
|
||||
|
||||
if doctype and docname and frappe.db.get_value(doctype, docname, ignore=True):
|
||||
communication.add_link(doctype, docname)
|
||||
doctype = unquote(document_parts[0])
|
||||
docname = unquote(document_parts[1])
|
||||
|
||||
if doctype and docname:
|
||||
yield doctype, docname
|
||||
|
||||
|
||||
def get_email_without_link(email):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import quote
|
||||
|
||||
import frappe
|
||||
from frappe.core.doctype.communication.communication import Communication, get_emails
|
||||
from frappe.core.doctype.communication.communication import Communication, get_emails, parse_email
|
||||
from frappe.core.doctype.communication.email import add_attachments
|
||||
from frappe.email.doctype.email_queue.email_queue import EmailQueue
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
|
@ -219,36 +218,25 @@ class TestCommunication(FrappeTestCase):
|
|||
self.assertIn(comm_note_1.name, data)
|
||||
self.assertIn(comm_note_2.name, data)
|
||||
|
||||
def test_link_in_email(self):
|
||||
create_email_account()
|
||||
def test_parse_email(self):
|
||||
to = "Jon Doe <jon.doe@example.org>"
|
||||
cc = """=?UTF-8?Q?Max_Mu=C3=9F?= <max.muss@examle.org>,
|
||||
erp+Customer+that%20company@example.org"""
|
||||
bcc = ""
|
||||
|
||||
notes = {}
|
||||
for i in range(2):
|
||||
frappe.delete_doc_if_exists("Note", f"test document link in email {i}")
|
||||
notes[i] = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Note",
|
||||
"title": f"test document link in email {i}",
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
results = list(parse_email([to, cc, bcc]))
|
||||
self.assertEqual([("Customer", "that company")], results)
|
||||
|
||||
comm = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Communication",
|
||||
"communication_medium": "Email",
|
||||
"subject": "Document Link in Email",
|
||||
"sender": "comm_sender@example.com",
|
||||
"recipients": f'comm_recipient+{quote("Note")}+{quote(notes[0].name)}@example.com,comm_recipient+{quote("Note")}={quote(notes[1].name)}@example.com',
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
results = list(parse_email([to, bcc]))
|
||||
self.assertEqual(results, [])
|
||||
|
||||
doc_links = [
|
||||
(timeline_link.link_doctype, timeline_link.link_name) for timeline_link in comm.timeline_links
|
||||
]
|
||||
self.assertIn(("Note", notes[0].name), doc_links)
|
||||
self.assertIn(("Note", notes[1].name), doc_links)
|
||||
to = "jane.doe+A+Test@example.org"
|
||||
cc = ""
|
||||
bcc = "=?UTF-8?Q?Max_Mu=C3=9F?= <max.muss+Note=Very%20important@examle.org>"
|
||||
results = list(parse_email([to, cc, bcc]))
|
||||
self.assertEqual([("A", "Test"), ("Note", "Very important")], results)
|
||||
|
||||
def test_parse_emails(self):
|
||||
def test_get_emails(self):
|
||||
emails = get_emails(
|
||||
[
|
||||
"comm_recipient+DocType+DocName@example.com",
|
||||
|
|
@ -293,6 +281,40 @@ class TestCommunication(FrappeTestCase):
|
|||
self.assertEqual(comm_with_signature.content.count(signature), 1)
|
||||
self.assertEqual(comm_without_signature.content.count(signature), 1)
|
||||
|
||||
def test_mark_as_spam(self):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Email Rule",
|
||||
"email_id": "spammer@example.com",
|
||||
"is_spam": 1,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
spam_comm: Communication = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Communication",
|
||||
"communication_medium": "Email",
|
||||
"subject": "This is spam",
|
||||
"sender": "spammer@example.com",
|
||||
"recipients": "comm_recipient@example.com",
|
||||
"sent_or_received": "Received",
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(spam_comm.email_status, "Spam")
|
||||
|
||||
normal_comm: Communication = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Communication",
|
||||
"communication_medium": "Email",
|
||||
"subject": "This is spam",
|
||||
"sender": "friendlyhuman@example.com",
|
||||
"recipients": "comm_recipient@example.com",
|
||||
"sent_or_received": "Received",
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
self.assertNotEqual(normal_comm.email_status, "Spam")
|
||||
|
||||
|
||||
class TestCommunicationEmailMixin(FrappeTestCase):
|
||||
def new_communication(self, recipients=None, cc=None, bcc=None) -> Communication:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"options",
|
||||
"sort_options",
|
||||
"show_dashboard",
|
||||
"link_filters",
|
||||
"defaults_section",
|
||||
"default",
|
||||
"column_break_6",
|
||||
|
|
@ -560,13 +561,18 @@
|
|||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_filters",
|
||||
"fieldtype": "JSON",
|
||||
"label": "Link Filters"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-25 06:53:45.194081",
|
||||
"modified": "2023-11-13 11:48:51.502812",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class DocField(Document):
|
|||
is_virtual: DF.Check
|
||||
label: DF.Data | None
|
||||
length: DF.Int
|
||||
link_filters: DF.JSON | None
|
||||
mandatory_depends_on: DF.Code | None
|
||||
max_height: DF.Data | None
|
||||
no_copy: DF.Check
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ class DocType(Document):
|
|||
is_virtual: DF.Check
|
||||
issingle: DF.Check
|
||||
istable: DF.Check
|
||||
link_filters: DF.JSON
|
||||
links: DF.Table[DocTypeLink]
|
||||
make_attachments_public: DF.Check
|
||||
max_attachments: DF.Int
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ frappe.listview_settings["DocType"] = {
|
|||
role: "System Manager",
|
||||
share: 1,
|
||||
write: 1,
|
||||
submit: values.is_submittable ? 1 : 0,
|
||||
},
|
||||
],
|
||||
fields: [{ fieldtype: "Section Break" }],
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ def relink_files(doc, fieldname, temp_doc_name):
|
|||
"attached_to_field": fieldname,
|
||||
"creation": (
|
||||
"between",
|
||||
[now_datetime() - add_to_date(date=now_datetime(), minutes=-60), now_datetime()],
|
||||
[add_to_date(date=now_datetime(), minutes=-60), now_datetime()],
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@ class RQJob(Document):
|
|||
matched_job_ids = RQJob.get_matching_job_ids(args)[start : start + page_length]
|
||||
|
||||
conn = get_redis_conn()
|
||||
jobs = [serialize_job(job) for job in Job.fetch_many(job_ids=matched_job_ids, connection=conn)]
|
||||
jobs = [
|
||||
serialize_job(job) for job in Job.fetch_many(job_ids=matched_job_ids, connection=conn) if job
|
||||
]
|
||||
|
||||
return sorted(jobs, key=lambda j: j.modified, reverse=order_desc)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"fieldname",
|
||||
"insert_after",
|
||||
"length",
|
||||
"link_filters",
|
||||
"column_break_6",
|
||||
"fieldtype",
|
||||
"precision",
|
||||
|
|
@ -444,6 +445,12 @@
|
|||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_filters",
|
||||
"fieldtype": "JSON",
|
||||
"hidden": 1,
|
||||
"label": "Link Filters"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class CustomField(Document):
|
|||
is_virtual: DF.Check
|
||||
label: DF.Data | None
|
||||
length: DF.Int
|
||||
link_filters: DF.JSON | None
|
||||
mandatory_depends_on: DF.Code | None
|
||||
module: DF.Link | None
|
||||
no_copy: DF.Check
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ frappe.ui.form.on("Customize Form", {
|
|||
);
|
||||
|
||||
render_form_builder(frm);
|
||||
frm.get_field("form_builder").tab.set_active();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"details_tab",
|
||||
"doc_type",
|
||||
"properties",
|
||||
"label",
|
||||
"search_fields",
|
||||
"link_filters",
|
||||
"column_break_5",
|
||||
"istable",
|
||||
"is_calendar_and_gantt",
|
||||
|
|
@ -24,17 +26,6 @@
|
|||
"naming_section",
|
||||
"naming_rule",
|
||||
"autoname",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"form_tab",
|
||||
"form_builder",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"settings_tab",
|
||||
"form_settings_section",
|
||||
"image_field",
|
||||
"max_attachments",
|
||||
|
|
@ -59,7 +50,17 @@
|
|||
"section_break_8",
|
||||
"sort_field",
|
||||
"column_break_10",
|
||||
"sort_order"
|
||||
"sort_order",
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"document_states_section",
|
||||
"states",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"form_tab",
|
||||
"form_builder"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -180,7 +181,6 @@
|
|||
"depends_on": "doc_type",
|
||||
"fieldname": "fields_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1,
|
||||
"label": "Fields"
|
||||
},
|
||||
{
|
||||
|
|
@ -372,11 +372,6 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Is Calendar and Gantt"
|
||||
},
|
||||
{
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_builder",
|
||||
"fieldtype": "HTML",
|
||||
|
|
@ -386,6 +381,17 @@
|
|||
"fieldname": "form_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_filters",
|
||||
"fieldtype": "JSON",
|
||||
"hidden": 1,
|
||||
"label": "Link Filters"
|
||||
},
|
||||
{
|
||||
"fieldname": "details_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Details"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
|
|
@ -394,7 +400,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-31 02:04:25.955931",
|
||||
"modified": "2023-11-16 11:23:06.427432",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class CustomizeForm(Document):
|
|||
is_calendar_and_gantt: DF.Check
|
||||
istable: DF.Check
|
||||
label: DF.Data | None
|
||||
link_filters: DF.JSON | None
|
||||
links: DF.Table[DocTypeLink]
|
||||
make_attachments_public: DF.Check
|
||||
max_attachments: DF.Int
|
||||
|
|
@ -681,6 +682,17 @@ def is_standard_or_system_generated_field(df):
|
|||
return not df.get("is_custom_field") or df.get("is_system_generated")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_link_filters_from_doc_without_customisations(doctype, fieldname):
|
||||
"""Get the filters of a link field from a doc without customisations
|
||||
In backend the customisations are not applied.
|
||||
Customisations are applied in the client side.
|
||||
"""
|
||||
doc = frappe.get_doc("DocType", doctype)
|
||||
field = list(filter(lambda x: x.fieldname == fieldname, doc.fields))
|
||||
return field[0].link_filters
|
||||
|
||||
|
||||
doctype_properties = {
|
||||
"search_fields": "Data",
|
||||
"title_field": "Data",
|
||||
|
|
@ -761,6 +773,7 @@ docfield_properties = {
|
|||
"hide_days": "Check",
|
||||
"hide_seconds": "Check",
|
||||
"is_virtual": "Check",
|
||||
"link_filters": "JSON",
|
||||
}
|
||||
|
||||
doctype_link_properties = {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"no_copy",
|
||||
"allow_in_quick_entry",
|
||||
"translatable",
|
||||
"link_filters",
|
||||
"column_break_7",
|
||||
"default",
|
||||
"precision",
|
||||
|
|
@ -471,13 +472,18 @@
|
|||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_filters",
|
||||
"fieldtype": "JSON",
|
||||
"label": "Link Filters"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-25 06:55:50.718441",
|
||||
"modified": "2023-11-07 13:17:21.373626",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ class CustomizeFormField(Document):
|
|||
is_virtual: DF.Check
|
||||
label: DF.Data | None
|
||||
length: DF.Int
|
||||
link_filters: DF.JSON | None
|
||||
mandatory_depends_on: DF.Code | None
|
||||
no_copy: DF.Check
|
||||
non_negative: DF.Check
|
||||
|
|
|
|||
|
|
@ -7,21 +7,34 @@
|
|||
from frappe.database.database import savepoint
|
||||
|
||||
|
||||
def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False):
|
||||
def setup_database(force, verbose=None, no_mariadb_socket=False):
|
||||
import frappe
|
||||
|
||||
if frappe.conf.db_type == "postgres":
|
||||
import frappe.database.postgres.setup_db
|
||||
|
||||
return frappe.database.postgres.setup_db.setup_database(force, source_sql, verbose)
|
||||
return frappe.database.postgres.setup_db.setup_database()
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
|
||||
return frappe.database.mariadb.setup_db.setup_database(
|
||||
force, source_sql, verbose, no_mariadb_socket=no_mariadb_socket
|
||||
force, verbose, no_mariadb_socket=no_mariadb_socket
|
||||
)
|
||||
|
||||
|
||||
def bootstrap_database(db_name, verbose=None, source_sql=None):
|
||||
import frappe
|
||||
|
||||
if frappe.conf.db_type == "postgres":
|
||||
import frappe.database.postgres.setup_db
|
||||
|
||||
return frappe.database.postgres.setup_db.bootstrap_database(db_name, verbose, source_sql)
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
|
||||
return frappe.database.mariadb.setup_db.bootstrap_database(db_name, verbose, source_sql)
|
||||
|
||||
|
||||
def drop_user_and_database(db_name, root_login=None, root_password=None):
|
||||
import frappe
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ def get_mariadb_version(version_string: str = ""):
|
|||
return version.rsplit(".", 1)
|
||||
|
||||
|
||||
def setup_database(force, source_sql, verbose, no_mariadb_socket=False):
|
||||
def setup_database(force, verbose, no_mariadb_socket=False):
|
||||
frappe.local.session = frappe._dict({"user": "Administrator"})
|
||||
|
||||
db_name = frappe.local.conf.db_name
|
||||
|
|
@ -55,8 +55,6 @@ def setup_database(force, source_sql, verbose, no_mariadb_socket=False):
|
|||
# close root connection
|
||||
root_conn.close()
|
||||
|
||||
bootstrap_database(db_name, verbose, source_sql)
|
||||
|
||||
|
||||
def drop_user_and_database(db_name, root_login, root_password):
|
||||
frappe.local.db = get_root_connection(root_login, root_password)
|
||||
|
|
@ -75,8 +73,8 @@ def bootstrap_database(db_name, verbose, source_sql=None):
|
|||
sys.exit(1)
|
||||
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
|
||||
frappe.connect(db_name=db_name)
|
||||
|
||||
if "tabDefaultValue" not in frappe.db.get_tables(cached=False):
|
||||
from click import secho
|
||||
|
||||
|
|
|
|||
|
|
@ -160,11 +160,16 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
|||
return LazyDecode(self._cursor.query)
|
||||
|
||||
def get_connection(self):
|
||||
conn = psycopg2.connect(
|
||||
"host='{}' dbname='{}' user='{}' password='{}' port={}".format(
|
||||
self.host, self.user, self.user, self.password, self.port
|
||||
)
|
||||
)
|
||||
conn_settings = {
|
||||
"user": self.user,
|
||||
"dbname": self.user,
|
||||
"host": self.host,
|
||||
"password": self.password,
|
||||
}
|
||||
if self.port:
|
||||
conn_settings["port"] = self.port
|
||||
|
||||
conn = psycopg2.connect(**conn_settings)
|
||||
conn.set_isolation_level(ISOLATION_LEVEL_REPEATABLE_READ)
|
||||
|
||||
return conn
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import os
|
|||
import frappe
|
||||
|
||||
|
||||
def setup_database(force, source_sql=None, verbose=False):
|
||||
def setup_database():
|
||||
root_conn = get_root_connection(frappe.flags.root_login, frappe.flags.root_password)
|
||||
root_conn.commit()
|
||||
root_conn.sql("end")
|
||||
|
|
@ -14,9 +14,6 @@ def setup_database(force, source_sql=None, verbose=False):
|
|||
root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(frappe.conf.db_name))
|
||||
root_conn.close()
|
||||
|
||||
bootstrap_database(frappe.conf.db_name, verbose, source_sql=source_sql)
|
||||
frappe.connect()
|
||||
|
||||
|
||||
def bootstrap_database(db_name, verbose, source_sql=None):
|
||||
frappe.connect(db_name=db_name)
|
||||
|
|
|
|||
|
|
@ -215,12 +215,12 @@ def clean_params(data):
|
|||
|
||||
|
||||
def parse_json(data):
|
||||
if isinstance(data.get("filters"), str):
|
||||
data["filters"] = json.loads(data["filters"])
|
||||
if isinstance(data.get("or_filters"), str):
|
||||
data["or_filters"] = json.loads(data["or_filters"])
|
||||
if isinstance(data.get("fields"), str):
|
||||
data["fields"] = ["*"] if data["fields"] == "*" else json.loads(data["fields"])
|
||||
if (filters := data.get("filters")) and isinstance(filters, str):
|
||||
data["filters"] = json.loads(filters)
|
||||
if (or_filters := data.get("or_filters")) and isinstance(or_filters, str):
|
||||
data["or_filters"] = json.loads(or_filters)
|
||||
if (fields := data.get("fields")) and isinstance(fields, str):
|
||||
data["fields"] = ["*"] if fields == "*" else json.loads(fields)
|
||||
if isinstance(data.get("docstatus"), str):
|
||||
data["docstatus"] = json.loads(data["docstatus"])
|
||||
if isinstance(data.get("save_user_settings"), str):
|
||||
|
|
|
|||
|
|
@ -123,6 +123,5 @@ def send_welcome_email(welcome_email, email, email_group):
|
|||
return
|
||||
|
||||
args = dict(email=email, email_group=email_group)
|
||||
email_message = welcome_email.response or welcome_email.response_html
|
||||
message = frappe.render_template(email_message, args)
|
||||
message = frappe.render_template(welcome_email.response_, args)
|
||||
frappe.sendmail(email, subject=welcome_email.subject, message=message)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ frappe.listview_settings["Newsletter"] = {
|
|||
add_fields: ["subject", "email_sent", "schedule_sending"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.email_sent) {
|
||||
return [__("Sent"), "green", "email_sent,=,Yes"];
|
||||
return [__("Sent"), "green", "email_sent,=,1"];
|
||||
} else if (doc.schedule_sending) {
|
||||
return [__("Scheduled"), "purple", "email_sent,=,No|schedule_sending,=,Yes"];
|
||||
return [__("Scheduled"), "purple", "email_sent,=,0|schedule_sending,=,1"];
|
||||
} else {
|
||||
return [__("Not Sent"), "gray", "email_sent,=,No"];
|
||||
return [__("Not Sent"), "gray", "email_sent,=,0"];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@ class EmailServer:
|
|||
).where(Communication.email_account == self.settings.email_account).run()
|
||||
|
||||
if self.settings.use_imap:
|
||||
# Remove {"} quotes that are added to handle spaces in IMAP Folder names
|
||||
if folder[0] == folder[-1] == '"':
|
||||
folder = folder[1:-2]
|
||||
# new update for the IMAP Folder DocType
|
||||
IMAPFolder = frappe.qb.DocType("IMAP Folder")
|
||||
frappe.qb.update(IMAPFolder).set(IMAPFolder.uidvalidity, current_uid_validity).set(
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ def _new_site(
|
|||
db_type=None,
|
||||
db_host=None,
|
||||
db_port=None,
|
||||
setup_db=True,
|
||||
):
|
||||
"""Install a new Frappe site"""
|
||||
|
||||
|
|
@ -91,6 +92,7 @@ def _new_site(
|
|||
db_host=db_host,
|
||||
db_port=db_port,
|
||||
no_mariadb_socket=no_mariadb_socket,
|
||||
setup=setup_db,
|
||||
)
|
||||
|
||||
apps_to_install = (
|
||||
|
|
@ -128,9 +130,10 @@ def install_db(
|
|||
db_host=None,
|
||||
db_port=None,
|
||||
no_mariadb_socket=False,
|
||||
setup=True,
|
||||
):
|
||||
import frappe.database
|
||||
from frappe.database import setup_database
|
||||
from frappe.database import bootstrap_database, setup_database
|
||||
|
||||
if not db_type:
|
||||
db_type = frappe.conf.db_type
|
||||
|
|
@ -152,7 +155,15 @@ def install_db(
|
|||
|
||||
frappe.flags.root_login = root_login
|
||||
frappe.flags.root_password = root_password
|
||||
setup_database(force, source_sql, verbose, no_mariadb_socket)
|
||||
|
||||
if setup:
|
||||
setup_database(force, verbose, no_mariadb_socket)
|
||||
|
||||
bootstrap_database(
|
||||
db_name=frappe.conf.db_name,
|
||||
verbose=verbose,
|
||||
source_sql=source_sql,
|
||||
)
|
||||
|
||||
frappe.conf.admin_password = frappe.conf.admin_password or admin_password
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.integrations.utils import json_handler
|
||||
from frappe.integrations.utils import get_json, json_handler
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class IntegrationRequest(Document):
|
|||
data = json.loads(self.data)
|
||||
data.update(params)
|
||||
|
||||
self.data = json.dumps(data)
|
||||
self.data = get_json(data)
|
||||
self.status = status
|
||||
self.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -69,9 +69,25 @@ def get_mapped_doc(
|
|||
|
||||
# main
|
||||
if not target_doc:
|
||||
target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"])
|
||||
target_doctype = table_maps[from_doctype]["doctype"]
|
||||
if table_maps[from_doctype].get("on_parent"):
|
||||
target_parent = table_maps[from_doctype].get("on_parent")
|
||||
if isinstance(target_parent, str):
|
||||
target_parent = frappe.get_doc(json.loads(target_parent))
|
||||
target_parentfield = target_parent.get_parentfield_of_doctype(target_doctype)
|
||||
target_doc = frappe.new_doc(
|
||||
target_doctype, parent_doc=target_parent, parentfield=target_parentfield
|
||||
)
|
||||
target_parent.append(target_parentfield, target_doc)
|
||||
ret_doc = target_parent
|
||||
else:
|
||||
target_doc = frappe.new_doc(target_doctype)
|
||||
ret_doc = target_doc
|
||||
elif isinstance(target_doc, str):
|
||||
target_doc = frappe.get_doc(json.loads(target_doc))
|
||||
ret_doc = target_doc
|
||||
else:
|
||||
ret_doc = target_doc
|
||||
|
||||
if (
|
||||
not apply_strict_user_permissions
|
||||
|
|
@ -147,16 +163,14 @@ def get_mapped_doc(
|
|||
if postprocess:
|
||||
postprocess(source_doc, target_doc)
|
||||
|
||||
target_doc.set_onload("load_after_mapping", True)
|
||||
ret_doc.set_onload("load_after_mapping", True)
|
||||
|
||||
if (
|
||||
apply_strict_user_permissions
|
||||
and not ignore_permissions
|
||||
and not target_doc.has_permission("create")
|
||||
apply_strict_user_permissions and not ignore_permissions and not ret_doc.has_permission("create")
|
||||
):
|
||||
target_doc.raise_no_permission_to("create")
|
||||
ret_doc.raise_no_permission_to("create")
|
||||
|
||||
return target_doc
|
||||
return ret_doc
|
||||
|
||||
|
||||
def map_doc(source_doc, target_doc, table_map, source_parent=None):
|
||||
|
|
|
|||
|
|
@ -201,8 +201,9 @@ def rename_doc(
|
|||
# call after_rename
|
||||
new_doc = frappe.get_doc(doctype, new)
|
||||
|
||||
# copy any flags if required
|
||||
new_doc._local = getattr(old_doc, "_local", None)
|
||||
if validate:
|
||||
# copy any flags if required
|
||||
new_doc._local = getattr(old_doc, "_local", None)
|
||||
|
||||
new_doc.run_method("after_rename", old, new, merge)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,19 +8,21 @@ from frappe.model.utils.user_settings import sync_user_settings, update_user_set
|
|||
from frappe.utils.password import rename_password_field
|
||||
|
||||
|
||||
def rename_field(doctype, old_fieldname, new_fieldname):
|
||||
def rename_field(doctype, old_fieldname, new_fieldname, validate=True):
|
||||
"""This functions assumes that doctype is already synced"""
|
||||
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
new_field = meta.get_field(new_fieldname)
|
||||
if not new_field:
|
||||
print("rename_field: " + (new_fieldname) + " not found in " + doctype)
|
||||
return
|
||||
|
||||
if not meta.issingle and not frappe.db.has_column(doctype, old_fieldname):
|
||||
print("rename_field: " + (old_fieldname) + " not found in table for: " + doctype)
|
||||
# never had the field?
|
||||
return
|
||||
if validate:
|
||||
if not new_field:
|
||||
print("rename_field: " + (new_fieldname) + " not found in " + doctype)
|
||||
return
|
||||
|
||||
if not meta.issingle and not frappe.db.has_column(doctype, old_fieldname):
|
||||
print("rename_field: " + (old_fieldname) + " not found in table for: " + doctype)
|
||||
# never had the field?
|
||||
return
|
||||
|
||||
if new_field.fieldtype in table_fields:
|
||||
# change parentfield of table mentioned in options
|
||||
|
|
|
|||
|
|
@ -1,164 +1,167 @@
|
|||
// TODO instead of making copy of inter.css find a way to import it.
|
||||
// workaround for css import as it fails for custom website_theme_template
|
||||
@font-face {
|
||||
font-family: 'Inter V';
|
||||
font-family: "Inter V";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
src: url('/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19') format('woff2-variations'),
|
||||
url('/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19') format('woff2');
|
||||
src: url('/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19') format('woff2') tech('variations');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter V';
|
||||
src: url("/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19") format("woff2-variations"),
|
||||
url("/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19") format("woff2");
|
||||
src: url("/assets/frappe/css/fonts/inter/Inter.var.woff2?v=3.19") format("woff2")
|
||||
tech("variations");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Inter V";
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
src: url('/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19') format('woff2-variations'),
|
||||
url('/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19') format('woff2');
|
||||
src: url('/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19') format('woff2') tech('variations');
|
||||
}
|
||||
src: url("/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19")
|
||||
format("woff2-variations"),
|
||||
url("/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19") format("woff2");
|
||||
src: url("/assets/frappe/css/fonts/inter/Inter-Italic.var.woff2?v=3.19") format("woff2")
|
||||
tech("variations");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_thinitalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_thinitalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_thinitalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_extralight.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_extralight.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_extralight.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_extralightitalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_extralightitalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_extralightitalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_light.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_light.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_light.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_lightitalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_lightitalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_lightitalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_regular.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_regular.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_regular.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_italic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_italic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_italic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_medium.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_medium.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_medium.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_mediumitalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_mediumitalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_mediumitalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_semibold.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_semibold.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_semibold.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_semibolditalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_semibolditalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_semibolditalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_bold.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_bold.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_bold.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_bolditalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_bolditalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_bolditalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_extrabold.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_extrabold.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_extrabold.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_extrabolditalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_extrabolditalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_extrabolditalic.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_black.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_black.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_black.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url("/assets/frappe/css/fonts/inter/inter_blackitalic.woff2") format("woff2"),
|
||||
url("/assets/frappe/css/fonts/inter/inter_blackitalic.woff") format("woff");
|
||||
url("/assets/frappe/css/fonts/inter/inter_blackitalic.woff") format("woff");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,220 +1,548 @@
|
|||
$octicons-font-path: "." !default;
|
||||
$octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
|
||||
$octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
|
||||
|
||||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: url('#{$octicons-font-path}/octicons.eot?#iefix&v=#{$octicons-version}') format('embedded-opentype'),
|
||||
url('#{$octicons-font-path}/octicons.woff?v=#{$octicons-version}') format('woff'),
|
||||
url('#{$octicons-font-path}/octicons.ttf?v=#{$octicons-version}') format('truetype'),
|
||||
url('#{$octicons-font-path}/octicons.svg?v=#{$octicons-version}#octicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-family: "octicons";
|
||||
src: url("#{$octicons-font-path}/octicons.eot?#iefix&v=#{$octicons-version}")
|
||||
format("embedded-opentype"),
|
||||
url("#{$octicons-font-path}/octicons.woff?v=#{$octicons-version}") format("woff"),
|
||||
url("#{$octicons-font-path}/octicons.ttf?v=#{$octicons-version}") format("truetype"),
|
||||
url("#{$octicons-font-path}/octicons.svg?v=#{$octicons-version}#octicons") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// .octicon is optimized for 16px.
|
||||
// .mega-octicon is optimized for 32px but can be used larger.
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
.octicon,
|
||||
.mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.mega-octicon {
|
||||
font-size: 32px;
|
||||
}
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-alert:before {
|
||||
content: "\f02d";
|
||||
} /* */
|
||||
.octicon-arrow-down:before {
|
||||
content: "\f03f";
|
||||
} /* */
|
||||
.octicon-arrow-left:before {
|
||||
content: "\f040";
|
||||
} /* */
|
||||
.octicon-arrow-right:before {
|
||||
content: "\f03e";
|
||||
} /* */
|
||||
.octicon-arrow-small-down:before {
|
||||
content: "\f0a0";
|
||||
} /* */
|
||||
.octicon-arrow-small-left:before {
|
||||
content: "\f0a1";
|
||||
} /* */
|
||||
.octicon-arrow-small-right:before {
|
||||
content: "\f071";
|
||||
} /* */
|
||||
.octicon-arrow-small-up:before {
|
||||
content: "\f09f";
|
||||
} /* */
|
||||
.octicon-arrow-up:before {
|
||||
content: "\f03d";
|
||||
} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-beaker:before {
|
||||
content: "\f0dd";
|
||||
} /* */
|
||||
.octicon-bell:before {
|
||||
content: "\f0de";
|
||||
} /* */
|
||||
.octicon-book:before {
|
||||
content: "\f007";
|
||||
} /* */
|
||||
.octicon-bookmark:before {
|
||||
content: "\f07b";
|
||||
} /* */
|
||||
.octicon-briefcase:before {
|
||||
content: "\f0d3";
|
||||
} /* */
|
||||
.octicon-broadcast:before {
|
||||
content: "\f048";
|
||||
} /* */
|
||||
.octicon-browser:before {
|
||||
content: "\f0c5";
|
||||
} /* */
|
||||
.octicon-bug:before {
|
||||
content: "\f091";
|
||||
} /* */
|
||||
.octicon-calendar:before {
|
||||
content: "\f068";
|
||||
} /* */
|
||||
.octicon-check:before {
|
||||
content: "\f03a";
|
||||
} /* */
|
||||
.octicon-checklist:before {
|
||||
content: "\f076";
|
||||
} /* */
|
||||
.octicon-chevron-down:before {
|
||||
content: "\f0a3";
|
||||
} /* */
|
||||
.octicon-chevron-left:before {
|
||||
content: "\f0a4";
|
||||
} /* */
|
||||
.octicon-chevron-right:before {
|
||||
content: "\f078";
|
||||
} /* */
|
||||
.octicon-chevron-up:before {
|
||||
content: "\f0a2";
|
||||
} /* */
|
||||
.octicon-circle-slash:before {
|
||||
content: "\f084";
|
||||
} /* */
|
||||
.octicon-circuit-board:before {
|
||||
content: "\f0d6";
|
||||
} /* */
|
||||
.octicon-clippy:before {
|
||||
content: "\f035";
|
||||
} /* */
|
||||
.octicon-clock:before {
|
||||
content: "\f046";
|
||||
} /* */
|
||||
.octicon-cloud-download:before {
|
||||
content: "\f00b";
|
||||
} /* */
|
||||
.octicon-cloud-upload:before {
|
||||
content: "\f00c";
|
||||
} /* */
|
||||
.octicon-code:before {
|
||||
content: "\f05f";
|
||||
} /* */
|
||||
.octicon-color-mode:before {
|
||||
content: "\f065";
|
||||
} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-comment:before {
|
||||
content: "\f02b";
|
||||
} /* */
|
||||
.octicon-comment-discussion:before {
|
||||
content: "\f04f";
|
||||
} /* */
|
||||
.octicon-credit-card:before {
|
||||
content: "\f045";
|
||||
} /* */
|
||||
.octicon-dash:before {
|
||||
content: "\f0ca";
|
||||
} /* */
|
||||
.octicon-dashboard:before {
|
||||
content: "\f07d";
|
||||
} /* */
|
||||
.octicon-database:before {
|
||||
content: "\f096";
|
||||
} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-desktop-download:before {
|
||||
content: "\f0dc";
|
||||
} /* */
|
||||
.octicon-device-camera:before {
|
||||
content: "\f056";
|
||||
} /* */
|
||||
.octicon-device-camera-video:before {
|
||||
content: "\f057";
|
||||
} /* */
|
||||
.octicon-device-desktop:before {
|
||||
content: "\f27c";
|
||||
} /* */
|
||||
.octicon-device-mobile:before {
|
||||
content: "\f038";
|
||||
} /* */
|
||||
.octicon-diff:before {
|
||||
content: "\f04d";
|
||||
} /* */
|
||||
.octicon-diff-added:before {
|
||||
content: "\f06b";
|
||||
} /* */
|
||||
.octicon-diff-ignored:before {
|
||||
content: "\f099";
|
||||
} /* */
|
||||
.octicon-diff-modified:before {
|
||||
content: "\f06d";
|
||||
} /* */
|
||||
.octicon-diff-removed:before {
|
||||
content: "\f06c";
|
||||
} /* */
|
||||
.octicon-diff-renamed:before {
|
||||
content: "\f06e";
|
||||
} /* */
|
||||
.octicon-ellipsis:before {
|
||||
content: "\f09a";
|
||||
} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-eye:before {
|
||||
content: "\f04e";
|
||||
} /* */
|
||||
.octicon-file-binary:before {
|
||||
content: "\f094";
|
||||
} /* */
|
||||
.octicon-file-code:before {
|
||||
content: "\f010";
|
||||
} /* */
|
||||
.octicon-file-directory:before {
|
||||
content: "\f016";
|
||||
} /* */
|
||||
.octicon-file-media:before {
|
||||
content: "\f012";
|
||||
} /* */
|
||||
.octicon-file-pdf:before {
|
||||
content: "\f014";
|
||||
} /* */
|
||||
.octicon-file-submodule:before {
|
||||
content: "\f017";
|
||||
} /* */
|
||||
.octicon-file-symlink-directory:before {
|
||||
content: "\f0b1";
|
||||
} /* */
|
||||
.octicon-file-symlink-file:before {
|
||||
content: "\f0b0";
|
||||
} /* */
|
||||
.octicon-file-text:before {
|
||||
content: "\f011";
|
||||
} /* */
|
||||
.octicon-file-zip:before {
|
||||
content: "\f013";
|
||||
} /* */
|
||||
.octicon-flame:before {
|
||||
content: "\f0d2";
|
||||
} /* */
|
||||
.octicon-fold:before {
|
||||
content: "\f0cc";
|
||||
} /* */
|
||||
.octicon-gear:before {
|
||||
content: "\f02f";
|
||||
} /* */
|
||||
.octicon-gift:before {
|
||||
content: "\f042";
|
||||
} /* */
|
||||
.octicon-gist:before {
|
||||
content: "\f00e";
|
||||
} /* */
|
||||
.octicon-gist-secret:before {
|
||||
content: "\f08c";
|
||||
} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-branch:before {
|
||||
content: "\f020";
|
||||
} /* */
|
||||
.octicon-git-commit:before {
|
||||
content: "\f01f";
|
||||
} /* */
|
||||
.octicon-git-compare:before {
|
||||
content: "\f0ac";
|
||||
} /* */
|
||||
.octicon-git-merge:before {
|
||||
content: "\f023";
|
||||
} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-git-pull-request:before {
|
||||
content: "\f009";
|
||||
} /* */
|
||||
.octicon-globe:before {
|
||||
content: "\f0b6";
|
||||
} /* */
|
||||
.octicon-graph:before {
|
||||
content: "\f043";
|
||||
} /* */
|
||||
.octicon-heart:before {
|
||||
content: "\2665";
|
||||
} /* ♥ */
|
||||
.octicon-history:before {
|
||||
content: "\f07e";
|
||||
} /* */
|
||||
.octicon-home:before {
|
||||
content: "\f08d";
|
||||
} /* */
|
||||
.octicon-horizontal-rule:before {
|
||||
content: "\f070";
|
||||
} /* */
|
||||
.octicon-hubot:before {
|
||||
content: "\f09d";
|
||||
} /* */
|
||||
.octicon-inbox:before {
|
||||
content: "\f0cf";
|
||||
} /* */
|
||||
.octicon-info:before {
|
||||
content: "\f059";
|
||||
} /* */
|
||||
.octicon-issue-closed:before {
|
||||
content: "\f028";
|
||||
} /* */
|
||||
.octicon-issue-opened:before {
|
||||
content: "\f026";
|
||||
} /* */
|
||||
.octicon-issue-reopened:before {
|
||||
content: "\f027";
|
||||
} /* */
|
||||
.octicon-jersey:before {
|
||||
content: "\f019";
|
||||
} /* */
|
||||
.octicon-key:before {
|
||||
content: "\f049";
|
||||
} /* */
|
||||
.octicon-keyboard:before {
|
||||
content: "\f00d";
|
||||
} /* */
|
||||
.octicon-law:before {
|
||||
content: "\f0d8";
|
||||
} /* */
|
||||
.octicon-light-bulb:before {
|
||||
content: "\f000";
|
||||
} /* */
|
||||
.octicon-link:before {
|
||||
content: "\f05c";
|
||||
} /* */
|
||||
.octicon-link-external:before {
|
||||
content: "\f07f";
|
||||
} /* */
|
||||
.octicon-list-ordered:before {
|
||||
content: "\f062";
|
||||
} /* */
|
||||
.octicon-list-unordered:before {
|
||||
content: "\f061";
|
||||
} /* */
|
||||
.octicon-location:before {
|
||||
content: "\f060";
|
||||
} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-lock:before {
|
||||
content: "\f06a";
|
||||
} /* */
|
||||
.octicon-logo-github:before {
|
||||
content: "\f092";
|
||||
} /* */
|
||||
.octicon-mail:before {
|
||||
content: "\f03b";
|
||||
} /* */
|
||||
.octicon-mail-read:before {
|
||||
content: "\f03c";
|
||||
} /* */
|
||||
.octicon-mail-reply:before {
|
||||
content: "\f051";
|
||||
} /* */
|
||||
.octicon-mark-github:before {
|
||||
content: "\f00a";
|
||||
} /* */
|
||||
.octicon-markdown:before {
|
||||
content: "\f0c9";
|
||||
} /* */
|
||||
.octicon-megaphone:before {
|
||||
content: "\f077";
|
||||
} /* */
|
||||
.octicon-mention:before {
|
||||
content: "\f0be";
|
||||
} /* */
|
||||
.octicon-milestone:before {
|
||||
content: "\f075";
|
||||
} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-mirror:before {
|
||||
content: "\f024";
|
||||
} /* */
|
||||
.octicon-mortar-board:before {
|
||||
content: "\f0d7";
|
||||
} /* */
|
||||
.octicon-mute:before {
|
||||
content: "\f080";
|
||||
} /* */
|
||||
.octicon-no-newline:before {
|
||||
content: "\f09c";
|
||||
} /* */
|
||||
.octicon-octoface:before {
|
||||
content: "\f008";
|
||||
} /* */
|
||||
.octicon-organization:before {
|
||||
content: "\f037";
|
||||
} /* */
|
||||
.octicon-package:before {
|
||||
content: "\f0c4";
|
||||
} /* */
|
||||
.octicon-paintcan:before {
|
||||
content: "\f0d1";
|
||||
} /* */
|
||||
.octicon-pencil:before {
|
||||
content: "\f058";
|
||||
} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-person:before {
|
||||
content: "\f018";
|
||||
} /* */
|
||||
.octicon-pin:before {
|
||||
content: "\f041";
|
||||
} /* */
|
||||
.octicon-plug:before {
|
||||
content: "\f0d4";
|
||||
} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-plus:before {
|
||||
content: "\f05d";
|
||||
} /* */
|
||||
.octicon-primitive-dot:before {
|
||||
content: "\f052";
|
||||
} /* */
|
||||
.octicon-primitive-square:before {
|
||||
content: "\f053";
|
||||
} /* */
|
||||
.octicon-pulse:before {
|
||||
content: "\f085";
|
||||
} /* */
|
||||
.octicon-question:before {
|
||||
content: "\f02c";
|
||||
} /* */
|
||||
.octicon-quote:before {
|
||||
content: "\f063";
|
||||
} /* */
|
||||
.octicon-radio-tower:before {
|
||||
content: "\f030";
|
||||
} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-repo:before {
|
||||
content: "\f001";
|
||||
} /* */
|
||||
.octicon-repo-clone:before {
|
||||
content: "\f04c";
|
||||
} /* */
|
||||
.octicon-repo-force-push:before {
|
||||
content: "\f04a";
|
||||
} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-repo-forked:before {
|
||||
content: "\f002";
|
||||
} /* */
|
||||
.octicon-repo-pull:before {
|
||||
content: "\f006";
|
||||
} /* */
|
||||
.octicon-repo-push:before {
|
||||
content: "\f005";
|
||||
} /* */
|
||||
.octicon-rocket:before {
|
||||
content: "\f033";
|
||||
} /* */
|
||||
.octicon-rss:before {
|
||||
content: "\f034";
|
||||
} /* */
|
||||
.octicon-ruby:before {
|
||||
content: "\f047";
|
||||
} /* */
|
||||
.octicon-screen-full:before {
|
||||
content: "\f066";
|
||||
} /* */
|
||||
.octicon-screen-normal:before {
|
||||
content: "\f067";
|
||||
} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-search:before {
|
||||
content: "\f02e";
|
||||
} /* */
|
||||
.octicon-server:before {
|
||||
content: "\f097";
|
||||
} /* */
|
||||
.octicon-settings:before {
|
||||
content: "\f07c";
|
||||
} /* */
|
||||
.octicon-shield:before {
|
||||
content: "\f0e1";
|
||||
} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-sign-in:before {
|
||||
content: "\f036";
|
||||
} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-sign-out:before {
|
||||
content: "\f032";
|
||||
} /* */
|
||||
.octicon-squirrel:before {
|
||||
content: "\f0b2";
|
||||
} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-star:before {
|
||||
content: "\f02a";
|
||||
} /* */
|
||||
.octicon-stop:before {
|
||||
content: "\f08f";
|
||||
} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-sync:before {
|
||||
content: "\f087";
|
||||
} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-tag:before {
|
||||
content: "\f015";
|
||||
} /* */
|
||||
.octicon-telescope:before {
|
||||
content: "\f088";
|
||||
} /* */
|
||||
.octicon-terminal:before {
|
||||
content: "\f0c8";
|
||||
} /* */
|
||||
.octicon-three-bars:before {
|
||||
content: "\f05e";
|
||||
} /* */
|
||||
.octicon-thumbsdown:before {
|
||||
content: "\f0db";
|
||||
} /* */
|
||||
.octicon-thumbsup:before {
|
||||
content: "\f0da";
|
||||
} /* */
|
||||
.octicon-tools:before {
|
||||
content: "\f031";
|
||||
} /* */
|
||||
.octicon-trashcan:before {
|
||||
content: "\f0d0";
|
||||
} /* */
|
||||
.octicon-triangle-down:before {
|
||||
content: "\f05b";
|
||||
} /* */
|
||||
.octicon-triangle-left:before {
|
||||
content: "\f044";
|
||||
} /* */
|
||||
.octicon-triangle-right:before {
|
||||
content: "\f05a";
|
||||
} /* */
|
||||
.octicon-triangle-up:before {
|
||||
content: "\f0aa";
|
||||
} /* */
|
||||
.octicon-unfold:before {
|
||||
content: "\f039";
|
||||
} /* */
|
||||
.octicon-unmute:before {
|
||||
content: "\f0ba";
|
||||
} /* */
|
||||
.octicon-versions:before {
|
||||
content: "\f064";
|
||||
} /* */
|
||||
.octicon-watch:before {
|
||||
content: "\f0e0";
|
||||
} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
.octicon-x:before {
|
||||
content: "\f081";
|
||||
} /* */
|
||||
.octicon-zap:before {
|
||||
content: "\26A1";
|
||||
} /* ⚡ */
|
||||
|
|
|
|||
688
frappe/public/css/octicons/sprockets-octicons.scss
vendored
688
frappe/public/css/octicons/sprockets-octicons.scss
vendored
|
|
@ -1,217 +1,543 @@
|
|||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
|
||||
font-url('octicons.woff') format('woff'),
|
||||
font-url('octicons.ttf') format('truetype'),
|
||||
font-url('octicons.svg#octicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-family: "octicons";
|
||||
src: font-url("octicons.eot?#iefix") format("embedded-opentype"),
|
||||
font-url("octicons.woff") format("woff"), font-url("octicons.ttf") format("truetype"),
|
||||
font-url("octicons.svg#octicons") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// .octicon is optimized for 16px.
|
||||
// .mega-octicon is optimized for 32px but can be used larger.
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
.octicon,
|
||||
.mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.mega-octicon {
|
||||
font-size: 32px;
|
||||
}
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-alert:before {
|
||||
content: "\f02d";
|
||||
} /* */
|
||||
.octicon-arrow-down:before {
|
||||
content: "\f03f";
|
||||
} /* */
|
||||
.octicon-arrow-left:before {
|
||||
content: "\f040";
|
||||
} /* */
|
||||
.octicon-arrow-right:before {
|
||||
content: "\f03e";
|
||||
} /* */
|
||||
.octicon-arrow-small-down:before {
|
||||
content: "\f0a0";
|
||||
} /* */
|
||||
.octicon-arrow-small-left:before {
|
||||
content: "\f0a1";
|
||||
} /* */
|
||||
.octicon-arrow-small-right:before {
|
||||
content: "\f071";
|
||||
} /* */
|
||||
.octicon-arrow-small-up:before {
|
||||
content: "\f09f";
|
||||
} /* */
|
||||
.octicon-arrow-up:before {
|
||||
content: "\f03d";
|
||||
} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-beaker:before {
|
||||
content: "\f0dd";
|
||||
} /* */
|
||||
.octicon-bell:before {
|
||||
content: "\f0de";
|
||||
} /* */
|
||||
.octicon-book:before {
|
||||
content: "\f007";
|
||||
} /* */
|
||||
.octicon-bookmark:before {
|
||||
content: "\f07b";
|
||||
} /* */
|
||||
.octicon-briefcase:before {
|
||||
content: "\f0d3";
|
||||
} /* */
|
||||
.octicon-broadcast:before {
|
||||
content: "\f048";
|
||||
} /* */
|
||||
.octicon-browser:before {
|
||||
content: "\f0c5";
|
||||
} /* */
|
||||
.octicon-bug:before {
|
||||
content: "\f091";
|
||||
} /* */
|
||||
.octicon-calendar:before {
|
||||
content: "\f068";
|
||||
} /* */
|
||||
.octicon-check:before {
|
||||
content: "\f03a";
|
||||
} /* */
|
||||
.octicon-checklist:before {
|
||||
content: "\f076";
|
||||
} /* */
|
||||
.octicon-chevron-down:before {
|
||||
content: "\f0a3";
|
||||
} /* */
|
||||
.octicon-chevron-left:before {
|
||||
content: "\f0a4";
|
||||
} /* */
|
||||
.octicon-chevron-right:before {
|
||||
content: "\f078";
|
||||
} /* */
|
||||
.octicon-chevron-up:before {
|
||||
content: "\f0a2";
|
||||
} /* */
|
||||
.octicon-circle-slash:before {
|
||||
content: "\f084";
|
||||
} /* */
|
||||
.octicon-circuit-board:before {
|
||||
content: "\f0d6";
|
||||
} /* */
|
||||
.octicon-clippy:before {
|
||||
content: "\f035";
|
||||
} /* */
|
||||
.octicon-clock:before {
|
||||
content: "\f046";
|
||||
} /* */
|
||||
.octicon-cloud-download:before {
|
||||
content: "\f00b";
|
||||
} /* */
|
||||
.octicon-cloud-upload:before {
|
||||
content: "\f00c";
|
||||
} /* */
|
||||
.octicon-code:before {
|
||||
content: "\f05f";
|
||||
} /* */
|
||||
.octicon-color-mode:before {
|
||||
content: "\f065";
|
||||
} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-comment:before {
|
||||
content: "\f02b";
|
||||
} /* */
|
||||
.octicon-comment-discussion:before {
|
||||
content: "\f04f";
|
||||
} /* */
|
||||
.octicon-credit-card:before {
|
||||
content: "\f045";
|
||||
} /* */
|
||||
.octicon-dash:before {
|
||||
content: "\f0ca";
|
||||
} /* */
|
||||
.octicon-dashboard:before {
|
||||
content: "\f07d";
|
||||
} /* */
|
||||
.octicon-database:before {
|
||||
content: "\f096";
|
||||
} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-desktop-download:before {
|
||||
content: "\f0dc";
|
||||
} /* */
|
||||
.octicon-device-camera:before {
|
||||
content: "\f056";
|
||||
} /* */
|
||||
.octicon-device-camera-video:before {
|
||||
content: "\f057";
|
||||
} /* */
|
||||
.octicon-device-desktop:before {
|
||||
content: "\f27c";
|
||||
} /* */
|
||||
.octicon-device-mobile:before {
|
||||
content: "\f038";
|
||||
} /* */
|
||||
.octicon-diff:before {
|
||||
content: "\f04d";
|
||||
} /* */
|
||||
.octicon-diff-added:before {
|
||||
content: "\f06b";
|
||||
} /* */
|
||||
.octicon-diff-ignored:before {
|
||||
content: "\f099";
|
||||
} /* */
|
||||
.octicon-diff-modified:before {
|
||||
content: "\f06d";
|
||||
} /* */
|
||||
.octicon-diff-removed:before {
|
||||
content: "\f06c";
|
||||
} /* */
|
||||
.octicon-diff-renamed:before {
|
||||
content: "\f06e";
|
||||
} /* */
|
||||
.octicon-ellipsis:before {
|
||||
content: "\f09a";
|
||||
} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-eye:before {
|
||||
content: "\f04e";
|
||||
} /* */
|
||||
.octicon-file-binary:before {
|
||||
content: "\f094";
|
||||
} /* */
|
||||
.octicon-file-code:before {
|
||||
content: "\f010";
|
||||
} /* */
|
||||
.octicon-file-directory:before {
|
||||
content: "\f016";
|
||||
} /* */
|
||||
.octicon-file-media:before {
|
||||
content: "\f012";
|
||||
} /* */
|
||||
.octicon-file-pdf:before {
|
||||
content: "\f014";
|
||||
} /* */
|
||||
.octicon-file-submodule:before {
|
||||
content: "\f017";
|
||||
} /* */
|
||||
.octicon-file-symlink-directory:before {
|
||||
content: "\f0b1";
|
||||
} /* */
|
||||
.octicon-file-symlink-file:before {
|
||||
content: "\f0b0";
|
||||
} /* */
|
||||
.octicon-file-text:before {
|
||||
content: "\f011";
|
||||
} /* */
|
||||
.octicon-file-zip:before {
|
||||
content: "\f013";
|
||||
} /* */
|
||||
.octicon-flame:before {
|
||||
content: "\f0d2";
|
||||
} /* */
|
||||
.octicon-fold:before {
|
||||
content: "\f0cc";
|
||||
} /* */
|
||||
.octicon-gear:before {
|
||||
content: "\f02f";
|
||||
} /* */
|
||||
.octicon-gift:before {
|
||||
content: "\f042";
|
||||
} /* */
|
||||
.octicon-gist:before {
|
||||
content: "\f00e";
|
||||
} /* */
|
||||
.octicon-gist-secret:before {
|
||||
content: "\f08c";
|
||||
} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-branch:before {
|
||||
content: "\f020";
|
||||
} /* */
|
||||
.octicon-git-commit:before {
|
||||
content: "\f01f";
|
||||
} /* */
|
||||
.octicon-git-compare:before {
|
||||
content: "\f0ac";
|
||||
} /* */
|
||||
.octicon-git-merge:before {
|
||||
content: "\f023";
|
||||
} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-git-pull-request:before {
|
||||
content: "\f009";
|
||||
} /* */
|
||||
.octicon-globe:before {
|
||||
content: "\f0b6";
|
||||
} /* */
|
||||
.octicon-graph:before {
|
||||
content: "\f043";
|
||||
} /* */
|
||||
.octicon-heart:before {
|
||||
content: "\2665";
|
||||
} /* ♥ */
|
||||
.octicon-history:before {
|
||||
content: "\f07e";
|
||||
} /* */
|
||||
.octicon-home:before {
|
||||
content: "\f08d";
|
||||
} /* */
|
||||
.octicon-horizontal-rule:before {
|
||||
content: "\f070";
|
||||
} /* */
|
||||
.octicon-hubot:before {
|
||||
content: "\f09d";
|
||||
} /* */
|
||||
.octicon-inbox:before {
|
||||
content: "\f0cf";
|
||||
} /* */
|
||||
.octicon-info:before {
|
||||
content: "\f059";
|
||||
} /* */
|
||||
.octicon-issue-closed:before {
|
||||
content: "\f028";
|
||||
} /* */
|
||||
.octicon-issue-opened:before {
|
||||
content: "\f026";
|
||||
} /* */
|
||||
.octicon-issue-reopened:before {
|
||||
content: "\f027";
|
||||
} /* */
|
||||
.octicon-jersey:before {
|
||||
content: "\f019";
|
||||
} /* */
|
||||
.octicon-key:before {
|
||||
content: "\f049";
|
||||
} /* */
|
||||
.octicon-keyboard:before {
|
||||
content: "\f00d";
|
||||
} /* */
|
||||
.octicon-law:before {
|
||||
content: "\f0d8";
|
||||
} /* */
|
||||
.octicon-light-bulb:before {
|
||||
content: "\f000";
|
||||
} /* */
|
||||
.octicon-link:before {
|
||||
content: "\f05c";
|
||||
} /* */
|
||||
.octicon-link-external:before {
|
||||
content: "\f07f";
|
||||
} /* */
|
||||
.octicon-list-ordered:before {
|
||||
content: "\f062";
|
||||
} /* */
|
||||
.octicon-list-unordered:before {
|
||||
content: "\f061";
|
||||
} /* */
|
||||
.octicon-location:before {
|
||||
content: "\f060";
|
||||
} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-lock:before {
|
||||
content: "\f06a";
|
||||
} /* */
|
||||
.octicon-logo-github:before {
|
||||
content: "\f092";
|
||||
} /* */
|
||||
.octicon-mail:before {
|
||||
content: "\f03b";
|
||||
} /* */
|
||||
.octicon-mail-read:before {
|
||||
content: "\f03c";
|
||||
} /* */
|
||||
.octicon-mail-reply:before {
|
||||
content: "\f051";
|
||||
} /* */
|
||||
.octicon-mark-github:before {
|
||||
content: "\f00a";
|
||||
} /* */
|
||||
.octicon-markdown:before {
|
||||
content: "\f0c9";
|
||||
} /* */
|
||||
.octicon-megaphone:before {
|
||||
content: "\f077";
|
||||
} /* */
|
||||
.octicon-mention:before {
|
||||
content: "\f0be";
|
||||
} /* */
|
||||
.octicon-milestone:before {
|
||||
content: "\f075";
|
||||
} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-mirror:before {
|
||||
content: "\f024";
|
||||
} /* */
|
||||
.octicon-mortar-board:before {
|
||||
content: "\f0d7";
|
||||
} /* */
|
||||
.octicon-mute:before {
|
||||
content: "\f080";
|
||||
} /* */
|
||||
.octicon-no-newline:before {
|
||||
content: "\f09c";
|
||||
} /* */
|
||||
.octicon-octoface:before {
|
||||
content: "\f008";
|
||||
} /* */
|
||||
.octicon-organization:before {
|
||||
content: "\f037";
|
||||
} /* */
|
||||
.octicon-package:before {
|
||||
content: "\f0c4";
|
||||
} /* */
|
||||
.octicon-paintcan:before {
|
||||
content: "\f0d1";
|
||||
} /* */
|
||||
.octicon-pencil:before {
|
||||
content: "\f058";
|
||||
} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-person:before {
|
||||
content: "\f018";
|
||||
} /* */
|
||||
.octicon-pin:before {
|
||||
content: "\f041";
|
||||
} /* */
|
||||
.octicon-plug:before {
|
||||
content: "\f0d4";
|
||||
} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-plus:before {
|
||||
content: "\f05d";
|
||||
} /* */
|
||||
.octicon-primitive-dot:before {
|
||||
content: "\f052";
|
||||
} /* */
|
||||
.octicon-primitive-square:before {
|
||||
content: "\f053";
|
||||
} /* */
|
||||
.octicon-pulse:before {
|
||||
content: "\f085";
|
||||
} /* */
|
||||
.octicon-question:before {
|
||||
content: "\f02c";
|
||||
} /* */
|
||||
.octicon-quote:before {
|
||||
content: "\f063";
|
||||
} /* */
|
||||
.octicon-radio-tower:before {
|
||||
content: "\f030";
|
||||
} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-repo:before {
|
||||
content: "\f001";
|
||||
} /* */
|
||||
.octicon-repo-clone:before {
|
||||
content: "\f04c";
|
||||
} /* */
|
||||
.octicon-repo-force-push:before {
|
||||
content: "\f04a";
|
||||
} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-repo-forked:before {
|
||||
content: "\f002";
|
||||
} /* */
|
||||
.octicon-repo-pull:before {
|
||||
content: "\f006";
|
||||
} /* */
|
||||
.octicon-repo-push:before {
|
||||
content: "\f005";
|
||||
} /* */
|
||||
.octicon-rocket:before {
|
||||
content: "\f033";
|
||||
} /* */
|
||||
.octicon-rss:before {
|
||||
content: "\f034";
|
||||
} /* */
|
||||
.octicon-ruby:before {
|
||||
content: "\f047";
|
||||
} /* */
|
||||
.octicon-screen-full:before {
|
||||
content: "\f066";
|
||||
} /* */
|
||||
.octicon-screen-normal:before {
|
||||
content: "\f067";
|
||||
} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-search:before {
|
||||
content: "\f02e";
|
||||
} /* */
|
||||
.octicon-server:before {
|
||||
content: "\f097";
|
||||
} /* */
|
||||
.octicon-settings:before {
|
||||
content: "\f07c";
|
||||
} /* */
|
||||
.octicon-shield:before {
|
||||
content: "\f0e1";
|
||||
} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-sign-in:before {
|
||||
content: "\f036";
|
||||
} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-sign-out:before {
|
||||
content: "\f032";
|
||||
} /* */
|
||||
.octicon-squirrel:before {
|
||||
content: "\f0b2";
|
||||
} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-star:before {
|
||||
content: "\f02a";
|
||||
} /* */
|
||||
.octicon-stop:before {
|
||||
content: "\f08f";
|
||||
} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-sync:before {
|
||||
content: "\f087";
|
||||
} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-tag:before {
|
||||
content: "\f015";
|
||||
} /* */
|
||||
.octicon-telescope:before {
|
||||
content: "\f088";
|
||||
} /* */
|
||||
.octicon-terminal:before {
|
||||
content: "\f0c8";
|
||||
} /* */
|
||||
.octicon-three-bars:before {
|
||||
content: "\f05e";
|
||||
} /* */
|
||||
.octicon-thumbsdown:before {
|
||||
content: "\f0db";
|
||||
} /* */
|
||||
.octicon-thumbsup:before {
|
||||
content: "\f0da";
|
||||
} /* */
|
||||
.octicon-tools:before {
|
||||
content: "\f031";
|
||||
} /* */
|
||||
.octicon-trashcan:before {
|
||||
content: "\f0d0";
|
||||
} /* */
|
||||
.octicon-triangle-down:before {
|
||||
content: "\f05b";
|
||||
} /* */
|
||||
.octicon-triangle-left:before {
|
||||
content: "\f044";
|
||||
} /* */
|
||||
.octicon-triangle-right:before {
|
||||
content: "\f05a";
|
||||
} /* */
|
||||
.octicon-triangle-up:before {
|
||||
content: "\f0aa";
|
||||
} /* */
|
||||
.octicon-unfold:before {
|
||||
content: "\f039";
|
||||
} /* */
|
||||
.octicon-unmute:before {
|
||||
content: "\f0ba";
|
||||
} /* */
|
||||
.octicon-versions:before {
|
||||
content: "\f064";
|
||||
} /* */
|
||||
.octicon-watch:before {
|
||||
content: "\f0e0";
|
||||
} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
.octicon-x:before {
|
||||
content: "\f081";
|
||||
} /* */
|
||||
.octicon-zap:before {
|
||||
content: "\26A1";
|
||||
} /* ⚡ */
|
||||
|
|
|
|||
|
|
@ -815,7 +815,7 @@
|
|||
|
||||
<symbol viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" id="icon-duplicate">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.936 2a2 2 0 0 0-2 2v.649h1V4a1 1 0 0 1 1-1h5.566a1 1 0 0 1 1 1v4.595a1 1 0 0 1-1 1h-.642v1h.642a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H6.936zM3.5 5.402a2 2 0 0 0-2 2v4.595a2 2 0 0 0 2 2h5.566a2 2 0 0 0 2-2V7.402a2 2 0 0 0-2-2H3.5zm-1 2a1 1 0 0 1 1-1h5.566a1 1 0 0 1 1 1v4.595a1 1 0 0 1-1 1H3.5a1 1 0 0 1-1-1V7.402z"
|
||||
stroke="none" fill="#192734"></path>
|
||||
stroke="none" fill="var(--icon-stroke)"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-chart">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
|
@ -113,10 +113,11 @@ watch(showOptions, (val) => {
|
|||
|
||||
.combo-box-options {
|
||||
width: 100%;
|
||||
background-color: var(--white);
|
||||
background-color: var(--fg-color);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
padding: 0;
|
||||
border: 1px solid var(--subtle-accent);
|
||||
}
|
||||
|
||||
.combo-box-option {
|
||||
|
|
|
|||
|
|
@ -1,113 +1,3 @@
|
|||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import Field from "./Field.vue";
|
||||
import AddFieldButton from "./AddFieldButton.vue";
|
||||
import EditableInput from "./EditableInput.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { move_children_to_parent, confirm_dialog, is_touch_screen_device } from "../utils";
|
||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
||||
|
||||
const props = defineProps(["section", "column"]);
|
||||
const store = useStore();
|
||||
|
||||
// delete/backspace to delete the field
|
||||
const { Backspace } = useMagicKeys();
|
||||
whenever(Backspace, (value) => {
|
||||
if (value && selected.value && store.not_using_input) {
|
||||
remove_column();
|
||||
}
|
||||
});
|
||||
|
||||
const hovered = ref(false);
|
||||
const selected = computed(() => store.selected(props.column.df.name));
|
||||
|
||||
function add_column() {
|
||||
// insert new column after the current column
|
||||
let index = props.section.columns.indexOf(props.column);
|
||||
props.section.columns.splice(index + 1, 0, {
|
||||
df: store.get_df("Column Break"),
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
|
||||
function remove_column() {
|
||||
if (store.is_customize_form && props.column.df.is_custom_field == 0) {
|
||||
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
|
||||
throw "cannot delete standard field";
|
||||
} else if (props.column.fields.length == 0 || store.has_standard_field(props.column)) {
|
||||
delete_column();
|
||||
} else {
|
||||
confirm_dialog(
|
||||
__("Delete Column", null, "Title of confirmation dialog"),
|
||||
__(
|
||||
"Are you sure you want to delete the column? All the fields in the column will be moved to the previous column.",
|
||||
null,
|
||||
"Confirmation dialog message"
|
||||
),
|
||||
() => delete_column(),
|
||||
__("Delete column", null, "Button text"),
|
||||
() => delete_column(true),
|
||||
__("Delete entire column with fields", null, "Button text")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function delete_column(with_children) {
|
||||
// move all fields to previous column
|
||||
let columns = props.section.columns;
|
||||
let index = columns.indexOf(props.column);
|
||||
|
||||
if (with_children && index == 0 && columns.length == 1) {
|
||||
if (props.column.fields.length == 0) {
|
||||
frappe.msgprint(__("Section must have at least one column"));
|
||||
throw "section must have at least one column";
|
||||
}
|
||||
|
||||
columns.unshift({
|
||||
df: store.get_df("Column Break"),
|
||||
fields: [],
|
||||
is_first: true,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!with_children) {
|
||||
if (index > 0) {
|
||||
let prev_column = columns[index - 1];
|
||||
prev_column.fields = [...prev_column.fields, ...props.column.fields];
|
||||
} else {
|
||||
if (props.column.fields.length == 0) {
|
||||
// set next column as first column
|
||||
let next_column = columns[index + 1];
|
||||
if (next_column) {
|
||||
next_column.is_first = true;
|
||||
} else {
|
||||
frappe.msgprint(__("Section must have at least one column"));
|
||||
throw "section must have at least one column";
|
||||
}
|
||||
} else {
|
||||
// create a new column if current column has fields and push fields to it
|
||||
columns.unshift({
|
||||
df: store.get_df("Column Break"),
|
||||
fields: props.column.fields,
|
||||
is_first: true,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove column
|
||||
columns.splice(index, 1);
|
||||
store.form.selected_field = null;
|
||||
}
|
||||
|
||||
function move_columns_to_section() {
|
||||
move_children_to_parent(props, "section", "column", store.current_tab);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="['column', selected ? 'selected' : hovered ? 'hovered' : '']"
|
||||
|
|
@ -117,35 +7,12 @@ function move_columns_to_section() {
|
|||
@mouseout.stop="hovered = false"
|
||||
>
|
||||
<div
|
||||
:class="['column-header', column.df.label ? 'has-label' : '']"
|
||||
v-if="column.df.label"
|
||||
class="column-header"
|
||||
:hidden="!column.df.label && store.read_only"
|
||||
>
|
||||
<div class="column-label">
|
||||
<EditableInput
|
||||
:text="column.df.label"
|
||||
:placeholder="__('Column Title')"
|
||||
v-model="column.df.label"
|
||||
/>
|
||||
</div>
|
||||
<div class="column-actions">
|
||||
<button class="btn btn-xs btn-icon" :title="__('Add Column')" @click="add_column">
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
v-if="section.columns.indexOf(column)"
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Move the current column & the following columns to a new section')"
|
||||
@click="move_columns_to_section"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Remove Column')"
|
||||
@click.stop="remove_column"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')"></div>
|
||||
</button>
|
||||
<span>{{ column.df.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="column.df.description" class="column-description">
|
||||
|
|
@ -169,11 +36,7 @@ function move_columns_to_section() {
|
|||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<div
|
||||
class="empty-column"
|
||||
:hidden="store.read_only"
|
||||
:style="store.selected(column.df.name) ? { top: '35px' } : { top: 0 }"
|
||||
>
|
||||
<div class="empty-column" :hidden="store.read_only">
|
||||
<AddFieldButton :column="column" />
|
||||
</div>
|
||||
<div v-if="column.fields.length" class="add-new-field-btn">
|
||||
|
|
@ -182,6 +45,30 @@ function move_columns_to_section() {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import Field from "./Field.vue";
|
||||
import AddFieldButton from "./AddFieldButton.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { is_touch_screen_device } from "../utils";
|
||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
||||
|
||||
const props = defineProps(["section", "column"]);
|
||||
const store = useStore();
|
||||
|
||||
// delete/backspace to delete the field
|
||||
const { Backspace } = useMagicKeys();
|
||||
whenever(Backspace, (value) => {
|
||||
if (value && selected.value && store.not_using_input) {
|
||||
remove_column();
|
||||
}
|
||||
});
|
||||
|
||||
const hovered = ref(false);
|
||||
const selected = computed(() => store.selected(props.column.df.name));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.column {
|
||||
position: relative;
|
||||
|
|
@ -197,35 +84,17 @@ function move_columns_to_section() {
|
|||
|
||||
&.hovered,
|
||||
&.selected {
|
||||
border-color: var(--primary);
|
||||
border-color: var(--border-primary);
|
||||
border-style: solid;
|
||||
|
||||
.btn.btn-icon {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.column-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.column-container:empty {
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.column-header {
|
||||
display: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 0.3rem;
|
||||
|
||||
&.has-label {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.column-label {
|
||||
:deep(span) {
|
||||
font-weight: 600;
|
||||
|
|
@ -278,8 +147,9 @@ function move_columns_to_section() {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
|
|
@ -304,7 +174,7 @@ function move_columns_to_section() {
|
|||
padding: 10px 6px 5px;
|
||||
|
||||
button {
|
||||
background-color: var(--white);
|
||||
background-color: var(--fg-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-default-hover-bg);
|
||||
|
|
|
|||
129
frappe/public/js/form_builder/components/Dropdown.vue
Normal file
129
frappe/public/js/form_builder/components/Dropdown.vue
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<button
|
||||
ref="dropdown_btn_ref"
|
||||
class="dropdown-btn btn btn-xs btn-icon"
|
||||
@click.stop="toggle_fieldtype_options"
|
||||
>
|
||||
<slot>
|
||||
<div v-html="frappe.utils.icon('dot-horizontal', 'sm')" />
|
||||
</slot>
|
||||
<Teleport to="#autocomplete-area">
|
||||
<div class="dropdown" ref="dropdown_ref">
|
||||
<div v-show="show" class="dropdown-options">
|
||||
<div v-for="group in groups" :key="group.key" class="groups">
|
||||
<div v-if="group.group" class="group-title">
|
||||
{{ group.group }}
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-option"
|
||||
v-for="item in group.items"
|
||||
:key="item.label"
|
||||
:title="item.tooltip"
|
||||
>
|
||||
<button class="dropdown-item" @click.stop="action(item.onClick)">
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { createPopper } from "@popperjs/core";
|
||||
import { nextTick, ref, computed } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: "bottom-end",
|
||||
},
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
const dropdown_btn_ref = ref(null);
|
||||
const dropdown_ref = ref(null);
|
||||
const popper = ref(null);
|
||||
|
||||
onClickOutside(dropdown_btn_ref, () => (show.value = false), { ignore: [dropdown_ref] });
|
||||
|
||||
const groups = computed(() => {
|
||||
let _groups = props.options[0]?.group ? props.options : [{ group: "", items: props.options }];
|
||||
|
||||
return _groups.map((group, i) => {
|
||||
return {
|
||||
key: i,
|
||||
group: group.group,
|
||||
items: group.items,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function setupPopper() {
|
||||
if (!popper.value) {
|
||||
popper.value = createPopper(dropdown_btn_ref.value, dropdown_ref.value, {
|
||||
placement: props.placement,
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 4],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
popper.value.update();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_fieldtype_options() {
|
||||
show.value = !show.value;
|
||||
nextTick(() => setupPopper());
|
||||
}
|
||||
|
||||
function action(clickEvent) {
|
||||
clickEvent && clickEvent();
|
||||
show.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.groups {
|
||||
padding: 5px;
|
||||
|
||||
.group-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
font-size: smaller;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-btn {
|
||||
&:hover {
|
||||
background-color: var(--bg-light-gray);
|
||||
}
|
||||
}
|
||||
.dropdown-options {
|
||||
background-color: var(--fg-color);
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
padding: 4px;
|
||||
border: 1px solid var(--subtle-accent);
|
||||
}
|
||||
.dropdown {
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -75,6 +75,110 @@ function duplicate_field() {
|
|||
store.form.selected_field = duplicate_field.df;
|
||||
}
|
||||
|
||||
function make_dialog(frm) {
|
||||
frm.dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Filters"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "filter_area",
|
||||
},
|
||||
],
|
||||
primary_action: () => {
|
||||
let fieldname = props.field.df.fieldname;
|
||||
let field_option = props.field.df.options;
|
||||
let filters = frm.filter_group.get_filters().map((filter) => {
|
||||
// last element is a boolean which hides the filter hence not required to store in meta
|
||||
filter.pop();
|
||||
|
||||
// filter_group component requires options and frm.set_query requires fieldname so storing both
|
||||
filter[0] = { fieldname, field_option };
|
||||
return filter;
|
||||
});
|
||||
|
||||
props.field.df.link_filters = JSON.stringify(filters);
|
||||
frm.dialog.hide();
|
||||
},
|
||||
primary_action_label: __("Apply"),
|
||||
});
|
||||
|
||||
if (frm.doctype === "Customize Form") {
|
||||
let current_doctype = frm.doc.doc_type;
|
||||
let fieldname = props.field.df.fieldname;
|
||||
let property = "link_filters";
|
||||
let property_setter_id = current_doctype + "-" + fieldname + "-" + property;
|
||||
|
||||
frappe.db.exists("Property Setter", property_setter_id).then((exits) => {
|
||||
if (exits) {
|
||||
frm.dialog.set_secondary_action_label(__("Reset To Default"));
|
||||
frm.dialog.set_secondary_action(() => {
|
||||
frappe.call({
|
||||
method: "frappe.custom.doctype.customize_form.customize_form.get_link_filters_from_doc_without_customisations",
|
||||
args: {
|
||||
doctype: current_doctype,
|
||||
fieldname: fieldname,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
props.field.df.link_filters = r.message;
|
||||
|
||||
frm.filter_group.clear_filters();
|
||||
add_existing_filter(frm, props.field.df);
|
||||
// hide the secondary action button
|
||||
frm.dialog.get_secondary_btn().addClass("hidden");
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setting selected field in store because when we click on the dialog the selected field is set to null
|
||||
frm.dialog.$wrapper.on("click", () => {
|
||||
store.form.selected_field = props.field.df;
|
||||
});
|
||||
}
|
||||
|
||||
function make_filter_area(frm, doctype) {
|
||||
frm.filter_group = new frappe.ui.FilterGroup({
|
||||
parent: frm.dialog.get_field("filter_area").$wrapper,
|
||||
doctype: doctype,
|
||||
on_change: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function add_existing_filter(frm, df) {
|
||||
if (df.link_filters) {
|
||||
let filters = JSON.parse(df.link_filters);
|
||||
filters.map((filter) => {
|
||||
// filter_group component requires options and frm.set_query requires fieldname
|
||||
filter[0] = filter[0].field_option;
|
||||
});
|
||||
if (filters) {
|
||||
frm.filter_group.add_filters_to_filter_group(filters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function edit_filters() {
|
||||
let field_doctype = props.field.df.options;
|
||||
const { frm } = store;
|
||||
|
||||
make_dialog(frm);
|
||||
make_filter_area(frm, field_doctype);
|
||||
frappe.model.with_doctype(field_doctype, () => {
|
||||
frm.dialog.show();
|
||||
add_existing_filter(frm, props.field.df);
|
||||
});
|
||||
}
|
||||
|
||||
function is_filter_applied() {
|
||||
if (props.field.df.link_filters && JSON.parse(props.field.df.link_filters).length > 0) {
|
||||
return "btn-filter-applied";
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => selected.value && label_input.value.focus_on_label());
|
||||
</script>
|
||||
|
||||
|
|
@ -111,22 +215,17 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
</template>
|
||||
<template #actions>
|
||||
<div class="field-actions" :hidden="store.read_only">
|
||||
<AddFieldButton
|
||||
v-if="column.fields.indexOf(field) != column.fields.length - 1"
|
||||
ref="add_field_ref"
|
||||
:field="field"
|
||||
:column="column"
|
||||
:tooltip="__('Add field below')"
|
||||
<button
|
||||
v-if="field.df.fieldtype === 'Link'"
|
||||
class="btn btn-xs btn-icon"
|
||||
:class="is_filter_applied()"
|
||||
@click="edit_filters"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('filter', 'sm')"></div>
|
||||
</button>
|
||||
<AddFieldButton ref="add_field_ref" :column="column" :field="field">
|
||||
<div v-html="frappe.utils.icon('add', 'sm')" />
|
||||
</AddFieldButton>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Duplicate field')"
|
||||
@click.stop="duplicate_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
v-if="column.fields.indexOf(field)"
|
||||
class="btn btn-xs btn-icon"
|
||||
|
|
@ -137,6 +236,13 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Duplicate field')"
|
||||
@click.stop="duplicate_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Remove field')"
|
||||
|
|
@ -166,7 +272,7 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
|
||||
&.hovered,
|
||||
&.selected {
|
||||
border-color: var(--primary);
|
||||
border-color: var(--border-primary);
|
||||
.btn.btn-icon {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
|
@ -211,4 +317,10 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
}
|
||||
}
|
||||
}
|
||||
.btn-filter-applied {
|
||||
background-color: var(--gray-300) !important;
|
||||
&:hover {
|
||||
background-color: var(--gray-400) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
class="search-input form-control"
|
||||
type="text"
|
||||
:placeholder="__('Search properties...')"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
<span class="search-icon">
|
||||
<div v-html="frappe.utils.icon('search', 'sm')"></div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,87 @@
|
|||
<template>
|
||||
<div
|
||||
class="form-section-container"
|
||||
:style="{ borderBottom: props.section.df.hide_border ? 'none' : '' }"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'form-section',
|
||||
hovered ? 'hovered' : '',
|
||||
store.selected(section.df.name) ? 'selected' : '',
|
||||
]"
|
||||
:title="section.df.fieldname"
|
||||
@click.stop="select_section"
|
||||
@mouseover.stop="hovered = true"
|
||||
@mouseout.stop="hovered = false"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'section-header',
|
||||
section.df.label || section.df.collapsible ? 'has-label' : '',
|
||||
collapsed ? 'collapsed' : '',
|
||||
]"
|
||||
:hidden="!section.df.label && store.read_only"
|
||||
>
|
||||
<div class="section-label">
|
||||
<EditableInput
|
||||
:text="section.df.label"
|
||||
:placeholder="__('Section Title')"
|
||||
v-model="section.df.label"
|
||||
/>
|
||||
<div
|
||||
v-if="section.df.collapsible"
|
||||
class="collapse-indicator"
|
||||
v-html="frappe.utils.icon(collapsed ? 'down' : 'up-line', 'sm')"
|
||||
></div>
|
||||
</div>
|
||||
<Dropdown v-if="!store.read_only" :options="options" @click.stop />
|
||||
</div>
|
||||
<div v-if="section.df.description" class="section-description">
|
||||
{{ section.df.description }}
|
||||
</div>
|
||||
<div
|
||||
class="section-columns"
|
||||
:class="{
|
||||
hidden: section.df.collapsible && collapsed,
|
||||
'has-one-column': section.columns.length === 1,
|
||||
}"
|
||||
>
|
||||
<draggable
|
||||
class="section-columns-container"
|
||||
v-model="section.columns"
|
||||
group="columns"
|
||||
item-key="id"
|
||||
:delay="is_touch_screen_device() ? 200 : 0"
|
||||
:animation="200"
|
||||
:easing="store.get_animation"
|
||||
:disabled="store.read_only"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<Column
|
||||
:section="section"
|
||||
:column="element"
|
||||
:data-is-user-generated="store.is_user_generated_field(element)"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import draggable from "vuedraggable";
|
||||
import Column from "./Column.vue";
|
||||
import EditableInput from "./EditableInput.vue";
|
||||
import Dropdown from "./Dropdown.vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { section_boilerplate, move_children_to_parent, confirm_dialog, is_touch_screen_device } from "../utils";
|
||||
import {
|
||||
section_boilerplate,
|
||||
move_children_to_parent,
|
||||
confirm_dialog,
|
||||
is_touch_screen_device,
|
||||
} from "../utils";
|
||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
||||
|
||||
const props = defineProps(["tab", "section"]);
|
||||
|
|
@ -21,7 +98,9 @@ whenever(Backspace, (value) => {
|
|||
const hovered = ref(false);
|
||||
const collapsed = ref(false);
|
||||
const selected = computed(() => store.selected(props.section.df.name));
|
||||
const column = computed(() => props.section.columns[props.section.columns.length - 1]);
|
||||
|
||||
// section
|
||||
function add_section_above() {
|
||||
let index = props.tab.sections.indexOf(props.section);
|
||||
props.tab.sections.splice(index, 0, section_boilerplate());
|
||||
|
|
@ -97,103 +176,130 @@ function move_sections_to_tab() {
|
|||
// activate tab
|
||||
store.form.active_tab = new_tab;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="form-section-container"
|
||||
:style="{ borderBottom: props.section.df.hide_border ? 'none' : '' }"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'form-section',
|
||||
hovered ? 'hovered' : '',
|
||||
store.selected(section.df.name) ? 'selected' : '',
|
||||
]"
|
||||
:title="section.df.fieldname"
|
||||
@click.stop="select_section"
|
||||
@mouseover.stop="hovered = true"
|
||||
@mouseout.stop="hovered = false"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'section-header',
|
||||
section.df.label || section.df.collapsible ? 'has-label' : '',
|
||||
collapsed ? 'collapsed' : '',
|
||||
]"
|
||||
:hidden="!section.df.label && store.read_only"
|
||||
>
|
||||
<div class="section-label">
|
||||
<EditableInput
|
||||
:text="section.df.label"
|
||||
:placeholder="__('Section Title')"
|
||||
v-model="section.df.label"
|
||||
/>
|
||||
<div
|
||||
v-if="section.df.collapsible"
|
||||
class="collapse-indicator"
|
||||
v-html="frappe.utils.icon(collapsed ? 'down' : 'up-line', 'sm')"
|
||||
></div>
|
||||
</div>
|
||||
<div class="section-actions" :hidden="store.read_only">
|
||||
<button
|
||||
class="btn btn-xs btn-section"
|
||||
:title="__('Add section above')"
|
||||
@click="add_section_above"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('add', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
v-if="tab.sections.indexOf(section)"
|
||||
class="btn btn-xs btn-section"
|
||||
:title="
|
||||
__('Move the current section and the following sections to a new tab')
|
||||
"
|
||||
@click="move_sections_to_tab"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-section"
|
||||
:title="__('Remove section')"
|
||||
@click.stop="remove_section"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="section.df.description" class="section-description">
|
||||
{{ section.df.description }}
|
||||
</div>
|
||||
<div
|
||||
class="section-columns"
|
||||
:class="{
|
||||
hidden: section.df.collapsible && collapsed,
|
||||
'has-one-column': section.columns.length === 1,
|
||||
}"
|
||||
>
|
||||
<draggable
|
||||
class="section-columns-container"
|
||||
v-model="section.columns"
|
||||
group="columns"
|
||||
item-key="id"
|
||||
:delay="is_touch_screen_device() ? 200 : 0"
|
||||
:animation="200"
|
||||
:easing="store.get_animation"
|
||||
:disabled="store.read_only"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<Column
|
||||
:section="section"
|
||||
:column="element"
|
||||
:data-is-user-generated="store.is_user_generated_field(element)"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
// column
|
||||
function add_column() {
|
||||
props.section.columns.push({
|
||||
fields: [],
|
||||
df: store.get_df("Column Break"),
|
||||
});
|
||||
}
|
||||
|
||||
function remove_column() {
|
||||
if (store.is_customize_form && column.value.df.is_custom_field == 0) {
|
||||
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
|
||||
throw "cannot delete standard field";
|
||||
} else if (column.value.fields.length == 0 || store.has_standard_field(column.value)) {
|
||||
delete_column();
|
||||
} else {
|
||||
confirm_dialog(
|
||||
__("Delete Column", null, "Title of confirmation dialog"),
|
||||
__(
|
||||
"Are you sure you want to delete the column? All the fields in the column will be moved to the previous column.",
|
||||
null,
|
||||
"Confirmation dialog message"
|
||||
),
|
||||
() => delete_column(),
|
||||
__("Delete column", null, "Button text"),
|
||||
() => delete_column(true),
|
||||
__("Delete entire column with fields", null, "Button text")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function delete_column(with_children) {
|
||||
// move all fields to previous column
|
||||
let columns = props.section.columns;
|
||||
let index = columns.length - 1;
|
||||
|
||||
if (with_children && index == 0 && columns.length == 1) {
|
||||
if (column.value.fields.length == 0) {
|
||||
frappe.msgprint(__("Section must have at least one column"));
|
||||
throw "section must have at least one column";
|
||||
}
|
||||
|
||||
columns.unshift({
|
||||
df: store.get_df("Column Break"),
|
||||
fields: [],
|
||||
is_first: true,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!with_children) {
|
||||
if (index > 0) {
|
||||
let prev_column = columns[index - 1];
|
||||
prev_column.fields = [...prev_column.fields, ...column.value.fields];
|
||||
} else {
|
||||
if (column.value.fields.length == 0) {
|
||||
// set next column as first column
|
||||
let next_column = columns[index + 1];
|
||||
if (next_column) {
|
||||
next_column.is_first = true;
|
||||
} else {
|
||||
frappe.msgprint(__("Section must have at least one column"));
|
||||
throw "section must have at least one column";
|
||||
}
|
||||
} else {
|
||||
// create a new column if current column has fields and push fields to it
|
||||
columns.unshift({
|
||||
df: store.get_df("Column Break"),
|
||||
fields: column.value.fields,
|
||||
is_first: true,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove column
|
||||
columns.splice(index, 1);
|
||||
store.form.selected_field = null;
|
||||
}
|
||||
|
||||
const options = computed(() => {
|
||||
let groups = [
|
||||
{
|
||||
group: "Section",
|
||||
items: [
|
||||
{ label: "Add section above", onClick: add_section_above },
|
||||
{ label: "Remove section", onClick: remove_section },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Column",
|
||||
items: [{ label: "Add column", onClick: add_column }],
|
||||
},
|
||||
];
|
||||
|
||||
// add remove column option if there are more than one columns
|
||||
if (props.section.columns.length > 1) {
|
||||
groups[1].items.push({
|
||||
label: "Remove column",
|
||||
tooltip: "Remove last column",
|
||||
onClick: remove_column,
|
||||
});
|
||||
} else if (props.section.columns[0].fields.length) {
|
||||
// add remove all fields option if there is only one column and it has fields
|
||||
groups[1].items.push({
|
||||
label: "Empty column",
|
||||
tooltip: "Remove all fields in the column",
|
||||
onClick: () => delete_column(true),
|
||||
});
|
||||
}
|
||||
|
||||
// add move to tab option if the current section is not the first section
|
||||
if (props.tab.sections.indexOf(props.section) > 0) {
|
||||
groups[0].items.push({
|
||||
label: "Move sections to new tab",
|
||||
tooltip: "Move current and all subsequent sections to a new tab",
|
||||
onClick: move_sections_to_tab,
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-section-container {
|
||||
|
|
@ -218,15 +324,11 @@ function move_sections_to_tab() {
|
|||
|
||||
&.hovered,
|
||||
&.selected {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
&.selected .section-header {
|
||||
display: flex;
|
||||
border-color: var(--border-primary);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 0.75rem;
|
||||
|
|
@ -254,16 +356,21 @@ function move_sections_to_tab() {
|
|||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-section {
|
||||
padding: var(--padding-xs);
|
||||
box-shadow: none;
|
||||
// .btn-section {
|
||||
// padding: var(--padding-xs);
|
||||
// box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-light-gray);
|
||||
}
|
||||
// &:hover {
|
||||
// background-color: var(--bg-light-gray);
|
||||
// }
|
||||
// }
|
||||
.btn-section {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const store = useStore();
|
|||
const { Backspace } = useMagicKeys();
|
||||
whenever(Backspace, (value) => {
|
||||
if (value && selected.value && store.not_using_input) {
|
||||
remove_tab(store.current_tab, '', true);
|
||||
remove_tab(store.current_tab, "", true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -45,12 +45,10 @@ function add_new_section() {
|
|||
|
||||
function is_tab_empty(tab) {
|
||||
// check if sections have columns and it contains fields
|
||||
return !tab.sections.some((section) =>
|
||||
section.columns.some((column) => column.fields.length)
|
||||
);
|
||||
return !tab.sections.some((section) => section.columns.some((column) => column.fields.length));
|
||||
}
|
||||
|
||||
function remove_tab(tab, event, force=false) {
|
||||
function remove_tab(tab, event, force = false) {
|
||||
// is remove_tab_btn is not visible then return
|
||||
if (!event?.currentTarget?.offsetParent && !force) return;
|
||||
|
||||
|
|
@ -270,7 +268,7 @@ function delete_tab(tab, with_children) {
|
|||
color: var(--text-color);
|
||||
|
||||
&::before {
|
||||
border-color: var(--primary);
|
||||
border-color: var(--border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ const props = defineProps(["df", "value"]);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="control frappe-control editable"
|
||||
:data-fieldtype="df.fieldtype"
|
||||
>
|
||||
<div class="control frappe-control editable" :data-fieldtype="df.fieldtype">
|
||||
<!-- label -->
|
||||
<div class="field-controls">
|
||||
<h4 v-if="df.fieldtype == 'Heading'">
|
||||
|
|
@ -28,5 +25,4 @@ const props = defineProps(["df", "value"]);
|
|||
h4 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ let slots = useSlots();
|
|||
type="checkbox"
|
||||
:checked="value"
|
||||
:disabled="read_only"
|
||||
@change="event => $emit('update:modelValue', event.target.checked)"
|
||||
@change="(event) => $emit('update:modelValue', event.target.checked)"
|
||||
/>
|
||||
<span class="label-area" :class="{ reqd: df.reqd }">{{ df.label }}</span>
|
||||
</label>
|
||||
|
|
@ -31,7 +31,8 @@ let slots = useSlots();
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
label, input {
|
||||
label,
|
||||
input {
|
||||
margin-bottom: 0 !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,40 +7,36 @@ let emit = defineEmits(["update:modelValue"]);
|
|||
let slots = useSlots();
|
||||
|
||||
let code = ref(null);
|
||||
let code_control = ref(null);
|
||||
let update_control = ref(true);
|
||||
|
||||
let code_control = computed(() => {
|
||||
if (!code.value) return;
|
||||
code.value.innerHTML = "";
|
||||
|
||||
return frappe.ui.form.make_control({
|
||||
parent: code.value,
|
||||
df: {
|
||||
...props.df,
|
||||
fieldtype: "Code",
|
||||
hidden: 0,
|
||||
read_only: props.read_only,
|
||||
change: () => {
|
||||
if (update_control.value) {
|
||||
content.value = code_control.value.get_value();
|
||||
}
|
||||
update_control.value = true;
|
||||
},
|
||||
},
|
||||
value: content.value,
|
||||
disabled: Boolean(slots.label) || props.read_only,
|
||||
render_input: true,
|
||||
only_input: Boolean(slots.label),
|
||||
});
|
||||
});
|
||||
|
||||
let content = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (code.value) code_control.value;
|
||||
if (code.value) {
|
||||
code_control.value = frappe.ui.form.make_control({
|
||||
parent: code.value,
|
||||
df: {
|
||||
...props.df,
|
||||
fieldtype: "Code",
|
||||
hidden: 0,
|
||||
read_only: props.read_only,
|
||||
change: () => {
|
||||
if (update_control.value) {
|
||||
content.value = code_control.value.get_value();
|
||||
}
|
||||
update_control.value = true;
|
||||
},
|
||||
},
|
||||
value: content.value,
|
||||
disabled: Boolean(slots.label) || props.read_only,
|
||||
render_input: true,
|
||||
only_input: Boolean(slots.label),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="control frappe-control"
|
||||
:class="{ editable: slots.label }"
|
||||
>
|
||||
<div class="control frappe-control" :class="{ editable: slots.label }">
|
||||
<!-- label -->
|
||||
<div v-if="slots.label" class="field-controls">
|
||||
<slot name="label" />
|
||||
|
|
@ -49,7 +46,7 @@ if (props.df.fieldtype === "Icon") {
|
|||
type="text"
|
||||
:value="value"
|
||||
:disabled="read_only || df.read_only"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
<input
|
||||
v-if="slots.label && df.fieldtype === 'Barcode'"
|
||||
|
|
|
|||
|
|
@ -13,20 +13,22 @@ function get_options() {
|
|||
|
||||
if (typeof options == "string") {
|
||||
options = options.split("\n") || "";
|
||||
options = options.map(opt => {
|
||||
options = options.map((opt) => {
|
||||
return { label: __(opt), value: opt };
|
||||
});
|
||||
}
|
||||
|
||||
if (options?.length && typeof options[0] == "string") {
|
||||
options = options.map(opt => {
|
||||
options = options.map((opt) => {
|
||||
return { label: __(opt), value: opt };
|
||||
});
|
||||
}
|
||||
|
||||
if (props.df.fieldname == "fieldtype") {
|
||||
if (!in_list(frappe.model.layout_fields, props.modelValue)) {
|
||||
options = options && options.filter(opt => !in_list(frappe.model.layout_fields, opt.value));
|
||||
options =
|
||||
options &&
|
||||
options.filter((opt) => !in_list(frappe.model.layout_fields, opt.value));
|
||||
} else {
|
||||
options = [{ label: __(props.modelValue), value: props.modelValue }];
|
||||
}
|
||||
|
|
@ -56,17 +58,17 @@ let select_control = computed(() => {
|
|||
content.value = select_control.value.get_value();
|
||||
}
|
||||
update_control.value = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
value: content.value,
|
||||
render_input: true,
|
||||
only_input: Boolean(slots.label) || props.no_label
|
||||
only_input: Boolean(slots.label) || props.no_label,
|
||||
});
|
||||
});
|
||||
|
||||
let content = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit("update:modelValue", value)
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -75,15 +77,18 @@ onMounted(() => {
|
|||
|
||||
watch(
|
||||
() => content.value,
|
||||
value => {
|
||||
(value) => {
|
||||
update_control.value = false;
|
||||
select_control.value?.set_value(value);
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => props.df.options, () => {
|
||||
select_control.value;
|
||||
})
|
||||
watch(
|
||||
() => props.df.options,
|
||||
() => {
|
||||
select_control.value;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ let height = computed(() => {
|
|||
type="text"
|
||||
:value="value"
|
||||
:disabled="read_only || df.read_only"
|
||||
@input="event => $emit('update:modelValue', event.target.value)"
|
||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||
/>
|
||||
|
||||
<!-- description -->
|
||||
|
|
|
|||
|
|
@ -201,6 +201,18 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
get_field_data(df)
|
||||
);
|
||||
}
|
||||
|
||||
// check if link_filters format is correct or not
|
||||
|
||||
if (df.link_filters) {
|
||||
try {
|
||||
let link_filters = JSON.parse(df.link_filters);
|
||||
} catch (e) {
|
||||
error_message = __(
|
||||
`Invalid Filter Format. Try using filter icon on the field to set it correctly`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return error_message;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ function open_in_editor(location) {
|
|||
}
|
||||
function error_component(error, i) {
|
||||
let location = data.value.error.errors[i].location;
|
||||
let location_string = `${location.file}:${location.line}:${
|
||||
location.column
|
||||
}`;
|
||||
let location_string = `${location.file}:${location.line}:${location.column}`;
|
||||
let template = error.replace(
|
||||
" > " + location_string,
|
||||
` > <a class="file-link" @click="open">${location_string}</a>`
|
||||
|
|
@ -41,11 +39,11 @@ function error_component(error, i) {
|
|||
methods: {
|
||||
open() {
|
||||
frappe.realtime.emit("open_in_editor", location);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
defineExpose({show, hide});
|
||||
defineExpose({ show, hide });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -58,8 +56,7 @@ defineExpose({show, hide});
|
|||
z-index: 9999;
|
||||
margin: 0;
|
||||
background: rgba(0, 0, 0, 0.66);
|
||||
--monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
|
||||
monospace;
|
||||
--monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
--dim: var(--gray-400);
|
||||
}
|
||||
.window {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="is_shown"
|
||||
class="flex justify-between build-success-message align-center"
|
||||
>
|
||||
<div v-if="is_shown" class="flex justify-between build-success-message align-center">
|
||||
Compiled successfully
|
||||
<a
|
||||
v-if="!live_reload"
|
||||
class="ml-4 text-white underline" href="/" @click.prevent="reload"
|
||||
>
|
||||
<a v-if="!live_reload" class="ml-4 text-white underline" href="/" @click.prevent="reload">
|
||||
Refresh
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -43,7 +37,7 @@ function reload() {
|
|||
window.location.reload();
|
||||
}
|
||||
|
||||
defineExpose({show, hide});
|
||||
defineExpose({ show, hide });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
<template>
|
||||
<div class="file-browser">
|
||||
<div>
|
||||
<a
|
||||
href=""
|
||||
class="text-muted text-medium"
|
||||
@click.prevent="emit('hide-browser')"
|
||||
>
|
||||
<a href="" class="text-muted text-medium" @click.prevent="emit('hide-browser')">
|
||||
{{ __("← Back to upload files") }}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -23,8 +19,8 @@
|
|||
class="tree with-skeleton"
|
||||
:node="node"
|
||||
:selected_node="selected_node"
|
||||
@node-click="n => toggle_node(n)"
|
||||
@load-more="n => load_more(n)"
|
||||
@node-click="(n) => toggle_node(n)"
|
||||
@load-more="(n) => load_more(n)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,7 +44,7 @@ let node = ref({
|
|||
fetching: false,
|
||||
fetched: false,
|
||||
open: false,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
});
|
||||
let selected_node = ref({});
|
||||
let search_text = ref("");
|
||||
|
|
@ -61,16 +57,14 @@ function toggle_node(node) {
|
|||
node.fetching = true;
|
||||
node.children_start = 0;
|
||||
node.children_loading = false;
|
||||
get_files_in_folder(node.value, 0).then(
|
||||
({ files, has_more }) => {
|
||||
node.open = true;
|
||||
node.children = files;
|
||||
node.fetched = true;
|
||||
node.fetching = false;
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
}
|
||||
);
|
||||
get_files_in_folder(node.value, 0).then(({ files, has_more }) => {
|
||||
node.open = true;
|
||||
node.children = files;
|
||||
node.fetched = true;
|
||||
node.fetching = false;
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
});
|
||||
} else {
|
||||
node.open = !node.open;
|
||||
select_node(node);
|
||||
|
|
@ -80,14 +74,12 @@ function load_more(node) {
|
|||
if (node.has_more_children) {
|
||||
let start = node.children_start;
|
||||
node.children_loading = true;
|
||||
get_files_in_folder(node.value, start).then(
|
||||
({ files, has_more }) => {
|
||||
node.children = node.children.concat(files);
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
node.children_loading = false;
|
||||
}
|
||||
);
|
||||
get_files_in_folder(node.value, start).then(({ files, has_more }) => {
|
||||
node.children = node.children.concat(files);
|
||||
node.children_start += page_length.value;
|
||||
node.has_more_children = has_more;
|
||||
node.children_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
function select_node(node) {
|
||||
|
|
@ -100,9 +92,9 @@ function get_files_in_folder(folder, start) {
|
|||
.call("frappe.core.api.file.get_files_in_folder", {
|
||||
folder,
|
||||
start,
|
||||
page_length: page_length.value
|
||||
page_length: page_length.value,
|
||||
})
|
||||
.then(r => {
|
||||
.then((r) => {
|
||||
let { files = [], has_more = false } = r.message || {};
|
||||
files.sort((a, b) => {
|
||||
if (a.is_folder && b.is_folder) {
|
||||
|
|
@ -116,7 +108,7 @@ function get_files_in_folder(folder, start) {
|
|||
}
|
||||
return 0;
|
||||
});
|
||||
files = files.map(file => make_file_node(file));
|
||||
files = files.map((file) => make_file_node(file));
|
||||
return { files, has_more };
|
||||
});
|
||||
}
|
||||
|
|
@ -127,15 +119,12 @@ function search_by_name() {
|
|||
}
|
||||
if (search_text.value.length < 3) return;
|
||||
frappe
|
||||
.call(
|
||||
"frappe.core.api.file.get_files_by_search_text",
|
||||
{
|
||||
text: search_text.value
|
||||
}
|
||||
)
|
||||
.then(r => {
|
||||
.call("frappe.core.api.file.get_files_by_search_text", {
|
||||
text: search_text.value,
|
||||
})
|
||||
.then((r) => {
|
||||
let files = r.message || [];
|
||||
files = files.map(file => make_file_node(file));
|
||||
files = files.map((file) => make_file_node(file));
|
||||
if (!folder_node.value) {
|
||||
folder_node.value = node.value;
|
||||
}
|
||||
|
|
@ -145,7 +134,7 @@ function search_by_name() {
|
|||
children: files,
|
||||
by_search: true,
|
||||
open: true,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -164,7 +153,7 @@ function make_file_node(file) {
|
|||
children_start: 0,
|
||||
open: false,
|
||||
fetching: false,
|
||||
filtered: true
|
||||
filtered: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
<template>
|
||||
<div class="file-preview">
|
||||
<div class="file-icon">
|
||||
<img
|
||||
v-if="is_image"
|
||||
:src="src"
|
||||
:alt="file.name"
|
||||
>
|
||||
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')">
|
||||
</div>
|
||||
<img v-if="is_image" :src="src" :alt="file.name" />
|
||||
<div class="fallback" v-else v-html="frappe.utils.icon('file', 'md')"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
|
|
@ -24,8 +19,20 @@
|
|||
</div>
|
||||
|
||||
<div class="flex config-area">
|
||||
<label v-if="is_optimizable" class="frappe-checkbox"><input type="checkbox" :checked="optimize" @change="emit('toggle_optimize')">{{ __('Optimize') }}</label>
|
||||
<label class="frappe-checkbox"><input type="checkbox" :checked="file.private" @change="emit('toggle_private')">{{ __('Private') }}</label>
|
||||
<label v-if="is_optimizable" class="frappe-checkbox"
|
||||
><input
|
||||
type="checkbox"
|
||||
:checked="optimize"
|
||||
@change="emit('toggle_optimize')"
|
||||
/>{{ __("Optimize") }}</label
|
||||
>
|
||||
<label class="frappe-checkbox"
|
||||
><input
|
||||
type="checkbox"
|
||||
:checked="file.private"
|
||||
@change="emit('toggle_private')"
|
||||
/>{{ __("Private") }}</label
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="file.error_message" class="file-error text-danger">
|
||||
|
|
@ -45,8 +52,18 @@
|
|||
<div v-if="uploaded" v-html="frappe.utils.icon('solid-success', 'lg')"></div>
|
||||
<div v-if="file.failed" v-html="frappe.utils.icon('solid-error', 'lg')"></div>
|
||||
<div class="file-action-buttons">
|
||||
<button v-if="is_cropable" class="btn btn-crop muted" @click="emit('toggle_image_cropper')" v-html="frappe.utils.icon('crop', 'md')"></button>
|
||||
<button v-if="!uploaded && !file.uploading && !file.failed" class="btn muted" @click="emit('remove')" v-html="frappe.utils.icon('delete', 'md')"></button>
|
||||
<button
|
||||
v-if="is_cropable"
|
||||
class="btn btn-crop muted"
|
||||
@click="emit('toggle_image_cropper')"
|
||||
v-html="frappe.utils.icon('crop', 'md')"
|
||||
></button>
|
||||
<button
|
||||
v-if="!uploaded && !file.uploading && !file.failed"
|
||||
class="btn muted"
|
||||
@click="emit('remove')"
|
||||
v-html="frappe.utils.icon('delete', 'md')"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -79,15 +96,20 @@ let uploaded = computed(() => {
|
|||
return props.file.request_succeeded;
|
||||
});
|
||||
let is_image = computed(() => {
|
||||
return props.file.file_obj.type.startsWith('image');
|
||||
return props.file.file_obj.type.startsWith("image");
|
||||
});
|
||||
let is_optimizable = computed(() => {
|
||||
let is_svg = props.file.file_obj.type == 'image/svg+xml';
|
||||
let is_svg = props.file.file_obj.type == "image/svg+xml";
|
||||
return is_image.value && !is_svg && !uploaded.value && !props.file.failed;
|
||||
});
|
||||
let is_cropable = computed(() => {
|
||||
let croppable_types = ['image/jpeg', 'image/png'];
|
||||
return !uploaded.value && !props.file.uploading && !props.file.failed && croppable_types.includes(props.file.file_obj.type);
|
||||
let croppable_types = ["image/jpeg", "image/png"];
|
||||
return (
|
||||
!uploaded.value &&
|
||||
!props.file.uploading &&
|
||||
!props.file.failed &&
|
||||
croppable_types.includes(props.file.file_obj.type)
|
||||
);
|
||||
});
|
||||
let progress = computed(() => {
|
||||
let value = Math.round((props.file.progress * 100) / props.file.total);
|
||||
|
|
@ -102,7 +124,7 @@ onMounted(() => {
|
|||
if (is_image.value) {
|
||||
if (window.FileReader) {
|
||||
let fr = new FileReader();
|
||||
fr.onload = () => src.value = fr.result;
|
||||
fr.onload = () => (src.value = fr.result);
|
||||
fr.readAsDataURL(props.file.file_obj);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="file-uploader"
|
||||
<div
|
||||
class="file-uploader"
|
||||
@dragover.prevent="dragover"
|
||||
@dragleave.prevent="dragleave"
|
||||
@drop.prevent="dropfiles"
|
||||
|
|
@ -10,19 +11,50 @@
|
|||
>
|
||||
<div v-if="!is_dragging">
|
||||
<div class="text-center">
|
||||
{{ __('Drag and drop files here or upload from') }}
|
||||
{{ __("Drag and drop files here or upload from") }}
|
||||
</div>
|
||||
<div class="mt-2 text-center">
|
||||
<button class="btn btn-file-upload" @click="browse_files">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M13.5 22V19" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 22V19" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 22H19.5" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.5 16H22.5" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 8H9C8.17157 8 7.5 8.67157 7.5 9.5V17.5C7.5 18.3284 8.17157 19 9 19H21C21.8284 19 22.5 18.3284 22.5 17.5V9.5C22.5 8.67157 21.8284 8 21 8Z" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M13.5 22V19"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 22V19"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.5 22H19.5"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.5 16H22.5"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M21 8H9C8.17157 8 7.5 8.67157 7.5 9.5V17.5C7.5 18.3284 8.17157 19 9 19H21C21.8284 19 22.5 18.3284 22.5 17.5V9.5C22.5 8.67157 21.8284 8 21 8Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('My Device') }}</div>
|
||||
<div class="mt-1">{{ __("My Device") }}</div>
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
|
|
@ -31,37 +63,105 @@
|
|||
@change="on_file_input"
|
||||
:multiple="allow_multiple"
|
||||
:accept="(restrictions.allowed_file_types || []).join(', ')"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-file-upload"
|
||||
v-if="!disable_file_browser"
|
||||
@click="show_file_browser = true"
|
||||
>
|
||||
<button class="btn btn-file-upload" v-if="!disable_file_browser" @click="show_file_browser = true">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M13.0245 11.5H8C7.72386 11.5 7.5 11.7239 7.5 12V20C7.5 21.1046 8.39543 22 9.5 22H20.5C21.6046 22 22.5 21.1046 22.5 20V14.5C22.5 14.2239 22.2761 14 22 14H15.2169C15.0492 14 14.8926 13.9159 14.8 13.776L13.4414 11.724C13.3488 11.5841 13.1922 11.5 13.0245 11.5Z" stroke="var(--text-color)" stroke-miterlimit="10" stroke-linecap="square"/>
|
||||
<path d="M8.87939 9.5V8.5C8.87939 8.22386 9.10325 8 9.37939 8H20.6208C20.8969 8 21.1208 8.22386 21.1208 8.5V12" stroke="var(--text-color)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M13.0245 11.5H8C7.72386 11.5 7.5 11.7239 7.5 12V20C7.5 21.1046 8.39543 22 9.5 22H20.5C21.6046 22 22.5 21.1046 22.5 20V14.5C22.5 14.2239 22.2761 14 22 14H15.2169C15.0492 14 14.8926 13.9159 14.8 13.776L13.4414 11.724C13.3488 11.5841 13.1922 11.5 13.0245 11.5Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
<path
|
||||
d="M8.87939 9.5V8.5C8.87939 8.22386 9.10325 8 9.37939 8H20.6208C20.8969 8 21.1208 8.22386 21.1208 8.5V12"
|
||||
stroke="var(--text-color)"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Library') }}</div>
|
||||
<div class="mt-1">{{ __("Library") }}</div>
|
||||
</button>
|
||||
<button class="btn btn-file-upload" v-if="allow_web_link" @click="show_web_link = true">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M12.0469 17.9543L17.9558 12.0454" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.8184 11.4547L15.7943 9.47873C16.4212 8.85205 17.2714 8.5 18.1578 8.5C19.0443 8.5 19.8945 8.85205 20.5214 9.47873V9.47873C21.1481 10.1057 21.5001 10.9558 21.5001 11.8423C21.5001 12.7287 21.1481 13.5789 20.5214 14.2058L18.5455 16.1818" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.4547 13.8184L9.47873 15.7943C8.85205 16.4212 8.5 17.2714 8.5 18.1578C8.5 19.0443 8.85205 19.8945 9.47873 20.5214V20.5214C10.1057 21.1481 10.9558 21.5001 11.8423 21.5001C12.7287 21.5001 13.5789 21.1481 14.2058 20.5214L16.1818 18.5455" stroke="var(--text-color)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<button
|
||||
class="btn btn-file-upload"
|
||||
v-if="allow_web_link"
|
||||
@click="show_web_link = true"
|
||||
>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M12.0469 17.9543L17.9558 12.0454"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.8184 11.4547L15.7943 9.47873C16.4212 8.85205 17.2714 8.5 18.1578 8.5C19.0443 8.5 19.8945 8.85205 20.5214 9.47873V9.47873C21.1481 10.1057 21.5001 10.9558 21.5001 11.8423C21.5001 12.7287 21.1481 13.5789 20.5214 14.2058L18.5455 16.1818"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.4547 13.8184L9.47873 15.7943C8.85205 16.4212 8.5 17.2714 8.5 18.1578C8.5 19.0443 8.85205 19.8945 9.47873 20.5214V20.5214C10.1057 21.1481 10.9558 21.5001 11.8423 21.5001C12.7287 21.5001 13.5789 21.1481 14.2058 20.5214L16.1818 18.5455"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Link') }}</div>
|
||||
<div class="mt-1">{{ __("Link") }}</div>
|
||||
</button>
|
||||
<button v-if="allow_take_photo" class="btn btn-file-upload" @click="capture_image">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)"/>
|
||||
<path d="M11.5 10.5H9.5C8.67157 10.5 8 11.1716 8 12V20C8 20.8284 8.67157 21.5 9.5 21.5H20.5C21.3284 21.5 22 20.8284 22 20V12C22 11.1716 21.3284 10.5 20.5 10.5H18.5L17.3 8.9C17.1111 8.64819 16.8148 8.5 16.5 8.5H13.5C13.1852 8.5 12.8889 8.64819 12.7 8.9L11.5 10.5Z" stroke="var(--text-color)" stroke-linejoin="round"/>
|
||||
<circle cx="15" cy="16" r="2.5" stroke="var(--text-color)"/>
|
||||
<button
|
||||
v-if="allow_take_photo"
|
||||
class="btn btn-file-upload"
|
||||
@click="capture_image"
|
||||
>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="15" cy="15" r="15" fill="var(--subtle-fg)" />
|
||||
<path
|
||||
d="M11.5 10.5H9.5C8.67157 10.5 8 11.1716 8 12V20C8 20.8284 8.67157 21.5 9.5 21.5H20.5C21.3284 21.5 22 20.8284 22 20V12C22 11.1716 21.3284 10.5 20.5 10.5H18.5L17.3 8.9C17.1111 8.64819 16.8148 8.5 16.5 8.5H13.5C13.1852 8.5 12.8889 8.64819 12.7 8.9L11.5 10.5Z"
|
||||
stroke="var(--text-color)"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="15" cy="16" r="2.5" stroke="var(--text-color)" />
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Camera') }}</div>
|
||||
<div class="mt-1">{{ __("Camera") }}</div>
|
||||
</button>
|
||||
<button v-if="google_drive_settings.enabled" class="btn btn-file-upload" @click="show_google_drive_picker">
|
||||
<button
|
||||
v-if="google_drive_settings.enabled"
|
||||
class="btn btn-file-upload"
|
||||
@click="show_google_drive_picker"
|
||||
>
|
||||
<svg width="30" height="30">
|
||||
<image href="/assets/frappe/icons/social/google_drive.svg" width="30" height="30"/>
|
||||
<image
|
||||
href="/assets/frappe/icons/social/google_drive.svg"
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
</svg>
|
||||
<div class="mt-1">{{ __('Google Drive') }}</div>
|
||||
<div class="mt-1">{{ __("Google Drive") }}</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-muted text-medium text-center">
|
||||
|
|
@ -69,10 +169,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ __('Drop files here') }}
|
||||
{{ __("Drop files here") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-preview-area" v-show="files.length && !show_file_browser && !show_web_link">
|
||||
<div
|
||||
class="file-preview-area"
|
||||
v-show="files.length && !show_file_browser && !show_web_link"
|
||||
>
|
||||
<div class="file-preview-container" v-if="!show_image_cropper">
|
||||
<FilePreview
|
||||
v-for="(file, i) in files"
|
||||
|
|
@ -85,19 +188,16 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="flex align-center" v-if="show_upload_button && currently_uploading === -1">
|
||||
<button
|
||||
class="btn btn-primary btn-sm margin-right"
|
||||
@click="upload_files"
|
||||
>
|
||||
<button class="btn btn-primary btn-sm margin-right" @click="upload_files">
|
||||
<span v-if="files.length === 1">
|
||||
{{ __('Upload file') }}
|
||||
{{ __("Upload file") }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ __('Upload {0} files', [files.length]) }}
|
||||
{{ __("Upload {0} files", [files.length]) }}
|
||||
</span>
|
||||
</button>
|
||||
<div class="text-muted text-medium">
|
||||
{{ __('Click on the lock icon to toggle public/private') }}
|
||||
{{ __("Click on the lock icon to toggle public/private") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -106,60 +206,56 @@
|
|||
:file="files[crop_image_with_index]"
|
||||
:fixed_aspect_ratio="restrictions.crop_image_aspect_ratio"
|
||||
@toggle_image_cropper="toggle_image_cropper(-1)"
|
||||
@upload_after_crop="trigger_upload=true"
|
||||
@upload_after_crop="trigger_upload = true"
|
||||
/>
|
||||
<FileBrowser
|
||||
ref="file_browser"
|
||||
v-if="show_file_browser && !disable_file_browser"
|
||||
@hide-browser="show_file_browser = false"
|
||||
/>
|
||||
<WebLink
|
||||
ref="web_link"
|
||||
v-if="show_web_link"
|
||||
@hide-web-link="show_web_link = false"
|
||||
/>
|
||||
<WebLink ref="web_link" v-if="show_web_link" @hide-web-link="show_web_link = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import FilePreview from './FilePreview.vue';
|
||||
import FileBrowser from './FileBrowser.vue';
|
||||
import WebLink from './WebLink.vue';
|
||||
import GoogleDrivePicker from '../../integrations/google_drive_picker';
|
||||
import ImageCropper from './ImageCropper.vue';
|
||||
import { computed, ref, watch } from "vue";
|
||||
import FilePreview from "./FilePreview.vue";
|
||||
import FileBrowser from "./FileBrowser.vue";
|
||||
import WebLink from "./WebLink.vue";
|
||||
import GoogleDrivePicker from "../../integrations/google_drive_picker";
|
||||
import ImageCropper from "./ImageCropper.vue";
|
||||
|
||||
// props
|
||||
const props = defineProps({
|
||||
show_upload_button: {
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
disable_file_browser: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
allow_multiple: {
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
as_dataurl: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
doctype: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
docname: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
fieldname: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
folder: {
|
||||
default: 'Home'
|
||||
default: "Home",
|
||||
},
|
||||
method: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
on_success: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
make_attachments_public: {
|
||||
default: null,
|
||||
|
|
@ -169,15 +265,15 @@ const props = defineProps({
|
|||
max_file_size: null, // 2048 -> 2KB
|
||||
max_number_of_files: null,
|
||||
allowed_file_types: [], // ['image/*', 'video/*', '.jpg', '.gif', '.pdf'],
|
||||
crop_image_aspect_ratio: null // 1, 16 / 9, 4 / 3, NaN (free)
|
||||
})
|
||||
crop_image_aspect_ratio: null, // 1, 16 / 9, 4 / 3, NaN (free)
|
||||
}),
|
||||
},
|
||||
attach_doc_image: {
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
upload_notes: {
|
||||
default: null // "Images or video, upto 2MB"
|
||||
}
|
||||
default: null, // "Images or video, upto 2MB"
|
||||
},
|
||||
});
|
||||
|
||||
// variables
|
||||
|
|
@ -197,7 +293,7 @@ let hide_dialog_footer = ref(false);
|
|||
let allow_take_photo = ref(false);
|
||||
let allow_web_link = ref(true);
|
||||
let google_drive_settings = ref({
|
||||
enabled: false
|
||||
enabled: false,
|
||||
});
|
||||
let wrapper_ready = ref(false);
|
||||
|
||||
|
|
@ -211,14 +307,13 @@ if (frappe.user_id !== "Guest") {
|
|||
if (!resp.exc) {
|
||||
google_drive_settings.value = resp.message;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
if (props.restrictions.max_file_size == null) {
|
||||
frappe.call('frappe.core.api.file.get_max_file_size')
|
||||
.then(res => {
|
||||
props.restrictions.max_file_size = Number(res.message);
|
||||
});
|
||||
frappe.call("frappe.core.api.file.get_max_file_size").then((res) => {
|
||||
props.restrictions.max_file_size = Number(res.message);
|
||||
});
|
||||
}
|
||||
if (props.restrictions.max_number_of_files == null && props.doctype) {
|
||||
props.restrictions.max_number_of_files = frappe.get_meta(props.doctype)?.max_attachments;
|
||||
|
|
@ -242,7 +337,7 @@ function on_file_input(e) {
|
|||
add_files(file_input.value.files);
|
||||
}
|
||||
function remove_file(file) {
|
||||
files.value = files.value.filter(f => f !== file);
|
||||
files.value = files.value.filter((f) => f !== file);
|
||||
}
|
||||
function toggle_image_cropper(index) {
|
||||
crop_image_with_index.value = show_image_cropper.value ? -1 : index;
|
||||
|
|
@ -251,7 +346,7 @@ function toggle_image_cropper(index) {
|
|||
}
|
||||
function toggle_all_private() {
|
||||
let flag;
|
||||
let private_values = files.value.filter(file => file.private);
|
||||
let private_values = files.value.filter((file) => file.private);
|
||||
if (private_values.length < files.value.length) {
|
||||
// there are some private and some public
|
||||
// set all to private
|
||||
|
|
@ -260,7 +355,7 @@ function toggle_all_private() {
|
|||
// all are private, set all to public
|
||||
flag = false;
|
||||
}
|
||||
files.value = files.value.map(file => {
|
||||
files.value = files.value.map((file) => {
|
||||
file.private = flag;
|
||||
return file;
|
||||
});
|
||||
|
|
@ -268,12 +363,19 @@ function toggle_all_private() {
|
|||
function show_max_files_number_warning(file) {
|
||||
console.warn(
|
||||
`File skipped because it exceeds the allowed specified limit of ${max_number_of_files} uploads`,
|
||||
file,
|
||||
file
|
||||
);
|
||||
if (props.doctype) {
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [file.name, max_number_of_files, props.doctype])
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [
|
||||
file.name,
|
||||
max_number_of_files,
|
||||
props.doctype,
|
||||
]);
|
||||
} else {
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed', [file.name, max_number_of_files])
|
||||
MSG = __('File "{0}" was skipped because only {1} uploads are allowed', [
|
||||
file.name,
|
||||
max_number_of_files,
|
||||
]);
|
||||
}
|
||||
frappe.show_alert({
|
||||
message: MSG,
|
||||
|
|
@ -283,14 +385,14 @@ function show_max_files_number_warning(file) {
|
|||
function add_files(file_array) {
|
||||
let _files = Array.from(file_array)
|
||||
.filter(check_restrictions)
|
||||
.map(file => {
|
||||
let is_image = file.type.startsWith('image');
|
||||
.map((file) => {
|
||||
let is_image = file.type.startsWith("image");
|
||||
let size_kb = file.size / 1024;
|
||||
return {
|
||||
file_obj: file,
|
||||
cropper_file: file,
|
||||
crop_box_data: null,
|
||||
optimize: size_kb > 200 && is_image && !file.type.includes('svg'),
|
||||
optimize: size_kb > 200 && is_image && !file.type.includes("svg"),
|
||||
name: file.name,
|
||||
doc: null,
|
||||
progress: 0,
|
||||
|
|
@ -306,7 +408,7 @@ function add_files(file_array) {
|
|||
// pop extra files as per FileUploader.restrictions.max_number_of_files
|
||||
max_number_of_files = props.restrictions.max_number_of_files;
|
||||
if (max_number_of_files && _files.length > max_number_of_files) {
|
||||
_files.slice(max_number_of_files).forEach(file => {
|
||||
_files.slice(max_number_of_files).forEach((file) => {
|
||||
show_max_files_number_warning(file, props.doctype);
|
||||
});
|
||||
|
||||
|
|
@ -315,8 +417,12 @@ function add_files(file_array) {
|
|||
|
||||
files.value = files.value.concat(_files);
|
||||
// if only one file is allowed and crop_image_aspect_ratio is set, open cropper immediately
|
||||
if (files.value.length === 1 && !props.allow_multiple && props.restrictions.crop_image_aspect_ratio != null) {
|
||||
if (!files.value[0].file_obj.type.includes('svg')) {
|
||||
if (
|
||||
files.value.length === 1 &&
|
||||
!props.allow_multiple &&
|
||||
props.restrictions.crop_image_aspect_ratio != null
|
||||
) {
|
||||
if (!files.value[0].file_obj.type.includes("svg")) {
|
||||
toggle_image_cropper(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -330,14 +436,14 @@ function check_restrictions(file) {
|
|||
if (allowed_file_types && allowed_file_types.length) {
|
||||
is_correct_type = allowed_file_types.some((type) => {
|
||||
// is this is a mime-type
|
||||
if (type.includes('/')) {
|
||||
if (type.includes("/")) {
|
||||
if (!file.type) return false;
|
||||
return file.type.match(type);
|
||||
}
|
||||
|
||||
// otherwise this is likely an extension
|
||||
if (type[0] === '.') {
|
||||
return file.name.endsWith(type);
|
||||
if (type[0] === ".") {
|
||||
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
@ -348,17 +454,20 @@ function check_restrictions(file) {
|
|||
}
|
||||
|
||||
if (!is_correct_type) {
|
||||
console.warn('File skipped because of invalid file type', file);
|
||||
console.warn("File skipped because of invalid file type", file);
|
||||
frappe.show_alert({
|
||||
message: __('File "{0}" was skipped because of invalid file type', [file.name]),
|
||||
indicator: 'orange'
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
if (!valid_file_size) {
|
||||
console.warn('File skipped because of invalid file size', file.size, file);
|
||||
console.warn("File skipped because of invalid file size", file.size, file);
|
||||
frappe.show_alert({
|
||||
message: __('File "{0}" was skipped because size exceeds {1} MB', [file.name, max_file_size / (1024 * 1024)]),
|
||||
indicator: 'orange'
|
||||
message: __('File "{0}" was skipped because size exceeds {1} MB', [
|
||||
file.name,
|
||||
max_file_size / (1024 * 1024),
|
||||
]),
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -374,17 +483,12 @@ function upload_files() {
|
|||
if (props.as_dataurl) {
|
||||
return return_as_dataurl();
|
||||
}
|
||||
return frappe.run_serially(
|
||||
files.value.map(
|
||||
(file, i) =>
|
||||
() => upload_file(file, i)
|
||||
)
|
||||
);
|
||||
return frappe.run_serially(files.value.map((file, i) => () => upload_file(file, i)));
|
||||
}
|
||||
function upload_via_file_browser() {
|
||||
let selected_file = file_browser.value.selected_node;
|
||||
if (!selected_file.value) {
|
||||
frappe.msgprint(__('Click on a file to select it.'));
|
||||
frappe.msgprint(__("Click on a file to select it."));
|
||||
close_dialog.value = true;
|
||||
return Promise.reject();
|
||||
}
|
||||
|
|
@ -396,23 +500,22 @@ function upload_via_file_browser() {
|
|||
function upload_via_web_link() {
|
||||
let file_url = web_link.value.url;
|
||||
if (!file_url) {
|
||||
frappe.msgprint(__('Invalid URL'));
|
||||
frappe.msgprint(__("Invalid URL"));
|
||||
close_dialog.value = true;
|
||||
return Promise.reject();
|
||||
}
|
||||
file_url = decodeURI(file_url)
|
||||
file_url = decodeURI(file_url);
|
||||
close_dialog.value = true;
|
||||
return upload_file({
|
||||
file_url
|
||||
file_url,
|
||||
});
|
||||
}
|
||||
function return_as_dataurl() {
|
||||
let promises = files.value.map(file =>
|
||||
frappe.dom.file_to_base64(file.file_obj)
|
||||
.then(dataurl => {
|
||||
file.dataurl = dataurl;
|
||||
props.on_success && props.on_success(file);
|
||||
})
|
||||
let promises = files.value.map((file) =>
|
||||
frappe.dom.file_to_base64(file.file_obj).then((dataurl) => {
|
||||
file.dataurl = dataurl;
|
||||
props.on_success && props.on_success(file);
|
||||
})
|
||||
);
|
||||
close_dialog.value = true;
|
||||
return Promise.all(promises);
|
||||
|
|
@ -422,23 +525,23 @@ function upload_file(file, i) {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.upload.addEventListener('loadstart', (e) => {
|
||||
xhr.upload.addEventListener("loadstart", (e) => {
|
||||
file.uploading = true;
|
||||
})
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
});
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
file.progress = e.loaded;
|
||||
file.total = e.total;
|
||||
}
|
||||
})
|
||||
xhr.upload.addEventListener('load', (e) => {
|
||||
});
|
||||
xhr.upload.addEventListener("load", (e) => {
|
||||
file.uploading = false;
|
||||
resolve();
|
||||
})
|
||||
xhr.addEventListener('error', (e) => {
|
||||
});
|
||||
xhr.addEventListener("error", (e) => {
|
||||
file.failed = true;
|
||||
reject();
|
||||
})
|
||||
});
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
|
|
@ -447,10 +550,10 @@ function upload_file(file, i) {
|
|||
let file_doc = null;
|
||||
try {
|
||||
r = JSON.parse(xhr.responseText);
|
||||
if (r.message.doctype === 'File') {
|
||||
if (r.message.doctype === "File") {
|
||||
file_doc = r.message;
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
r = xhr.responseText;
|
||||
}
|
||||
|
||||
|
|
@ -460,14 +563,16 @@ function upload_file(file, i) {
|
|||
props.on_success(file_doc, r);
|
||||
}
|
||||
|
||||
if (i == files.value.length - 1 && files.value.every(file => file.request_succeeded)) {
|
||||
if (
|
||||
i == files.value.length - 1 &&
|
||||
files.value.every((file) => file.request_succeeded)
|
||||
) {
|
||||
close_dialog.value = true;
|
||||
}
|
||||
|
||||
} else if (xhr.status === 403) {
|
||||
file.failed = true;
|
||||
let response = JSON.parse(xhr.responseText);
|
||||
file.error_message = `Not permitted. ${response._error_message || ''}.`;
|
||||
file.error_message = `Not permitted. ${response._error_message || ""}.`;
|
||||
|
||||
try {
|
||||
// Append server messages which are useful hint for perm issues
|
||||
|
|
@ -475,73 +580,73 @@ function upload_file(file, i) {
|
|||
|
||||
server_messages.forEach((m) => {
|
||||
m = JSON.parse(m);
|
||||
file.error_message += `\n ${m.message} `
|
||||
})
|
||||
file.error_message += `\n ${m.message} `;
|
||||
});
|
||||
} catch (e) {
|
||||
console.warning("Failed to parse server message", e)
|
||||
console.warning("Failed to parse server message", e);
|
||||
}
|
||||
|
||||
|
||||
} else if (xhr.status === 413) {
|
||||
file.failed = true;
|
||||
file.error_message = 'Size exceeds the maximum allowed file size.';
|
||||
|
||||
file.error_message = "Size exceeds the maximum allowed file size.";
|
||||
} else {
|
||||
file.failed = true;
|
||||
file.error_message = xhr.status === 0 ? 'XMLHttpRequest Error' : `${xhr.status} : ${xhr.statusText}`;
|
||||
file.error_message =
|
||||
xhr.status === 0
|
||||
? "XMLHttpRequest Error"
|
||||
: `${xhr.status} : ${xhr.statusText}`;
|
||||
|
||||
let error = null;
|
||||
try {
|
||||
error = JSON.parse(xhr.responseText);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
frappe.request.cleanup({}, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.open('POST', '/api/method/upload_file', true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('X-Frappe-CSRF-Token', frappe.csrf_token);
|
||||
};
|
||||
xhr.open("POST", "/api/method/upload_file", true);
|
||||
xhr.setRequestHeader("Accept", "application/json");
|
||||
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
|
||||
|
||||
let form_data = new FormData();
|
||||
if (file.file_obj) {
|
||||
form_data.append('file', file.file_obj, file.name);
|
||||
form_data.append("file", file.file_obj, file.name);
|
||||
}
|
||||
form_data.append('is_private', +file.private);
|
||||
form_data.append('folder', props.folder);
|
||||
form_data.append("is_private", +file.private);
|
||||
form_data.append("folder", props.folder);
|
||||
|
||||
if (file.file_url) {
|
||||
form_data.append('file_url', file.file_url);
|
||||
form_data.append("file_url", file.file_url);
|
||||
}
|
||||
|
||||
if (file.file_name) {
|
||||
form_data.append('file_name', file.file_name);
|
||||
form_data.append("file_name", file.file_name);
|
||||
}
|
||||
if (file.library_file_name) {
|
||||
form_data.append('library_file_name', file.library_file_name);
|
||||
form_data.append("library_file_name", file.library_file_name);
|
||||
}
|
||||
|
||||
if (props.doctype && props.docname) {
|
||||
form_data.append('doctype', props.doctype);
|
||||
form_data.append('docname', props.docname);
|
||||
form_data.append("doctype", props.doctype);
|
||||
form_data.append("docname", props.docname);
|
||||
}
|
||||
|
||||
if (props.fieldname) {
|
||||
form_data.append('fieldname', props.fieldname);
|
||||
form_data.append("fieldname", props.fieldname);
|
||||
}
|
||||
|
||||
if (props.method) {
|
||||
form_data.append('method', props.method);
|
||||
form_data.append("method", props.method);
|
||||
}
|
||||
|
||||
if (file.optimize) {
|
||||
form_data.append('optimize', true);
|
||||
form_data.append("optimize", true);
|
||||
}
|
||||
|
||||
if (props.attach_doc_image) {
|
||||
form_data.append('max_width', 200);
|
||||
form_data.append('max_height', 200);
|
||||
form_data.append("max_width", 200);
|
||||
form_data.append("max_height", 200);
|
||||
}
|
||||
|
||||
xhr.send(form_data);
|
||||
|
|
@ -550,23 +655,23 @@ function upload_file(file, i) {
|
|||
function capture_image() {
|
||||
const capture = new frappe.ui.Capture({
|
||||
animate: false,
|
||||
error: true
|
||||
error: true,
|
||||
});
|
||||
capture.show();
|
||||
capture.submit(data_urls => {
|
||||
data_urls.forEach(data_url => {
|
||||
let filename = `capture_${frappe.datetime.now_datetime().replaceAll(/[: -]/g, '_')}.png`;
|
||||
url_to_file(data_url, filename, 'image/png').then((file) =>
|
||||
add_files([file])
|
||||
);
|
||||
capture.submit((data_urls) => {
|
||||
data_urls.forEach((data_url) => {
|
||||
let filename = `capture_${frappe.datetime
|
||||
.now_datetime()
|
||||
.replaceAll(/[: -]/g, "_")}.png`;
|
||||
url_to_file(data_url, filename, "image/png").then((file) => add_files([file]));
|
||||
});
|
||||
});
|
||||
}
|
||||
function show_google_drive_picker() {
|
||||
close_dialog.value = true;
|
||||
let google_drive = new GoogleDrivePicker({
|
||||
pickerCallback: data => google_drive_callback(data),
|
||||
...google_drive_settings.value
|
||||
pickerCallback: (data) => google_drive_callback(data),
|
||||
...google_drive_settings.value,
|
||||
});
|
||||
google_drive.loadPicker();
|
||||
}
|
||||
|
|
@ -574,31 +679,36 @@ function google_drive_callback(data) {
|
|||
if (data.action == google.picker.Action.PICKED) {
|
||||
upload_file({
|
||||
file_url: data.docs[0].url,
|
||||
file_name: data.docs[0].name
|
||||
file_name: data.docs[0].name,
|
||||
});
|
||||
} else if (data.action == google.picker.Action.CANCEL) {
|
||||
cur_frm.attachments.new_attachment()
|
||||
cur_frm.attachments.new_attachment();
|
||||
}
|
||||
}
|
||||
function url_to_file(url, filename, mime_type) {
|
||||
return fetch(url)
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(buffer => new File([buffer], filename, { type: mime_type }));
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => new File([buffer], filename, { type: mime_type }));
|
||||
}
|
||||
|
||||
// computed
|
||||
let upload_complete = computed(() => {
|
||||
return files.value.length > 0
|
||||
&& files.value.every(
|
||||
file => file.total !== 0 && file.progress === file.total);
|
||||
return (
|
||||
files.value.length > 0 &&
|
||||
files.value.every((file) => file.total !== 0 && file.progress === file.total)
|
||||
);
|
||||
});
|
||||
|
||||
// watcher
|
||||
watch(files, (newvalue, oldvalue) => {
|
||||
if (!props.allow_multiple && newvalue.length > 1) {
|
||||
files.value = [newvalue[newvalue.length - 1]];
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(
|
||||
files,
|
||||
(newvalue, oldvalue) => {
|
||||
if (!props.allow_multiple && newvalue.length > 1) {
|
||||
files.value = [newvalue[newvalue.length - 1]];
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
files,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
:class="{
|
||||
active: isNaN(aspect_ratio)
|
||||
? isNaN(button.value)
|
||||
: button.value === aspect_ratio
|
||||
: button.value === aspect_ratio,
|
||||
}"
|
||||
:key="button.label"
|
||||
@click="aspect_ratio = button.value"
|
||||
|
|
@ -63,9 +63,9 @@ function crop_image() {
|
|||
props.file.crop_box_data = cropper.value.getData();
|
||||
const canvas = cropper.value.getCroppedCanvas();
|
||||
const file_type = props.file.file_obj.type;
|
||||
canvas.toBlob(blob => {
|
||||
canvas.toBlob((blob) => {
|
||||
var cropped_file_obj = new File([blob], props.file.name, {
|
||||
type: blob.type
|
||||
type: blob.type,
|
||||
});
|
||||
props.file.file_obj = cropped_file_obj;
|
||||
emit("toggle_image_cropper");
|
||||
|
|
@ -87,7 +87,7 @@ onMounted(() => {
|
|||
scalable: false,
|
||||
viewMode: 1,
|
||||
data: crop_box,
|
||||
aspectRatio: aspect_ratio.value
|
||||
aspectRatio: aspect_ratio.value,
|
||||
});
|
||||
window.cropper = cropper.value;
|
||||
};
|
||||
|
|
@ -98,30 +98,33 @@ let aspect_ratio_buttons = computed(() => {
|
|||
return [
|
||||
{
|
||||
label: __("1:1"),
|
||||
value: 1
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: __("4:3"),
|
||||
value: 4 / 3
|
||||
value: 4 / 3,
|
||||
},
|
||||
{
|
||||
label: __("16:9"),
|
||||
value: 16 / 9
|
||||
value: 16 / 9,
|
||||
},
|
||||
{
|
||||
label: __("Free"),
|
||||
value: NaN
|
||||
}
|
||||
value: NaN,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// watcher
|
||||
watch(aspect_ratio, (value) => {
|
||||
if (cropper.value) {
|
||||
cropper.value.setAspectRatio(value);
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
watch(
|
||||
aspect_ratio,
|
||||
(value) => {
|
||||
if (cropper.value) {
|
||||
cropper.value.setAspectRatio(value);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
:stroke-dasharray="circumference + ' ' + circumference"
|
||||
:style="{
|
||||
stroke: secondary,
|
||||
strokeDashoffset: 0
|
||||
strokeDashoffset: 0,
|
||||
}"
|
||||
:stroke-width="stroke"
|
||||
fill="transparent"
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
:stroke-dasharray="circumference + ' ' + circumference"
|
||||
:style="{
|
||||
stroke: primary,
|
||||
strokeDashoffset: strokeDashoffset
|
||||
strokeDashoffset: strokeDashoffset,
|
||||
}"
|
||||
:stroke-width="stroke"
|
||||
fill="transparent"
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
:style="{
|
||||
color: 'var(--text-color)',
|
||||
fontSize: 'var(--text-xs)',
|
||||
fontWeight: 'var(--text-bold)'
|
||||
fontWeight: 'var(--text-bold)',
|
||||
}"
|
||||
>
|
||||
{{ progress }}%
|
||||
|
|
@ -49,7 +49,7 @@ const props = defineProps({
|
|||
secondary: String,
|
||||
radius: Number,
|
||||
progress: Number,
|
||||
stroke: Number
|
||||
stroke: Number,
|
||||
});
|
||||
|
||||
// variables
|
||||
|
|
@ -60,7 +60,6 @@ let circumference = ref(normalizedRadius.value * 2 * Math.PI);
|
|||
let strokeDashoffset = computed(() => {
|
||||
return circumference.value - (props.progress / 100) * circumference.value;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<div class="file-web-link margin-bottom">
|
||||
<a href class="text-muted text-medium"
|
||||
@click.prevent="emit('hide-web-link')"
|
||||
>
|
||||
{{ __('← Back to upload files') }}
|
||||
<a href class="text-muted text-medium" @click.prevent="emit('hide-web-link')">
|
||||
{{ __("← Back to upload files") }}
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input
|
||||
|
|
@ -11,7 +9,7 @@
|
|||
class="form-control"
|
||||
:placeholder="__('Attach a web link')"
|
||||
v-model="url"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,4 @@
|
|||
frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlInt {
|
||||
make_input() {
|
||||
super.make_input();
|
||||
const change_handler = (e) => {
|
||||
if (this.change) this.change(e);
|
||||
else {
|
||||
let value = this.get_input_value();
|
||||
this.parse_validate_and_set_in_model(value, e);
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
// convert to number format on focusout since focus converts it to flt.
|
||||
this.$input.on("focusout", change_handler);
|
||||
}
|
||||
parse(value) {
|
||||
value = this.eval_expression(value);
|
||||
return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision());
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData
|
|||
.on("focus", function () {
|
||||
setTimeout(function () {
|
||||
if (!document.activeElement) return;
|
||||
document.activeElement.value = me.validate(document.activeElement.value);
|
||||
document.activeElement.select();
|
||||
}, 100);
|
||||
return false;
|
||||
|
|
@ -24,10 +23,19 @@ frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData
|
|||
}
|
||||
eval_expression(value) {
|
||||
if (typeof value === "string") {
|
||||
if (value.match(/^[0-9+\-/* ]+$/)) {
|
||||
const parsed_components = value.match(/[^\d.,]+|[\d.,]+/g);
|
||||
var parsed_value = value;
|
||||
if (parsed_components !== null) {
|
||||
parsed_value = parsed_components
|
||||
.map((v) => {
|
||||
return isNaN(parseFloat(v)) ? v : flt(v);
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
if (parsed_value.match(/^[0-9+\-/*.() ]+$/)) {
|
||||
// If it is a string containing operators
|
||||
try {
|
||||
return eval(value);
|
||||
return eval(parsed_value);
|
||||
} catch (e) {
|
||||
// bad expression
|
||||
return value;
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
|
||||
// 2 column layout
|
||||
this.setup_std_layout();
|
||||
this.setup_filters();
|
||||
|
||||
// client script must be called after "setup" - there are no fields_dict attached to the frm otherwise
|
||||
this.script_manager = new frappe.ui.form.ScriptManager({
|
||||
|
|
@ -272,6 +273,41 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
});
|
||||
}
|
||||
|
||||
setup_filters() {
|
||||
let fields_with_filters = frappe
|
||||
.get_meta(this.doctype)
|
||||
.fields.filter((field) => field.link_filters)
|
||||
.map((field) => JSON.parse(field.link_filters));
|
||||
if (fields_with_filters.length === 0) return;
|
||||
fields_with_filters = this.parse_filters(fields_with_filters);
|
||||
for (let link_field in fields_with_filters) {
|
||||
const filters = fields_with_filters[link_field];
|
||||
this.set_query(link_field, () => filters);
|
||||
}
|
||||
}
|
||||
|
||||
parse_filters(data) {
|
||||
const parsed_data = {};
|
||||
|
||||
for (const d of data) {
|
||||
for (const condition of d) {
|
||||
let [doctype, field, operator, value] = condition;
|
||||
doctype = doctype.fieldname;
|
||||
if (!parsed_data[doctype]) {
|
||||
parsed_data[doctype] = {
|
||||
filters: {},
|
||||
};
|
||||
}
|
||||
|
||||
if (!parsed_data[doctype].filters[field]) {
|
||||
parsed_data[doctype].filters[field] = [operator, value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parsed_data;
|
||||
}
|
||||
|
||||
watch_model_updates() {
|
||||
// watch model updates
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -1520,9 +1520,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
if (this.filter_area.is_being_edited()) {
|
||||
return true;
|
||||
}
|
||||
// this is set when a bulk operation is called from a list view which might update the list view
|
||||
// this is to avoid the list view from refreshing a lot of times
|
||||
// the list view is updated once after the bulk operation is complete
|
||||
// this flag is left for backward compatibility, there's no need to prevent realtime
|
||||
// refresh. They are by default debounced now and there's no way to bypass that.
|
||||
if (this.disable_list_update) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1673,7 +1672,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
},
|
||||
standard: true,
|
||||
shortcut: "Ctrl+J",
|
||||
shortcut: "Ctrl+Y",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1724,7 +1723,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
get_workflow_action_menu_items() {
|
||||
const workflow_actions = [];
|
||||
const me = this;
|
||||
|
||||
if (frappe.model.has_workflow(this.doctype)) {
|
||||
const actions = frappe.workflow.get_all_transition_actions(this.doctype);
|
||||
|
|
@ -1733,16 +1731,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
label: __(action),
|
||||
name: action,
|
||||
action: () => {
|
||||
me.disable_list_update = true;
|
||||
frappe
|
||||
.xcall("frappe.model.workflow.bulk_workflow_approval", {
|
||||
docnames: this.get_checked_items(true),
|
||||
doctype: this.doctype,
|
||||
action: action,
|
||||
})
|
||||
.finally(() => {
|
||||
me.disable_list_update = false;
|
||||
});
|
||||
frappe.xcall("frappe.model.workflow.bulk_workflow_approval", {
|
||||
docnames: this.get_checked_items(true),
|
||||
doctype: this.doctype,
|
||||
action: action,
|
||||
});
|
||||
},
|
||||
is_workflow_action: true,
|
||||
});
|
||||
|
|
@ -1803,9 +1796,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return {
|
||||
label: __("Assign To", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.assign(this.get_checked_items(true), () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1818,9 +1809,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return {
|
||||
label: __("Apply Assignment Rule", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.apply_assignment_rule(this.get_checked_items(true), () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1833,9 +1822,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return {
|
||||
label: __("Add Tags", null, "Button in list view actions menu"),
|
||||
action: () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.add_tags(this.get_checked_items(true), () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1872,9 +1859,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
);
|
||||
}
|
||||
frappe.confirm(message, () => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.delete(docnames, () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1897,9 +1882,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
"Title of confirmation dialog"
|
||||
),
|
||||
() => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.submit_or_cancel(docnames, "cancel", () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1924,9 +1907,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
"Title of confirmation dialog"
|
||||
),
|
||||
() => {
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.submit_or_cancel(docnames, "submit", () => {
|
||||
this.disable_list_update = false;
|
||||
this.clear_checked_items();
|
||||
this.refresh();
|
||||
});
|
||||
|
|
@ -1950,9 +1931,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
});
|
||||
|
||||
this.disable_list_update = true;
|
||||
bulk_operations.edit(this.get_checked_items(true), field_mappings, () => {
|
||||
this.disable_list_update = false;
|
||||
this.refresh();
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
frappe.ui.FilterGroup = class {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.filters = [];
|
||||
this.filters = this.filters || [];
|
||||
window.fltr = this;
|
||||
if (!this.filter_button) {
|
||||
this.wrapper = this.parent;
|
||||
|
|
@ -239,6 +239,7 @@ frappe.ui.FilterGroup = class {
|
|||
},
|
||||
filter_list: this.base_list || this,
|
||||
};
|
||||
|
||||
let filter = new frappe.ui.Filter(args);
|
||||
this.filters.push(filter);
|
||||
return filter;
|
||||
|
|
|
|||
|
|
@ -600,40 +600,11 @@ frappe.search.utils = {
|
|||
return { score, marked_string };
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated Use frappe.search.utils.fuzzy_search(subseq, str, true).marked_string instead.
|
||||
*/
|
||||
bolden_match_part: function (str, subseq) {
|
||||
if (fuzzy_match(subseq, str)[0] === false) {
|
||||
return str;
|
||||
}
|
||||
if (str.indexOf(subseq) == 0) {
|
||||
var tail = str.split(subseq)[1];
|
||||
return "<mark>" + subseq + "</mark>" + tail;
|
||||
}
|
||||
var rendered = "";
|
||||
var str_orig = str;
|
||||
var str_len = str.length;
|
||||
str = str.toLowerCase();
|
||||
subseq = subseq.toLowerCase();
|
||||
|
||||
outer: for (var i = 0, j = 0; i < subseq.length; i++) {
|
||||
var sub_ch = subseq.charCodeAt(i);
|
||||
while (j < str_len) {
|
||||
if (str.charCodeAt(j) === sub_ch) {
|
||||
var str_char = str_orig.charAt(j);
|
||||
if (str_char === str_char.toLowerCase()) {
|
||||
rendered += "<mark>" + subseq.charAt(i) + "</mark>";
|
||||
} else {
|
||||
rendered += "<mark>" + subseq.charAt(i).toUpperCase() + "</mark>";
|
||||
}
|
||||
j++;
|
||||
continue outer;
|
||||
}
|
||||
rendered += str_orig.charAt(j);
|
||||
j++;
|
||||
}
|
||||
return str_orig;
|
||||
}
|
||||
rendered += str_orig.slice(j);
|
||||
return rendered;
|
||||
return this.fuzzy_search(subseq, str, true).marked_string;
|
||||
},
|
||||
|
||||
get_executables(keywords) {
|
||||
|
|
|
|||
|
|
@ -13,15 +13,14 @@ frappe.tags.utils = {
|
|||
return [];
|
||||
}
|
||||
|
||||
for (let i in frappe.tags.tags) {
|
||||
let tag = frappe.tags.tags[i];
|
||||
let level = frappe.search.utils.fuzzy_search(txt, tag);
|
||||
if (level) {
|
||||
frappe.tags.tags.forEach((tag) => {
|
||||
const search_result = frappe.search.utils.fuzzy_search(txt, tag, true);
|
||||
if (search_result.score) {
|
||||
out.push({
|
||||
type: "Tag",
|
||||
label: __("#{0}", [frappe.search.utils.bolden_match_part(__(tag), txt)]),
|
||||
label: __("#{0}", [search_result.marked_string]),
|
||||
value: __("#{0}", [__(tag)]),
|
||||
index: 1 + level,
|
||||
index: 1 + search_result.score,
|
||||
match: tag,
|
||||
onclick() {
|
||||
// Use Global Search Dialog for tag search too.
|
||||
|
|
@ -29,8 +28,7 @@ frappe.tags.utils = {
|
|||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return out;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ frappe.views.Workspace = class Workspace {
|
|||
) {
|
||||
default_page = {
|
||||
name: localStorage.current_page,
|
||||
public: localStorage.is_current_page_public == "true",
|
||||
public: localStorage.is_current_page_public != "false",
|
||||
};
|
||||
} else if (Object.keys(this.all_pages).length !== 0) {
|
||||
default_page = { name: this.all_pages[0].title, public: this.all_pages[0].public };
|
||||
|
|
@ -616,6 +616,8 @@ frappe.views.Workspace = class Workspace {
|
|||
"options",
|
||||
this.get_value() ? me.public_parent_pages : me.private_parent_pages
|
||||
);
|
||||
d.set_df_property("icon", "hidden", this.get_value() ? 0 : 1);
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -625,24 +627,23 @@ frappe.views.Workspace = class Workspace {
|
|||
label: __("Icon"),
|
||||
fieldtype: "Icon",
|
||||
fieldname: "icon",
|
||||
default: item.icon,
|
||||
change: function () {
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
default: item.public && item.icon,
|
||||
hidden: !item.public,
|
||||
},
|
||||
{
|
||||
label: __("Indicator color"),
|
||||
fieldtype: "Select",
|
||||
fieldname: "indicator_color",
|
||||
options: this.indicator_colors,
|
||||
default: item.indicator_color,
|
||||
default: !item.public && item.indicator_color,
|
||||
hidden: item.public,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Update"),
|
||||
primary_action: (values) => {
|
||||
values.title = frappe.utils.escape_html(values.title);
|
||||
let is_title_changed = values.title != old_item.title;
|
||||
let is_section_changed = values.is_public != old_item.public;
|
||||
let is_section_changed = Boolean(values.is_public) != Boolean(old_item.public);
|
||||
if (
|
||||
(is_title_changed || is_section_changed) &&
|
||||
!this.validate_page(values, old_item)
|
||||
|
|
@ -943,6 +944,8 @@ frappe.views.Workspace = class Workspace {
|
|||
"options",
|
||||
this.get_value() ? me.public_parent_pages : me.private_parent_pages
|
||||
);
|
||||
d.set_df_property("icon", "hidden", this.get_value() ? 0 : 1);
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -952,17 +955,16 @@ frappe.views.Workspace = class Workspace {
|
|||
label: __("Icon"),
|
||||
fieldtype: "Icon",
|
||||
fieldname: "icon",
|
||||
default: new_page.icon,
|
||||
change: function () {
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
default: new_page.public && new_page.icon,
|
||||
hidden: !new_page.public,
|
||||
},
|
||||
{
|
||||
label: __("Indicator color"),
|
||||
fieldtype: "Select",
|
||||
fieldname: "indicator_color",
|
||||
options: this.indicator_colors,
|
||||
default: new_page.indicator_color,
|
||||
hidden: new_page.public,
|
||||
default: !new_page.public && new_page.indicator_color,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Duplicate"),
|
||||
|
|
@ -1187,6 +1189,8 @@ frappe.views.Workspace = class Workspace {
|
|||
"options",
|
||||
this.get_value() ? me.public_parent_pages : me.private_parent_pages
|
||||
);
|
||||
d.set_df_property("icon", "hidden", this.get_value() ? 0 : 1);
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -1196,9 +1200,7 @@ frappe.views.Workspace = class Workspace {
|
|||
label: __("Icon"),
|
||||
fieldtype: "Icon",
|
||||
fieldname: "icon",
|
||||
change: function () {
|
||||
d.set_df_property("indicator_color", "hidden", this.get_value() ? 1 : 0);
|
||||
},
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
label: __("Indicator color"),
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@
|
|||
handle=".icon-drag"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="mt-2 row align-center column-row"
|
||||
v-for="column in df.table_columns"
|
||||
>
|
||||
<div class="mt-2 row align-center column-row" v-for="column in df.table_columns">
|
||||
<div class="col-8">
|
||||
<div class="column-label d-flex align-center">
|
||||
<div class="px-2 icon-drag ml-n2">
|
||||
|
|
@ -50,10 +47,7 @@
|
|||
max="100"
|
||||
step="5"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 btn btn-xs btn-icon"
|
||||
@click="remove_column(column)"
|
||||
>
|
||||
<button class="ml-2 btn btn-xs btn-icon" @click="remove_column(column)">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
|
|
@ -74,7 +68,7 @@ const props = defineProps(["df"]);
|
|||
|
||||
// methods
|
||||
function remove_column(column) {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(_column => _column !== column)
|
||||
props.df["table_columns"] = props.df.table_columns.filter((_column) => _column !== column);
|
||||
}
|
||||
// computed
|
||||
let help_message = computed(() => {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@
|
|||
v-if="df.fieldtype == 'HTML' && df.html"
|
||||
v-html="df.html"
|
||||
></div>
|
||||
<div
|
||||
class="custom-html"
|
||||
v-if="df.fieldtype == 'Field Template'"
|
||||
>
|
||||
<div class="custom-html" v-if="df.fieldtype == 'Field Template'">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<input
|
||||
|
|
@ -24,9 +21,7 @@
|
|||
@blur="editing = false"
|
||||
/>
|
||||
<span v-else-if="df.label">{{ df.label }}</span>
|
||||
<i class="text-muted" v-else>
|
||||
{{ __("No Label") }} ({{ df.fieldname }})
|
||||
</i>
|
||||
<i class="text-muted" v-else> {{ __("No Label") }} ({{ df.fieldname }}) </i>
|
||||
</div>
|
||||
<div class="field-actions">
|
||||
<button
|
||||
|
|
@ -45,10 +40,7 @@
|
|||
>
|
||||
Configure columns
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
@click="df['remove'] = true"
|
||||
>
|
||||
<button class="btn btn-xs btn-icon" @click="df['remove'] = true">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close"></use>
|
||||
</svg>
|
||||
|
|
@ -94,14 +86,14 @@ function edit_html() {
|
|||
label: __("HTML"),
|
||||
fieldname: "html",
|
||||
fieldtype: "Code",
|
||||
options: "HTML"
|
||||
}
|
||||
options: "HTML",
|
||||
},
|
||||
],
|
||||
primary_action: ({ html }) => {
|
||||
html = frappe.dom.remove_script_and_style(html);
|
||||
props.df["html"] = html;
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
d.set_value("html", props.df.html);
|
||||
d.show();
|
||||
|
|
@ -112,7 +104,7 @@ function configure_columns() {
|
|||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "columns_area"
|
||||
fieldname: "columns_area",
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
|
|
@ -130,8 +122,8 @@ function configure_columns() {
|
|||
dialog.set_value("add_column", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
on_page_show: () => {
|
||||
createApp(ConfigureColumnsVue, { df: props.df }).mount(
|
||||
|
|
@ -139,8 +131,8 @@ function configure_columns() {
|
|||
);
|
||||
},
|
||||
on_hide: () => {
|
||||
props.df["table_columns"] = props.df.table_columns.filter(col => !col.invalid_width);
|
||||
}
|
||||
props.df["table_columns"] = props.df.table_columns.filter((col) => !col.invalid_width);
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
|
@ -149,18 +141,18 @@ function get_all_columns() {
|
|||
let more_columns = [
|
||||
{
|
||||
label: __("Sr No."),
|
||||
value: "idx"
|
||||
}
|
||||
value: "idx",
|
||||
},
|
||||
];
|
||||
return more_columns.concat(
|
||||
meta.fields
|
||||
.map(tf => {
|
||||
.map((tf) => {
|
||||
if (frappe.model.no_value_type.includes(tf.fieldtype)) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
label: tf.label,
|
||||
value: tf.fieldname
|
||||
value: tf.fieldname,
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
|
@ -172,8 +164,8 @@ function get_column_to_add(fieldname) {
|
|||
label: __("Sr No."),
|
||||
fieldtype: "Data",
|
||||
fieldname: "idx",
|
||||
width: 10
|
||||
}
|
||||
width: 10,
|
||||
},
|
||||
};
|
||||
|
||||
if (fieldname in standard_columns) {
|
||||
|
|
@ -182,7 +174,7 @@ function get_column_to_add(fieldname) {
|
|||
|
||||
return {
|
||||
...frappe.meta.get_docfield(props.df.options, fieldname),
|
||||
width: 10
|
||||
width: 10,
|
||||
};
|
||||
}
|
||||
function validate_table_columns() {
|
||||
|
|
@ -214,7 +206,6 @@ watch(
|
|||
() => validate_table_columns(),
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<template>
|
||||
<div class="html-editor">
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
class="btn btn-default btn-xs btn-edit"
|
||||
@click="toggle_edit"
|
||||
>
|
||||
<button class="btn btn-default btn-xs btn-edit" @click="toggle_edit">
|
||||
{{ !editing ? buttonLabel : __("Done") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -46,9 +43,9 @@ function toggle_edit() {
|
|||
max_lines: 30,
|
||||
change: () => {
|
||||
emit("change", get_value());
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
}
|
||||
control.value.set_value(props.value);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@
|
|||
type="button"
|
||||
class="btn btn-xs"
|
||||
@click="letterhead.align = direction"
|
||||
:class="
|
||||
letterhead.align == direction
|
||||
? 'btn-secondary'
|
||||
: 'btn-default'
|
||||
"
|
||||
:class="letterhead.align == direction ? 'btn-secondary' : 'btn-default'"
|
||||
>
|
||||
{{ direction }}
|
||||
</button>
|
||||
|
|
@ -30,12 +26,7 @@
|
|||
min="20"
|
||||
:max="range_input_field === 'image_width' ? 700 : 500"
|
||||
:value="letterhead[range_input_field]"
|
||||
@input="
|
||||
e =>
|
||||
(letterhead[range_input_field] = parseFloat(
|
||||
e.target.value
|
||||
))
|
||||
"
|
||||
@input="(e) => (letterhead[range_input_field] = parseFloat(e.target.value))"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -58,11 +49,7 @@
|
|||
class="ml-2 btn btn-default btn-xs btn-edit"
|
||||
@click="toggle_edit_letterhead"
|
||||
>
|
||||
{{
|
||||
!store.edit_letterhead
|
||||
? __("Edit Letter Head")
|
||||
: __("Done")
|
||||
}}
|
||||
{{ !store.edit_letterhead ? __("Edit Letter Head") : __("Done") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!letterhead"
|
||||
|
|
@ -73,10 +60,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="letterhead && !store.edit_letterhead"
|
||||
v-html="letterhead.content"
|
||||
></div>
|
||||
<div v-if="letterhead && !store.edit_letterhead" v-html="letterhead.content"></div>
|
||||
<!-- <div v-show="letterhead && store.edit_letterhead" ref="editor"></div> -->
|
||||
<div
|
||||
class="edit-letterhead"
|
||||
|
|
@ -85,8 +69,8 @@
|
|||
justifyContent: {
|
||||
Left: 'flex-start',
|
||||
Center: 'center',
|
||||
Right: 'flex-end'
|
||||
}[letterhead.align]
|
||||
Right: 'flex-end',
|
||||
}[letterhead.align],
|
||||
}"
|
||||
>
|
||||
<div class="edit-image">
|
||||
|
|
@ -101,7 +85,7 @@
|
|||
height:
|
||||
range_input_field === 'image_height'
|
||||
? letterhead.image_height + 'px'
|
||||
: null
|
||||
: null,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -143,15 +127,15 @@ function toggle_edit_letterhead() {
|
|||
change: () => {
|
||||
letterhead.value._dirty = true;
|
||||
letterhead.value.content = control.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true,
|
||||
only_input: true,
|
||||
no_wrapper: true
|
||||
no_wrapper: true,
|
||||
});
|
||||
}
|
||||
control.value.set_value(letterhead.value.content);
|
||||
};
|
||||
}
|
||||
function change_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Change Letter Head"),
|
||||
|
|
@ -160,62 +144,52 @@ function change_letterhead() {
|
|||
label: __("Letter Head"),
|
||||
fieldname: "letterhead",
|
||||
fieldtype: "Link",
|
||||
options: "Letter Head"
|
||||
}
|
||||
options: "Letter Head",
|
||||
},
|
||||
],
|
||||
primary_action: ({ letterhead }) => {
|
||||
if (letterhead) {
|
||||
set_letterhead(letterhead);
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
}
|
||||
function upload_image() {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
on_success: file_doc => {
|
||||
get_image_dimensions(file_doc.file_url).then(
|
||||
({ width, height }) => {
|
||||
letterhead.value["image"] = file_doc.file_url;
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
on_success: (file_doc) => {
|
||||
get_image_dimensions(file_doc.file_url).then(({ width, height }) => {
|
||||
letterhead.value["image"] = file_doc.file_url;
|
||||
let new_width = width;
|
||||
let new_height = height;
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value = aspect_ratio.value > 1 ? "image_width" : "image_height";
|
||||
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio.value;
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio.value * new_height;
|
||||
}
|
||||
|
||||
letterhead.value["image_height"] = new_height;
|
||||
letterhead.value["image_width"] = new_width;
|
||||
if (width > 200) {
|
||||
new_width = 200;
|
||||
new_height = new_width / aspect_ratio.value;
|
||||
}
|
||||
);
|
||||
}
|
||||
if (height > 80) {
|
||||
new_height = 80;
|
||||
new_width = aspect_ratio.value * new_height;
|
||||
}
|
||||
|
||||
letterhead.value["image_height"] = new_height;
|
||||
letterhead.value["image_width"] = new_width;
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
function set_letterhead(_letterhead) {
|
||||
store.value.change_letterhead(_letterhead).then(() => {
|
||||
get_image_dimensions(letterhead.value.image).then(
|
||||
({ width, height }) => {
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value =
|
||||
aspect_ratio.value > 1
|
||||
? "image_width"
|
||||
: "image_height";
|
||||
}
|
||||
);
|
||||
get_image_dimensions(letterhead.value.image).then(({ width, height }) => {
|
||||
aspect_ratio.value = width / height;
|
||||
range_input_field.value = aspect_ratio.value > 1 ? "image_width" : "image_height";
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
function create_letterhead() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Create Letter Head"),
|
||||
|
|
@ -223,23 +197,23 @@ function create_letterhead() {
|
|||
{
|
||||
label: __("Letter Head Name"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
}
|
||||
fieldtype: "Data",
|
||||
},
|
||||
],
|
||||
primary_action: ({ name }) => {
|
||||
return frappe.db
|
||||
.insert({
|
||||
doctype: "Letter Head",
|
||||
letter_head_name: name,
|
||||
source: "Image"
|
||||
source: "Image",
|
||||
})
|
||||
.then(doc => {
|
||||
.then((doc) => {
|
||||
d.hide();
|
||||
store.value.change_letterhead(doc.name).then(() => {
|
||||
toggle_edit_letterhead();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
d.show();
|
||||
}
|
||||
|
|
@ -249,34 +223,33 @@ onMounted(() => {
|
|||
set_letterhead(frappe.boot.sysdefaults.letter_head);
|
||||
}
|
||||
|
||||
watch(() => {
|
||||
return letterhead.value
|
||||
? letterhead.value[range_input_field.value]
|
||||
: null;
|
||||
}, () => {
|
||||
if (aspect_ratio.value === null) return;
|
||||
watch(
|
||||
() => {
|
||||
return letterhead.value ? letterhead.value[range_input_field.value] : null;
|
||||
},
|
||||
() => {
|
||||
if (aspect_ratio.value === null) return;
|
||||
|
||||
let update_field =
|
||||
range_input_field.value == "image_width"
|
||||
? "image_height"
|
||||
: "image_width";
|
||||
letterhead.value[update_field] =
|
||||
update_field == "image_width"
|
||||
? aspect_ratio.value * letterhead.value.image_height
|
||||
: letterhead.value.image_width / aspect_ratio.value;
|
||||
});
|
||||
let update_field =
|
||||
range_input_field.value == "image_width" ? "image_height" : "image_width";
|
||||
letterhead.value[update_field] =
|
||||
update_field == "image_width"
|
||||
? aspect_ratio.value * letterhead.value.image_height
|
||||
: letterhead.value.image_width / aspect_ratio.value;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// watch
|
||||
watch(letterhead, () => {
|
||||
if (!letterhead.value) return;
|
||||
if (letterhead.value.image_width && letterhead.value.image_height) {
|
||||
let dimension =
|
||||
letterhead.value.image_width > letterhead.value.image_height
|
||||
? "width"
|
||||
: "height";
|
||||
let dimension_value = letterhead.value["image_" + dimension];
|
||||
letterhead.value.content = `
|
||||
watch(
|
||||
letterhead,
|
||||
() => {
|
||||
if (!letterhead.value) return;
|
||||
if (letterhead.value.image_width && letterhead.value.image_height) {
|
||||
let dimension =
|
||||
letterhead.value.image_width > letterhead.value.image_height ? "width" : "height";
|
||||
let dimension_value = letterhead.value["image_" + dimension];
|
||||
letterhead.value.content = `
|
||||
<div style="text-align: ${letterhead.value.align.toLowerCase()};">
|
||||
<img
|
||||
src="${letterhead.value.image}"
|
||||
|
|
@ -285,8 +258,11 @@ watch(letterhead, () => {
|
|||
style="${dimension}: ${dimension_value}px;">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}, { deep: true }, { immediate: true });
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function refresh() {
|
|||
iframe.value?.contentWindow.location.reload();
|
||||
}
|
||||
function get_default_docname() {
|
||||
return frappe.db.get_list(doctype.value, { limit: 1 }).then(doc => {
|
||||
return frappe.db.get_list(doctype.value, { limit: 1 }).then((doc) => {
|
||||
return doc.length > 0 ? doc[0].name : null;
|
||||
});
|
||||
}
|
||||
|
|
@ -78,9 +78,7 @@ let url = computed(() => {
|
|||
params.append("letterhead", store.value.letterhead.name);
|
||||
}
|
||||
let _url =
|
||||
type.value == "PDF"
|
||||
? `/api/method/frappe.utils.weasyprint.download_pdf`
|
||||
: "/printpreview";
|
||||
type.value == "PDF" ? `/api/method/frappe.utils.weasyprint.download_pdf` : "/printpreview";
|
||||
return `${_url}?${params.toString()}`;
|
||||
});
|
||||
|
||||
|
|
@ -95,9 +93,9 @@ onMounted(() => {
|
|||
options: doctype.value,
|
||||
change: () => {
|
||||
docname.value = doc_select.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
preview_type.value = frappe.ui.form.make_control({
|
||||
parent: preview_type_ref.value,
|
||||
|
|
@ -108,12 +106,12 @@ onMounted(() => {
|
|||
options: ["PDF", "HTML"],
|
||||
change: () => {
|
||||
type.value = preview_type.value.get_value();
|
||||
}
|
||||
},
|
||||
},
|
||||
render_input: true
|
||||
render_input: true,
|
||||
});
|
||||
preview_type.value.set_value(type.value);
|
||||
get_default_docname().then(doc_name => {
|
||||
get_default_docname().then((doc_name) => {
|
||||
doc_name && doc_select.value.set_value(doc_name);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ function add_section_above(section) {
|
|||
label: "",
|
||||
columns: [
|
||||
{ label: "", fields: [] },
|
||||
{ label: "", fields: [] }
|
||||
]
|
||||
{ label: "", fields: [] },
|
||||
],
|
||||
});
|
||||
}
|
||||
sections.push(_section);
|
||||
|
|
@ -75,12 +75,12 @@ let rootStyles = computed(() => {
|
|||
margin_top = 0,
|
||||
margin_bottom = 0,
|
||||
margin_left = 0,
|
||||
margin_right = 0
|
||||
margin_right = 0,
|
||||
} = print_format.value;
|
||||
return {
|
||||
padding: `${margin_top}mm ${margin_right}mm ${margin_bottom}mm ${margin_left}mm`,
|
||||
width: "210mm",
|
||||
minHeight: "297mm"
|
||||
minHeight: "297mm",
|
||||
};
|
||||
});
|
||||
let page_number_style = computed(() => {
|
||||
|
|
@ -89,7 +89,7 @@ let page_number_style = computed(() => {
|
|||
background: "white",
|
||||
padding: "4px",
|
||||
borderRadius: "var(--border-radius)",
|
||||
border: "1px solid var(--border-color)"
|
||||
border: "1px solid var(--border-color)",
|
||||
};
|
||||
if (print_format.value.page_number.includes("Top")) {
|
||||
style.top = print_format.value.margin_top / 2 + "mm";
|
||||
|
|
|
|||
|
|
@ -27,14 +27,12 @@ let show_preview = ref(false);
|
|||
|
||||
// computed
|
||||
let $store = computed(() => {
|
||||
return getStore(props.print_format_name)
|
||||
return getStore(props.print_format_name);
|
||||
});
|
||||
|
||||
let shouldRender = computed(() => {
|
||||
return Boolean(
|
||||
$store.value.print_format.value &&
|
||||
$store.value.meta.value &&
|
||||
$store.value.layout.value
|
||||
$store.value.print_format.value && $store.value.meta.value && $store.value.layout.value
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@
|
|||
<div class="sidebar-menu">
|
||||
<div class="sidebar-label">{{ __("Page Margins") }}</div>
|
||||
<div class="margin-controls">
|
||||
<div
|
||||
class="form-group"
|
||||
v-for="df in margins"
|
||||
:key="df.fieldname"
|
||||
>
|
||||
<div class="form-group" v-for="df in margins" :key="df.fieldname">
|
||||
<div class="clearfix">
|
||||
<label class="control-label">
|
||||
{{ df.label }}
|
||||
|
|
@ -21,13 +17,7 @@
|
|||
class="form-control form-control-sm"
|
||||
:value="print_format[df.fieldname]"
|
||||
min="0"
|
||||
@change="
|
||||
e =>
|
||||
update_margin(
|
||||
df.fieldname,
|
||||
e.target.value
|
||||
)
|
||||
"
|
||||
@change="(e) => update_margin(df.fieldname, e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -43,10 +33,7 @@
|
|||
class="form-control form-control-sm"
|
||||
v-model="print_format.font"
|
||||
>
|
||||
<option
|
||||
v-for="font in google_fonts"
|
||||
:value="font"
|
||||
>
|
||||
<option v-for="font in google_fonts" :value="font">
|
||||
{{ font }}
|
||||
</option>
|
||||
</select>
|
||||
|
|
@ -65,10 +52,7 @@
|
|||
placeholder="12, 13, 14"
|
||||
:value="print_format.font_size"
|
||||
@change="
|
||||
e =>
|
||||
(print_format.font_size = parseFloat(
|
||||
e.target.value
|
||||
))
|
||||
(e) => (print_format.font_size = parseFloat(e.target.value))
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -112,10 +96,7 @@
|
|||
item-key="id"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="field"
|
||||
:title="element.fieldname"
|
||||
>
|
||||
<div class="field" :title="element.fieldname">
|
||||
{{ element.label }}
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -157,7 +138,7 @@ function clone_field(df) {
|
|||
"options",
|
||||
"table_columns",
|
||||
"html",
|
||||
"field_template"
|
||||
"field_template",
|
||||
]);
|
||||
if (cloned.custom) {
|
||||
// generate unique fieldnames for custom blocks
|
||||
|
|
@ -171,16 +152,14 @@ let margins = computed(() => {
|
|||
return [
|
||||
{ label: __("Top"), fieldname: "margin_top" },
|
||||
{ label: __("Bottom"), fieldname: "margin_bottom" },
|
||||
{ label: __("Left", null, 'alignment'), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, 'alignment'), fieldname: "margin_right" }
|
||||
{ label: __("Left", null, "alignment"), fieldname: "margin_left" },
|
||||
{ label: __("Right", null, "alignment"), fieldname: "margin_right" },
|
||||
];
|
||||
});
|
||||
let fields = computed(() => {
|
||||
let fields = meta.value.fields
|
||||
.filter(df => {
|
||||
if (
|
||||
["Section Break", "Column Break"].includes(df.fieldtype)
|
||||
) {
|
||||
.filter((df) => {
|
||||
if (["Section Break", "Column Break"].includes(df.fieldtype)) {
|
||||
return false;
|
||||
}
|
||||
if (search_text.value) {
|
||||
|
|
@ -195,12 +174,12 @@ let fields = computed(() => {
|
|||
return true;
|
||||
}
|
||||
})
|
||||
.map(df => {
|
||||
.map((df) => {
|
||||
let out = {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
fieldtype: df.fieldtype,
|
||||
options: df.options
|
||||
options: df.options,
|
||||
};
|
||||
if (df.fieldtype == "Table") {
|
||||
out.table_columns = get_table_columns(df);
|
||||
|
|
@ -214,27 +193,27 @@ let fields = computed(() => {
|
|||
fieldname: "custom_html",
|
||||
fieldtype: "HTML",
|
||||
html: "",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
{
|
||||
label: __("ID (name)"),
|
||||
fieldname: "name",
|
||||
fieldtype: "Data"
|
||||
fieldtype: "Data",
|
||||
},
|
||||
{
|
||||
label: __("Spacer"),
|
||||
fieldname: "spacer",
|
||||
fieldtype: "Spacer",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
{
|
||||
label: __("Divider"),
|
||||
fieldname: "divider",
|
||||
fieldtype: "Divider",
|
||||
custom: 1
|
||||
custom: 1,
|
||||
},
|
||||
...print_templates.value,
|
||||
...fields
|
||||
...fields,
|
||||
];
|
||||
});
|
||||
let print_templates = computed(() => {
|
||||
|
|
@ -243,21 +222,18 @@ let print_templates = computed(() => {
|
|||
for (let template of templates) {
|
||||
let df;
|
||||
if (template.field) {
|
||||
df = frappe.meta.get_docfield(
|
||||
meta.value.name,
|
||||
template.field
|
||||
);
|
||||
df = frappe.meta.get_docfield(meta.value.name, template.field);
|
||||
} else {
|
||||
df = {
|
||||
label: template.name,
|
||||
fieldname: frappe.scrub(template.name)
|
||||
fieldname: frappe.scrub(template.name),
|
||||
};
|
||||
}
|
||||
out.push({
|
||||
label: `${__(df.label)} (${__("Field Template")})`,
|
||||
fieldname: df.fieldname + "_template",
|
||||
fieldtype: "Field Template",
|
||||
field_template: template.name
|
||||
field_template: template.name,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
|
|
@ -270,7 +246,7 @@ let page_number_positions = computed(() => {
|
|||
{ label: __("Top Right"), value: "Top Right" },
|
||||
{ label: __("Bottom Left"), value: "Bottom Left" },
|
||||
{ label: __("Bottom Center"), value: "Bottom Center" },
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" }
|
||||
{ label: __("Bottom Right"), value: "Bottom Right" },
|
||||
];
|
||||
});
|
||||
|
||||
|
|
@ -278,7 +254,7 @@ let page_number_positions = computed(() => {
|
|||
onMounted(() => {
|
||||
let method =
|
||||
"frappe.printing.page.print_format_builder_beta.print_format_builder_beta.get_google_fonts";
|
||||
frappe.call(method).then(r => {
|
||||
frappe.call(method).then((r) => {
|
||||
google_fonts.value = r.message || [];
|
||||
if (!google_fonts.value.includes(print_format.value.font)) {
|
||||
google_fonts.value.push(print_format.value.font);
|
||||
|
|
|
|||
|
|
@ -28,10 +28,7 @@
|
|||
<use href="#icon-dot-horizontal"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu dropdown-menu-right"
|
||||
role="menu"
|
||||
>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<button
|
||||
v-for="option in section_options"
|
||||
class="dropdown-item"
|
||||
|
|
@ -44,17 +41,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row section-columns">
|
||||
<div
|
||||
class="column col"
|
||||
v-for="(column, i) in section.columns"
|
||||
:key="i"
|
||||
>
|
||||
<div class="column col" v-for="(column, i) in section.columns" :key="i">
|
||||
<draggable
|
||||
class="drag-container"
|
||||
:style="{
|
||||
backgroundColor: column.fields.length
|
||||
? null
|
||||
: 'var(--gray-50)'
|
||||
backgroundColor: column.fields.length ? null : 'var(--gray-50)',
|
||||
}"
|
||||
v-model="column.fields"
|
||||
group="fields"
|
||||
|
|
@ -68,10 +59,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="my-4 text-center text-muted font-italic"
|
||||
v-if="section.page_break"
|
||||
>
|
||||
<div class="my-4 text-center text-muted font-italic" v-if="section.page_break">
|
||||
{{ __("Page Break") }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,7 +81,7 @@ function add_column() {
|
|||
if (props.section.columns.length < 4) {
|
||||
props.section.columns.push({
|
||||
label: "",
|
||||
fields: []
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -121,46 +109,50 @@ let section_options = computed(() => {
|
|||
return [
|
||||
{
|
||||
label: __("Add section above"),
|
||||
action: () => emit("add_section_above")
|
||||
action: () => emit("add_section_above"),
|
||||
},
|
||||
{
|
||||
label: __("Add column"),
|
||||
action: add_column,
|
||||
condition: () => props.section.columns.length < 4
|
||||
condition: () => props.section.columns.length < 4,
|
||||
},
|
||||
{
|
||||
label: __("Remove column"),
|
||||
action: remove_column,
|
||||
condition: () => props.section.columns.length > 1
|
||||
condition: () => props.section.columns.length > 1,
|
||||
},
|
||||
{
|
||||
label: __("Add page break"),
|
||||
action: add_page_break,
|
||||
condition: () => !props.section.page_break
|
||||
condition: () => !props.section.page_break,
|
||||
},
|
||||
{
|
||||
label: __("Remove page break"),
|
||||
action: remove_page_break,
|
||||
condition: () => props.section.page_break
|
||||
condition: () => props.section.page_break,
|
||||
},
|
||||
{
|
||||
label: __("Remove section"),
|
||||
action: () => { props.section["remove"] = true }
|
||||
action: () => {
|
||||
props.section["remove"] = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Left-Right)"),
|
||||
condition: () => !props.section.field_orientation,
|
||||
action: () => { props.section["field_orientation"] = "left-right" }
|
||||
action: () => {
|
||||
props.section["field_orientation"] = "left-right";
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __("Field Orientation (Top-Down)"),
|
||||
condition: () =>
|
||||
props.section.field_orientation == "left-right",
|
||||
action: () => { props.section["field_orientation"] = "" }
|
||||
}
|
||||
].filter(option => (option.condition ? option.condition() : true));
|
||||
})
|
||||
|
||||
condition: () => props.section.field_orientation == "left-right",
|
||||
action: () => {
|
||||
props.section["field_orientation"] = "";
|
||||
},
|
||||
},
|
||||
].filter((option) => (option.condition ? option.condition() : true));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ onConnect((edge) => {
|
|||
data: {
|
||||
action: "",
|
||||
allowed: "All",
|
||||
allow_self_approval: 1,
|
||||
from: source_node.data.state,
|
||||
to: target_node.data.state,
|
||||
from_id: source_node.id,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { useStore } from "../store";
|
|||
const props = defineProps({
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isValidConnection = ({ source, target }) => {
|
||||
|
|
@ -26,21 +26,25 @@ let store = useStore();
|
|||
const { edges, findNode } = useVueFlow();
|
||||
watch(
|
||||
() => findNode(props.node.id)?.selected,
|
||||
val => {
|
||||
(val) => {
|
||||
if (val) store.workflow.selected = props.node;
|
||||
|
||||
let connected_edges = edges.value.filter(
|
||||
edge => edge.source === props.node.id || edge.target === props.node.id
|
||||
(edge) => edge.source === props.node.id || edge.target === props.node.id
|
||||
);
|
||||
connected_edges.forEach(edge => edge.selected = val);
|
||||
connected_edges.forEach((edge) => (edge.selected = val));
|
||||
}
|
||||
);
|
||||
|
||||
let label = computed(() => findNode(props.node.id)?.data?.action);
|
||||
|
||||
watch(() => props.node.data, () => {
|
||||
store.ref_history.commit();
|
||||
}, { deep: true });
|
||||
watch(
|
||||
() => props.node.data,
|
||||
() => {
|
||||
store.ref_history.commit();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -11,21 +11,21 @@ const props = defineProps({
|
|||
targetPosition: { type: String, required: false },
|
||||
sourceHandle: { type: Object, required: false },
|
||||
targetHandle: { type: Object, required: false },
|
||||
markerEnd: { type: String, required: false }
|
||||
markerEnd: { type: String, required: false },
|
||||
});
|
||||
|
||||
let opposite = {
|
||||
left: "left",
|
||||
right: "right",
|
||||
top: "bottom",
|
||||
bottom: "top"
|
||||
bottom: "top",
|
||||
};
|
||||
|
||||
const d = computed(() =>
|
||||
getSmoothStepPath({
|
||||
...props,
|
||||
borderRadius: 30,
|
||||
targetPosition: opposite[props.targetPosition]
|
||||
targetPosition: opposite[props.targetPosition],
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ $alert-types: info, success, warning, danger;
|
|||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
&> input {
|
||||
& > input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&> ul:empty {
|
||||
& > ul:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&> ul {
|
||||
& > ul {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
list-style: none;
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
z-index: 4;
|
||||
min-width: 250px;
|
||||
|
||||
&> li {
|
||||
& > li {
|
||||
cursor: pointer;
|
||||
@include get_textstyle("sm", "regular");
|
||||
padding: var(--padding-sm);
|
||||
|
|
@ -54,12 +54,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
&> li .link-option {
|
||||
& > li .link-option {
|
||||
font-weight: normal;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&> li:hover, &> li[aria-selected=true] {
|
||||
& > li:hover,
|
||||
& > li[aria-selected="true"] {
|
||||
background-color: var(--awesomplete-hover-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
|
@ -68,4 +69,4 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
height: var(--btn-height);
|
||||
padding: 0px;
|
||||
@extend .center-content;
|
||||
&.btn-default, &.btn-secondary {
|
||||
&.btn-default,
|
||||
&.btn-secondary {
|
||||
min-width: 28px;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +42,9 @@
|
|||
);
|
||||
|
||||
color: $white;
|
||||
&:hover, &:active, &:focus {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $white;
|
||||
}
|
||||
.icon {
|
||||
|
|
@ -61,7 +64,8 @@
|
|||
);
|
||||
|
||||
color: var(--primary);
|
||||
&:hover, &:active {
|
||||
&:hover,
|
||||
&:active {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +77,8 @@
|
|||
.btn.btn-secondary {
|
||||
background-color: var(--control-bg);
|
||||
color: var(--text-color);
|
||||
&:hover, &:active {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--btn-default-hover-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
|
@ -82,7 +87,8 @@
|
|||
.btn.btn-default {
|
||||
background-color: var(--control-bg);
|
||||
color: var(--text-color);
|
||||
&:hover, &:active {
|
||||
&:hover,
|
||||
&:active {
|
||||
background: var(--btn-default-hover-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
|
@ -120,7 +126,7 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
.btn-primary:active {
|
||||
color: var(--gray-900) !important;
|
||||
background-color: var(--invert-neutral) !important;
|
||||
color: var(--gray-900) !important;
|
||||
background-color: var(--invert-neutral) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-selector, .hue-selector {
|
||||
.color-selector,
|
||||
.hue-selector {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: transparent;
|
||||
|
|
@ -27,11 +28,12 @@
|
|||
border-radius: 50%;
|
||||
/* box-shadow: 0 0 0 1px gray, 0 0 0 3px white, 0 0 0 4px gray; */
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
&::before, &::after {
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
content: ' ';
|
||||
content: " ";
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
|
@ -74,8 +76,8 @@
|
|||
position: relative;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
hsl(0, 100%, 50%),
|
||||
hsl(60, 100%, 50%),
|
||||
hsl(0, 100%, 50%),
|
||||
hsl(60, 100%, 50%),
|
||||
hsl(120, 100%, 50%),
|
||||
hsl(180, 100%, 50%),
|
||||
hsl(240, 100%, 50%),
|
||||
|
|
@ -92,7 +94,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype='Color'] {
|
||||
.frappe-control[data-fieldtype="Color"] {
|
||||
input {
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
|
@ -108,9 +110,9 @@
|
|||
position: absolute;
|
||||
top: 5px;
|
||||
left: 8px;
|
||||
content: ' ';
|
||||
content: " ";
|
||||
&.no-value {
|
||||
background: url('/assets/frappe/images/color-circle.png');
|
||||
background: url("/assets/frappe/images/color-circle.png");
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ select.form-control {
|
|||
}
|
||||
}
|
||||
|
||||
.selectable-item:hover, .selectable-item.highlighted {
|
||||
.selectable-item:hover,
|
||||
.selectable-item.highlighted {
|
||||
background-color: var(--fg-hover-color);
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +143,7 @@ select.form-control {
|
|||
.frappe-control {
|
||||
@include get_textstyle("base", "regular");
|
||||
.control-label.reqd:after {
|
||||
content: ' *';
|
||||
content: " *";
|
||||
color: var(--red-400);
|
||||
}
|
||||
.help:empty {
|
||||
|
|
@ -193,7 +194,7 @@ select.form-control {
|
|||
}
|
||||
}
|
||||
|
||||
.frappe-control:not([data-fieldtype='MultiSelectPills']):not([data-fieldtype='Table MultiSelect']) {
|
||||
.frappe-control:not([data-fieldtype="MultiSelectPills"]):not([data-fieldtype="Table MultiSelect"]) {
|
||||
&.has-error {
|
||||
input {
|
||||
border: 1px solid var(--error-border);
|
||||
|
|
@ -205,8 +206,8 @@ select.form-control {
|
|||
}
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype='MultiSelectPills'],
|
||||
.frappe-control[data-fieldtype='Table MultiSelect'] {
|
||||
.frappe-control[data-fieldtype="MultiSelectPills"],
|
||||
.frappe-control[data-fieldtype="Table MultiSelect"] {
|
||||
&.has-error {
|
||||
.control-input {
|
||||
border: 1px solid var(--error-border);
|
||||
|
|
@ -257,7 +258,8 @@ select.form-control {
|
|||
}
|
||||
|
||||
/* progress bar */
|
||||
.progress, .progress-bar {
|
||||
.progress,
|
||||
.progress-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
@ -361,7 +363,8 @@ textarea.form-control {
|
|||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype="Data"] .control-input, .control-value {
|
||||
.frappe-control[data-fieldtype="Data"] .control-input,
|
||||
.control-value {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
@ -372,26 +375,30 @@ textarea.form-control {
|
|||
padding: 3px;
|
||||
}
|
||||
|
||||
.markdown-preview, .html-preview {
|
||||
.markdown-preview,
|
||||
.html-preview {
|
||||
padding: var(--padding-md);
|
||||
min-height: 300px;
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-toggle, .html-toggle {
|
||||
.markdown-toggle,
|
||||
.html-toggle {
|
||||
margin-bottom: var(--margin-xs);
|
||||
}
|
||||
|
||||
.barcode-scanner {
|
||||
position: relative;
|
||||
|
||||
& > canvas, & > video {
|
||||
& > canvas,
|
||||
& > video {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
canvas.drawing, canvas.drawingBuffer {
|
||||
canvas.drawing,
|
||||
canvas.drawingBuffer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ $disabled-input-height: 22px;
|
|||
--margin-xl: 30px;
|
||||
--margin-2xl: 40px;
|
||||
|
||||
|
||||
--modal-shadow: var(--shadow-md);
|
||||
--card-shadow: var(--shadow-sm);
|
||||
--btn-shadow: var(--shadow-xs);
|
||||
|
||||
|
||||
// navbar
|
||||
--navbar-height: 48px;
|
||||
|
||||
|
|
@ -50,8 +48,6 @@ $disabled-input-height: 22px;
|
|||
--bg-pink: var(--pink-50);
|
||||
--bg-cyan: var(--cyan-50);
|
||||
|
||||
|
||||
|
||||
--text-on-blue: var(--blue-700);
|
||||
--text-on-light-blue: var(--blue-600);
|
||||
--text-on-dark-blue: var(--blue-800);
|
||||
|
|
@ -102,6 +98,9 @@ $disabled-input-height: 22px;
|
|||
--btn-default-bg: var(--gray-100);
|
||||
--btn-default-hover-bg: var(--gray-300);
|
||||
|
||||
// Border Colors
|
||||
--border-primary: var(--gray-900);
|
||||
|
||||
// Other Colors
|
||||
--sidebar-select-color: var(--gray-200);
|
||||
|
||||
|
|
@ -125,7 +124,6 @@ $disabled-input-height: 22px;
|
|||
--code-block-bg: var(--gray-900);
|
||||
--code-block-text: var(--gray-400);
|
||||
|
||||
|
||||
--primary-color: var(--gray-900);
|
||||
--btn-height: 28px;
|
||||
|
||||
|
|
@ -140,8 +138,11 @@ $disabled-input-height: 22px;
|
|||
|
||||
--checkbox-focus-shadow: 0 0 0 2px var(--gray-300);
|
||||
--checkbox-gradient: linear-gradient(180deg, var(--primary) -124.51%, var(--primary) 100%);
|
||||
--checkbox-disabled-gradient: linear-gradient(180deg, var(--disabled-control-bg) -124.51%, var(--disabled-control-bg) 100%);
|
||||
|
||||
--checkbox-disabled-gradient: linear-gradient(
|
||||
180deg,
|
||||
var(--disabled-control-bg) -124.51%,
|
||||
var(--disabled-control-bg) 100%
|
||||
);
|
||||
|
||||
// switch
|
||||
--switch-bg: var(--gray-300);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@
|
|||
&--nav {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
&--nav-title:hover, &--nav-action:hover {
|
||||
&--nav-title:hover,
|
||||
&--nav-action:hover {
|
||||
background-color: var(--fg-hover-color);
|
||||
}
|
||||
|
||||
&--time-current-hours, &--time-current-minutes, &--time-current-seconds {
|
||||
&--time-current-hours,
|
||||
&--time-current-minutes,
|
||||
&--time-current-seconds {
|
||||
font-family: inherit;
|
||||
&:after {
|
||||
color: var(--text-color);
|
||||
|
|
@ -48,12 +51,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.-range-from-, &.-range-to- {
|
||||
&.-range-from-,
|
||||
&.-range-to- {
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--date-range-bg);
|
||||
}
|
||||
|
||||
&.-selected-, &.-current-.-selected- {
|
||||
&.-selected-,
|
||||
&.-current-.-selected- {
|
||||
color: var(--date-active-text);
|
||||
background: var(--date-active-bg);
|
||||
border-radius: var(--border-radius-tiny);
|
||||
|
|
@ -79,7 +84,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
&--time, &--buttons {
|
||||
&--time,
|
||||
&--buttons {
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
|
|
@ -109,4 +115,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-flex-end {
|
||||
|
|
@ -51,7 +51,8 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.level-left, .level-right {
|
||||
.level-left,
|
||||
.level-right {
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
|
|
@ -84,5 +85,5 @@
|
|||
}
|
||||
|
||||
.fill-width {
|
||||
flex: 1
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,4 @@
|
|||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
color: var(--heading-color);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@import "../element/checkbox";
|
||||
@import "../element/radio";
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
font-family: var(--font-stack);
|
||||
font-variation-settings: "opsz" 24;
|
||||
|
|
@ -21,7 +22,8 @@ html, body {
|
|||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.input-area, .disp-area {
|
||||
.input-area,
|
||||
.disp-area {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +59,6 @@ html, body {
|
|||
-moz-appearance: none;
|
||||
/* for Chrome */
|
||||
-webkit-appearance: none;
|
||||
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
|
|
@ -134,7 +135,8 @@ html, body {
|
|||
.btn-link {
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
.icon, &:hover {
|
||||
.icon,
|
||||
&:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,15 +31,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.grid-static-col, .row-check, .row-index {
|
||||
.grid-static-col,
|
||||
.row-check,
|
||||
.row-index {
|
||||
height: 32px;
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
.grid-static-col {
|
||||
padding: 6px 8px !important;
|
||||
.static-area{
|
||||
.static-area {
|
||||
&.reqd:after {
|
||||
content: ' *';
|
||||
content: " *";
|
||||
color: var(--red-400);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +71,8 @@
|
|||
}
|
||||
|
||||
// hide row index in 6/4 column child tables
|
||||
.form-column.col-sm-6, .form-column.col-sm-4 {
|
||||
.form-column.col-sm-6,
|
||||
.form-column.col-sm-4 {
|
||||
.form-grid {
|
||||
.row-index {
|
||||
display: none;
|
||||
|
|
@ -98,7 +101,7 @@
|
|||
|
||||
.grid-body .data-row {
|
||||
@include get_textstyle("sm", "regular");
|
||||
color: var(--text-muted)
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.grid-empty,
|
||||
|
|
@ -219,7 +222,7 @@
|
|||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype=Select].form-group .select-icon {
|
||||
.frappe-control[data-fieldtype="Select"].form-group .select-icon {
|
||||
top: 9px;
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +290,8 @@
|
|||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.grid-static-col[data-fieldtype="Code"], .grid-static-col[data-fieldtype="HTML Editor"] {
|
||||
.grid-static-col[data-fieldtype="Code"],
|
||||
.grid-static-col[data-fieldtype="HTML Editor"] {
|
||||
overflow: hidden;
|
||||
|
||||
.static-area {
|
||||
|
|
@ -450,7 +454,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.grid-buttons, .grid-bulk-actions {
|
||||
.grid-buttons,
|
||||
.grid-bulk-actions {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
|
|
@ -460,7 +465,8 @@
|
|||
.grid-footer {
|
||||
margin-top: var(--margin-sm);
|
||||
}
|
||||
.grid-footer, .grid-custom-buttons {
|
||||
.grid-footer,
|
||||
.grid-custom-buttons {
|
||||
padding: var(--padding-sm) 0px;
|
||||
background-color: var(--fg-color);
|
||||
.btn {
|
||||
|
|
@ -517,8 +523,7 @@
|
|||
}
|
||||
|
||||
.grid-footer-toolbar {
|
||||
padding: var(--padding-md) var(--padding-sm) var(--padding-xs)
|
||||
var(--padding-sm);
|
||||
padding: var(--padding-md) var(--padding-sm) var(--padding-xs) var(--padding-sm);
|
||||
// border-top: 1px solid var(--border-color);
|
||||
span {
|
||||
margin-right: var(--margin-xs);
|
||||
|
|
@ -565,10 +570,10 @@
|
|||
|
||||
@media (min-width: map-get($grid-breakpoints, "md")) {
|
||||
.form-grid-container {
|
||||
overflow-x: unset!important;
|
||||
overflow-x: unset !important;
|
||||
|
||||
.form-grid {
|
||||
position: unset!important;
|
||||
position: unset !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -579,4 +584,3 @@
|
|||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
cursor: pointer;
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
&::-webkit-scrollbar {
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
.search-icons {
|
||||
position: relative;
|
||||
|
||||
input[type='search'] {
|
||||
input[type="search"] {
|
||||
height: inherit;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldtype='Icon'] {
|
||||
.frappe-control[data-fieldtype="Icon"] {
|
||||
input {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
position: absolute;
|
||||
top: calc(50% + 2px);
|
||||
left: 8px;
|
||||
content: ' ';
|
||||
content: " ";
|
||||
}
|
||||
.like-disabled-input {
|
||||
.icon-value {
|
||||
|
|
@ -88,8 +88,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dt-cell__edit, .filter-field {
|
||||
.dt-cell__edit,
|
||||
.filter-field {
|
||||
.selected-icon {
|
||||
top: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,4 +76,4 @@ use.like-icon {
|
|||
|
||||
.no-stroke {
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
|
||||
.indicator::before {
|
||||
content: '';
|
||||
content: "";
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: var(--border-radius);
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
.indicator-pill-right {
|
||||
@include get_textstyle("sm", "regular");
|
||||
padding: 4.5px 8px;
|
||||
border-radius: var( --border-radius-full);
|
||||
border-radius: var(--border-radius-full);
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
.indicator-pill:not(.no-indicator-dot)::before,
|
||||
.indicator-pill-right::after {
|
||||
content:'';
|
||||
content: "";
|
||||
display: inline-table;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
|
|
@ -48,7 +48,8 @@
|
|||
margin: 0 0 0 4px;
|
||||
}
|
||||
|
||||
$indicator-colors: green, cyan, blue, orange, yellow, gray, grey, red, pink, darkgrey, purple, light-blue;
|
||||
$indicator-colors: green, cyan, blue, orange, yellow, gray, grey, red, pink, darkgrey, purple,
|
||||
light-blue;
|
||||
@each $color in $indicator-colors {
|
||||
.indicator.#{"" + $color} {
|
||||
&::before,
|
||||
|
|
@ -72,11 +73,12 @@ $indicator-colors: green, cyan, blue, orange, yellow, gray, grey, red, pink, dar
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.indicator.blink {
|
||||
animation: blink 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0.5; }
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
@mixin flex(
|
||||
$dis: flex,
|
||||
$x: center,
|
||||
$y: center,
|
||||
$dir: row
|
||||
) {
|
||||
@mixin flex($dis: flex, $x: center, $y: center, $dir: row) {
|
||||
display: $dis;
|
||||
justify-content: $x;
|
||||
align-items: $y;
|
||||
|
|
@ -43,7 +38,7 @@
|
|||
$top: 0,
|
||||
$left: 0,
|
||||
$background-color: var(--bg-color),
|
||||
$border-radius: var(--border-radius),
|
||||
$border-radius: var(--border-radius)
|
||||
) {
|
||||
// Deprecated: Does not work as expected anymore. Also, this never worked in Safari.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue