diff --git a/cypress/integration/control_icon.js b/cypress/integration/control_icon.js
index 5c531a0823..d89eba8840 100644
--- a/cypress/integration/control_icon.js
+++ b/cypress/integration/control_icon.js
@@ -19,18 +19,18 @@ context('Control Icon', () => {
get_dialog_with_icon().as('dialog');
cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click();
- cy.get('.icon-picker .icon-wrapper[id=active]').first().click();
- cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'active');
+ cy.get('.icon-picker .icon-wrapper[id=heart-active]').first().click();
+ cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart-active');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('icon');
- expect(value).to.equal('active');
+ expect(value).to.equal('heart-active');
});
- cy.get('.icon-picker .icon-wrapper[id=resting]').first().click();
- cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'resting');
+ cy.get('.icon-picker .icon-wrapper[id=heart]').first().click();
+ cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('icon');
- expect(value).to.equal('resting');
+ expect(value).to.equal('heart');
});
});
diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js
index 65586366e6..9d6eeaff64 100644
--- a/cypress/integration/workspace.js
+++ b/cypress/integration/workspace.js
@@ -33,44 +33,39 @@ context('Workspace 2.0', () => {
});
it('Add New Block', () => {
- cy.get('.codex-editor__redactor .ce-block');
- cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click();
- cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Heading').click();
+ cy.get('.ce-block').click().type('{enter}');
+ cy.get('.block-list-container .block-list-item').contains('Heading').click();
cy.get(":focus").type('Header');
cy.get(".ce-block:last").find('.ce-header').should('exist');
- cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click();
- cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Text').click();
+ cy.get('.ce-block:last').click().type('{enter}');
+ cy.get('.block-list-container .block-list-item').contains('Text').click();
cy.get(":focus").type('Paragraph text');
cy.get(".ce-block:last").find('.ce-paragraph').should('exist');
});
it('Delete A Block', () => {
- cy.get(".ce-block:last").find('.delete-paragraph').click();
+ cy.get(":focus").click();
+ cy.get('.paragraph-control .setting-btn').click();
+ cy.get('.paragraph-control .dropdown-item').contains('Delete').click();
cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist');
});
it('Shrink and Expand A Block', () => {
- cy.get(".ce-block:last").find('.tune-btn').click();
- cy.get('.ce-settings--opened .ce-shrink-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-11');
- cy.get('.ce-settings--opened .ce-shrink-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-10');
- cy.get('.ce-settings--opened .ce-shrink-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-9');
- cy.get('.ce-settings--opened .ce-expand-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-10');
- cy.get('.ce-settings--opened .ce-expand-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-11');
- cy.get('.ce-settings--opened .ce-expand-button').click();
- cy.get(".ce-block:last").should('have.class', 'col-12');
- });
-
- it('Change Header Text Size', () => {
- cy.get('.ce-settings--opened .cdx-settings-button[data-level="3"]').click();
- cy.get(".ce-block:last").find('.widget-head h3').should('exist');
- cy.get('.ce-settings--opened .cdx-settings-button[data-level="4"]').click();
- cy.get(".ce-block:last").find('.widget-head h4').should('exist');
+ cy.get(":focus").click();
+ cy.get('.ce-block:last .setting-btn').click();
+ cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-11');
+ cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-10');
+ cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-9');
+ cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-10');
+ cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-11');
+ cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
+ cy.get(".ce-block:last").should('have.class', 'col-xs-12');
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
});
@@ -79,7 +74,10 @@ context('Workspace 2.0', () => {
cy.get('.codex-editor__redactor .ce-block');
cy.get('.standard-actions .btn-secondary[data-label=Edit]').click();
- cy.get('.sidebar-item-container[item-name="Test Private Page"]').find('.sidebar-item-control .delete-page').click();
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]')
+ .find('.sidebar-item-control .setting-btn').click();
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]')
+ .find('.dropdown-item[title="Delete Workspace"]').click({force: true});
cy.wait(300);
cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click();
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
diff --git a/frappe/automation/workspace/tools/tools.json b/frappe/automation/workspace/tools/tools.json
index fa2606dc43..40b265b34f 100644
--- a/frappe/automation/workspace/tools/tools.json
+++ b/frappe/automation/workspace/tools/tools.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"ToDo\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"File\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Assignment Rule\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Auto Repeat\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Email\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Automation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Event Streaming\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"ToDo\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"File\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Assignment Rule\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Auto Repeat\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Automation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Event Streaming\",\"col\":4}}]",
"creation": "2020-03-02 14:53:24.980279",
"docstatus": 0,
"doctype": "Workspace",
@@ -208,7 +208,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:02.839181",
+ "modified": "2022-01-13 17:48:48.456763",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",
@@ -217,7 +217,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 26,
+ "sequence_id": 26.0,
"shortcuts": [
{
"label": "ToDo",
diff --git a/frappe/boot.py b/frappe/boot.py
index d5d992343a..f1fd0f6a3b 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -107,8 +107,8 @@ def load_conf_settings(bootinfo):
if key in conf: bootinfo[key] = conf.get(key)
def load_desktop_data(bootinfo):
- from frappe.desk.desktop import get_wspace_sidebar_items
- bootinfo.allowed_workspaces = get_wspace_sidebar_items().get('pages')
+ from frappe.desk.desktop import get_workspace_sidebar_items
+ bootinfo.allowed_workspaces = get_workspace_sidebar_items().get('pages')
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map()
bootinfo.dashboards = frappe.get_all("Dashboard")
diff --git a/frappe/core/workspace/build/build.json b/frappe/core/workspace/build/build.json
index aabb4f9d1c..c1c506ae3a 100644
--- a/frappe/core/workspace/build/build.json
+++ b/frappe/core/workspace/build/build.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]",
"creation": "2021-01-02 10:51:16.579957",
"docstatus": 0,
"doctype": "Workspace",
@@ -222,7 +222,7 @@
"type": "Link"
}
],
- "modified": "2021-09-05 21:14:52.384816",
+ "modified": "2022-01-13 17:26:02.736366",
"modified_by": "Administrator",
"module": "Core",
"name": "Build",
@@ -231,7 +231,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 5,
+ "sequence_id": 5.0,
"shortcuts": [
{
"doc_view": "",
diff --git a/frappe/core/workspace/settings/settings.json b/frappe/core/workspace/settings/settings.json
index 917ce2cbdc..5aadbc42d5 100644
--- a/frappe/core/workspace/settings/settings.json
+++ b/frappe/core/workspace/settings/settings.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"header\",\"data\": {\"text\":\"Settings\",\"level\": 4,\"col\": 12}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"System Settings\",\"col\": 4}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"Print Settings\",\"col\": 4}}, {\"type\":\"shortcut\",\"data\": {\"shortcut_name\":\"Website Settings\",\"col\": 4}}, {\"type\":\"spacer\",\"data\": {\"col\": 12}}, {\"type\":\"header\",\"data\": {\"text\":\"Reports & Masters\",\"level\": 4,\"col\": 12}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Data\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Email / Notifications\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Website\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Core\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Printing\",\"col\": 4}}, {\"type\":\"card\",\"data\": {\"card_name\":\"Workflow\",\"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]",
"creation": "2020-03-02 15:09:40.527211",
"docstatus": 0,
"doctype": "Workspace",
@@ -367,7 +367,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:03.456174",
+ "modified": "2022-01-13 17:49:59.586909",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",
@@ -376,7 +376,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 29,
+ "sequence_id": 29.0,
"shortcuts": [
{
"icon": "setting",
diff --git a/frappe/core/workspace/users/users.json b/frappe/core/workspace/users/users.json
index 85c110151b..5741c54eeb 100644
--- a/frappe/core/workspace/users/users.json
+++ b/frappe/core/workspace/users/users.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Role\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Permission Manager\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Profile\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Type\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Users\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Logs\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Permissions\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Permission Manager\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Profile\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"User Type\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Users\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Logs\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Permissions\",\"col\":4}}]",
"creation": "2020-03-02 15:12:16.754449",
"docstatus": 0,
"doctype": "Workspace",
@@ -145,7 +145,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:03.010205",
+ "modified": "2022-01-13 17:49:08.912772",
"modified_by": "Administrator",
"module": "Core",
"name": "Users",
@@ -154,7 +154,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 27,
+ "sequence_id": 27.0,
"shortcuts": [
{
"label": "User",
diff --git a/frappe/custom/workspace/customization/customization.json b/frappe/custom/workspace/customization/customization.json
index 8938bdec9c..1756abcb1d 100644
--- a/frappe/custom/workspace/customization/customization.json
+++ b/frappe/custom/workspace/customization/customization.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Customization\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customize Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Custom Role\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Client Script\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Server Script\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Dashboards\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Form Customization\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other\",\"col\":4}}]",
"creation": "2020-03-02 15:15:03.839594",
"docstatus": 0,
"doctype": "Workspace",
@@ -123,7 +123,7 @@
"type": "Link"
}
],
- "modified": "2021-11-24 16:20:03.500885",
+ "modified": "2022-01-13 17:28:08.345794",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customization",
@@ -132,7 +132,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 8,
+ "sequence_id": 8.0,
"shortcuts": [
{
"label": "Customize Form",
diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py
index e1789852f1..4164db679d 100644
--- a/frappe/desk/desktop.py
+++ b/frappe/desk/desktop.py
@@ -56,31 +56,6 @@ class Workspace:
self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache()
self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache()
- def is_page_allowed(self):
- cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module)
- shortcuts = self.doc.shortcuts
-
- for section in cards:
- links = loads(section.get('links')) if isinstance(section.get('links'), str) else section.get('links')
- for item in links:
- if self.is_item_allowed(item.get('link_to'), item.get('link_type')):
- return True
-
- def _in_active_domains(item):
- if not item.restrict_to_domain:
- return True
- else:
- return item.restrict_to_domain in frappe.get_active_domains()
-
- for item in shortcuts:
- if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item):
- return True
-
- if not shortcuts and not self.doc.links:
- return True
-
- return False
-
def is_permitted(self):
"""Returns true if Has Role is not set or the user is allowed."""
from frappe.utils import has_common
@@ -346,20 +321,20 @@ def get_desktop_page(page):
dict: dictionary of cards, charts and shortcuts to be displayed on website
"""
try:
- wspace = Workspace(loads(page))
- wspace.build_workspace()
+ workspace = Workspace(loads(page))
+ workspace.build_workspace()
return {
- 'charts': wspace.charts,
- 'shortcuts': wspace.shortcuts,
- 'cards': wspace.cards,
- 'onboardings': wspace.onboardings
+ 'charts': workspace.charts,
+ 'shortcuts': workspace.shortcuts,
+ 'cards': workspace.cards,
+ 'onboardings': workspace.onboardings
}
except DoesNotExistError:
frappe.log_error(frappe.get_traceback())
return {}
@frappe.whitelist()
-def get_wspace_sidebar_items():
+def get_workspace_sidebar_items():
"""Get list of sidebar items for desk"""
has_access = "Workspace Manager" in frappe.get_roles()
@@ -385,8 +360,8 @@ def get_wspace_sidebar_items():
# Filter Page based on Permission
for page in all_pages:
try:
- wspace = Workspace(page, True)
- if wspace.is_permitted() and wspace.is_page_allowed() or has_access:
+ workspace = Workspace(page, True)
+ if has_access or workspace.is_permitted():
if page.public:
pages.append(page)
elif page.for_user == frappe.session.user:
@@ -453,25 +428,24 @@ def get_custom_report_list(module):
return out
def save_new_widget(doc, page, blocks, new_widgets):
+ if loads(new_widgets):
+ widgets = _dict(loads(new_widgets))
- widgets = _dict(loads(new_widgets))
-
- if widgets.chart:
- doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts"))
- if widgets.shortcut:
- doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts"))
- if widgets.card:
- doc.build_links_table_from_card(widgets.card)
+ if widgets.chart:
+ doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts"))
+ if widgets.shortcut:
+ doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts"))
+ if widgets.card:
+ doc.build_links_table_from_card(widgets.card)
# remove duplicate and unwanted widgets
- if widgets:
- clean_up(doc, blocks)
+ clean_up(doc, blocks)
try:
doc.save(ignore_permissions=True)
except (ValidationError, TypeError) as e:
# Create a json string to log
- json_config = dumps(widgets, sort_keys=True, indent=4)
+ json_config = widgets and dumps(widgets, sort_keys=True, indent=4)
# Error log body
log = \
diff --git a/frappe/desk/doctype/workspace/workspace.json b/frappe/desk/doctype/workspace/workspace.json
index 04975c69e3..211029dfcf 100644
--- a/frappe/desk/doctype/workspace/workspace.json
+++ b/frappe/desk/doctype/workspace/workspace.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_rename": 1,
"autoname": "field:label",
"beta": 1,
"creation": "2020-01-23 13:45:59.470592",
@@ -141,7 +142,7 @@
},
{
"fieldname": "sequence_id",
- "fieldtype": "Int",
+ "fieldtype": "Float",
"label": "Sequence Id"
},
{
@@ -158,7 +159,7 @@
],
"in_create": 1,
"links": [],
- "modified": "2021-09-16 12:01:06.450622",
+ "modified": "2021-12-15 19:33:00.805265",
"modified_by": "Administrator",
"module": "Desk",
"name": "Workspace",
diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py
index 94114e3918..b40f517350 100644
--- a/frappe/desk/doctype/workspace/workspace.py
+++ b/frappe/desk/doctype/workspace/workspace.py
@@ -6,6 +6,7 @@ import frappe
from frappe import _
from frappe.modules.export_file import export_to_files
from frappe.model.document import Document
+from frappe.model.rename_doc import rename_doc
from frappe.desk.desktop import save_new_widget
from frappe.desk.utils import validate_route_conflict
@@ -121,77 +122,157 @@ def get_report_type(report):
report_type = frappe.get_value("Report", report, "report_type")
return report_type in ["Query Report", "Script Report", "Custom Report"]
+@frappe.whitelist()
+def new_page(new_page):
+ if not loads(new_page):
+ return
+
+ page = loads(new_page)
+
+ if page.get("public") and not is_workspace_manager():
+ return
+
+ doc = frappe.new_doc('Workspace')
+ doc.title = page.get('title')
+ doc.icon = page.get('icon')
+ doc.content = page.get('content')
+ doc.parent_page = page.get('parent_page')
+ doc.label = page.get('label')
+ doc.for_user = page.get('for_user')
+ doc.public = page.get('public')
+ doc.sequence_id = last_sequence_id(doc) + 1
+ doc.save(ignore_permissions=True)
+
+ return doc
@frappe.whitelist()
-def save_page(title, icon, parent, public, sb_public_items, sb_private_items, deleted_pages, new_widgets, blocks, save):
- save = frappe.parse_json(save)
+def save_page(title, public, new_widgets, blocks):
public = frappe.parse_json(public)
- if save:
- doc = frappe.new_doc('Workspace')
+
+ filters = {
+ 'public': public,
+ 'label': title
+ }
+
+ if not public:
+ filters = {
+ 'for_user': frappe.session.user,
+ 'label': title + "-" + frappe.session.user
+ }
+ pages = frappe.get_list("Workspace", filters=filters)
+ if pages:
+ doc = frappe.get_doc("Workspace", pages[0])
+
+ doc.content = blocks
+ doc.save(ignore_permissions=True)
+
+ save_new_widget(doc, title, blocks, new_widgets)
+
+ return {"name": title, "public": public, "label": doc.label}
+
+@frappe.whitelist()
+def update_page(name, title, icon, parent, public):
+ public = frappe.parse_json(public)
+
+ doc = frappe.get_doc("Workspace", name)
+
+ filters = {
+ 'parent_page': doc.title,
+ 'public': doc.public
+ }
+ child_docs = frappe.get_list("Workspace", filters=filters)
+
+ if doc:
doc.title = title
doc.icon = icon
- doc.content = blocks
doc.parent_page = parent
-
- if public:
- doc.label = title
- doc.public = 1
- else:
- doc.label = title + "-" + frappe.session.user
- doc.for_user = frappe.session.user
- doc.save(ignore_permissions=True)
- else:
- if public:
- filters = {
- 'public': public,
- 'label': title
- }
- else:
- filters = {
- 'for_user': frappe.session.user,
- 'label': title + "-" + frappe.session.user
- }
- pages = frappe.get_list("Workspace", filters=filters)
- if pages:
- doc = frappe.get_doc("Workspace", pages[0])
-
- doc.content = blocks
+ if doc.public != public:
+ doc.sequence_id = frappe.db.count('Workspace', {'public':public}, cache=True)
+ doc.public = public
+ doc.for_user = '' if public else doc.for_user or frappe.session.user
+ doc.label = '{0}-{1}'.format(title, doc.for_user) if doc.for_user else title
doc.save(ignore_permissions=True)
- if loads(new_widgets):
- save_new_widget(doc, title, blocks, new_widgets)
+ if name != doc.label:
+ rename_doc("Workspace", name, doc.label, force=True, ignore_permissions=True)
- if loads(sb_public_items) or loads(sb_private_items):
- sort_pages(loads(sb_public_items), loads(sb_private_items))
+ # update new name and public in child pages
+ if child_docs:
+ for child in child_docs:
+ child_doc = frappe.get_doc("Workspace", child.name)
+ child_doc.parent_page = doc.title
+ child_doc.public = doc.public
+ child_doc.save(ignore_permissions=True)
- if loads(deleted_pages):
- return delete_pages(loads(deleted_pages))
+ return {"name": doc.title, "public": doc.public, "label": doc.label}
- return {"name": title, "public": public, "label": doc.label}
+@frappe.whitelist()
+def duplicate_page(page_name, new_page):
+ if not loads(new_page):
+ return
+
+ new_page = loads(new_page)
+
+ if new_page.get("is_public") and not is_workspace_manager():
+ return
+
+ old_doc = frappe.get_doc("Workspace", page_name)
+ doc = frappe.copy_doc(old_doc)
+ doc.title = new_page.get('title')
+ doc.icon = new_page.get('icon')
+ doc.parent_page = new_page.get('parent') or ''
+ doc.public = new_page.get('is_public')
+ doc.for_user = ''
+ doc.label = doc.title
+ if not doc.public:
+ doc.for_user = doc.for_user or frappe.session.user
+ doc.label = '{0}-{1}'.format(doc.title, doc.for_user)
+ doc.name = doc.label
+ if old_doc.public == doc.public:
+ doc.sequence_id += 0.1
+ else:
+ doc.sequence_id = last_sequence_id(doc) + 1
+ doc.insert(ignore_permissions=True)
-def delete_pages(deleted_pages):
- for page in deleted_pages:
- if page.get("public") and not is_workspace_manager():
- return {"name": page.get("title"), "public": 1, "label": page.get("label")}
+ return doc
- if frappe.db.exists("Workspace", page.get("name")):
- frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True)
+@frappe.whitelist()
+def delete_page(page):
+ if not loads(page):
+ return
+
+ page = loads(page)
+
+ if page.get("public") and not is_workspace_manager():
+ return
+
+ if frappe.db.exists("Workspace", page.get("name")):
+ frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True)
- return {"name": "Home", "public": 1, "label": "Home"}
+ return {"name": page.get("name"), "public": page.get("public"), "title": page.get("title")}
+@frappe.whitelist()
def sort_pages(sb_public_items, sb_private_items):
- wspace_public_pages = get_page_list(['name', 'title'], {'public': 1})
- wspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user})
+ if not loads(sb_public_items) and not loads(sb_private_items):
+ return
+
+ sb_public_items = loads(sb_public_items)
+ sb_private_items = loads(sb_private_items)
+
+ workspace_public_pages = get_page_list(['name', 'title'], {'public': 1})
+ workspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user})
if sb_private_items:
- sort_page(wspace_private_pages, sb_private_items)
+ return sort_page(workspace_private_pages, sb_private_items)
if sb_public_items and is_workspace_manager():
- sort_page(wspace_public_pages, sb_public_items)
+ return sort_page(workspace_public_pages, sb_public_items)
-def sort_page(wspace_pages, pages):
+ return False
+
+def sort_page(workspace_pages, pages):
for seq, d in enumerate(pages):
- for page in wspace_pages:
+ for page in workspace_pages:
if page.title == d.get('title'):
doc = frappe.get_doc('Workspace', page.name)
doc.sequence_id = seq + 1
@@ -199,6 +280,27 @@ def sort_page(wspace_pages, pages):
doc.save(ignore_permissions=True)
break
+ return True
+
+def last_sequence_id(doc):
+ doc_exists = frappe.db.exists({
+ 'doctype': 'Workspace',
+ 'public': doc.public,
+ 'for_user': doc.for_user
+ })
+
+ if not doc_exists:
+ return 0
+
+ return frappe.db.get_list('Workspace',
+ fields=['sequence_id'],
+ filters={
+ 'public': doc.public,
+ 'for_user': doc.for_user
+ },
+ order_by="sequence_id desc"
+ )[0].sequence_id
+
def get_page_list(fields, filters):
return frappe.get_list("Workspace", fields=fields, filters=filters, order_by='sequence_id asc')
diff --git a/frappe/integrations/workspace/integrations/integrations.json b/frappe/integrations/workspace/integrations/integrations.json
index b85056e3ef..bbd2e1199f 100644
--- a/frappe/integrations/workspace/integrations/integrations.json
+++ b/frappe/integrations/workspace/integrations/integrations.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Backup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Google Services\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Authentication\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:16:18.714190",
"docstatus": 0,
"doctype": "Workspace",
@@ -260,7 +260,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:00.355268",
+ "modified": "2022-01-13 17:39:01.292154",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",
@@ -269,7 +269,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 15,
+ "sequence_id": 15.0,
"shortcuts": [],
"title": "Integrations"
}
\ No newline at end of file
diff --git a/frappe/patches/v14_0/update_workspace2.py b/frappe/patches/v14_0/update_workspace2.py
index 82076c4328..a4b057b989 100644
--- a/frappe/patches/v14_0/update_workspace2.py
+++ b/frappe/patches/v14_0/update_workspace2.py
@@ -5,10 +5,10 @@ from frappe import _
def execute():
frappe.reload_doc('desk', 'doctype', 'workspace', force=True)
- for seq, wspace in enumerate(frappe.get_all('Workspace', order_by='name asc')):
- doc = frappe.get_doc('Workspace', wspace.name)
+ for seq, workspace in enumerate(frappe.get_all('Workspace', order_by='name asc')):
+ doc = frappe.get_doc('Workspace', workspace.name)
content = create_content(doc)
- update_wspace(doc, seq, content)
+ update_workspace(doc, seq, content)
frappe.db.commit()
def create_content(doc):
@@ -49,7 +49,7 @@ def create_content(doc):
del doc.links[doc.links.index(l)]
return content
-def update_wspace(doc, seq, content):
+def update_workspace(doc, seq, content):
if not doc.title and not doc.content and not doc.is_standard and not doc.public:
doc.sequence_id = seq + 1
doc.content = json.dumps(content)
diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg
index b878f713e9..f2977e3016 100644
--- a/frappe/public/icons/timeless/symbol-defs.svg
+++ b/frappe/public/icons/timeless/symbol-defs.svg
@@ -1,217 +1,262 @@
diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js
index cac02c7a68..e056a34be2 100644
--- a/frappe/public/js/desk.bundle.js
+++ b/frappe/public/js/desk.bundle.js
@@ -96,6 +96,7 @@ import "./frappe/ui/sort_selector.js";
import "./frappe/change_log.html";
import "./frappe/ui/workspace_loading_skeleton.html";
+import "./frappe/ui/workspace_sidebar_loading_skeleton.html";
import "./frappe/desk.js";
import "./frappe/query_string.js";
diff --git a/frappe/public/js/frappe/form/controls/dynamic_link.js b/frappe/public/js/frappe/form/controls/dynamic_link.js
index 2c5661ca87..ea9ceb35f3 100644
--- a/frappe/public/js/frappe/form/controls/dynamic_link.js
+++ b/frappe/public/js/frappe/form/controls/dynamic_link.js
@@ -2,7 +2,7 @@ frappe.ui.form.ControlDynamicLink = class ControlDynamicLink extends frappe.ui.f
get_options() {
let options = '';
if (this.df.get_options) {
- options = this.df.get_options();
+ options = this.df.get_options(this);
} else if (this.docname==null && cur_dialog) {
//for dialog box
options = cur_dialog.get_value(this.df.options);
diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js
index 4fcf0dbd14..7bea7f0584 100644
--- a/frappe/public/js/frappe/router.js
+++ b/frappe/public/js/frappe/router.js
@@ -133,14 +133,14 @@ frappe.router = {
// /app/user/user-001 = ["Form", "User", "user-001"]
// /app/event/view/calendar/default = ["List", "Event", "Calendar", "Default"]
- let private_wspace = route[1] && `${route[1]}-${frappe.user.name.toLowerCase()}`;
+ let private_workspace = route[1] && `${route[1]}-${frappe.user.name.toLowerCase()}`;
if (frappe.workspaces[route[0]]) {
// public workspace
route = ['Workspaces', frappe.workspaces[route[0]].title];
- } else if (route[0] == 'private' && frappe.workspaces[private_wspace]) {
+ } else if (route[0] == 'private' && frappe.workspaces[private_workspace]) {
// private workspace
- route = ['Workspaces', 'private', frappe.workspaces[private_wspace].title];
+ route = ['Workspaces', 'private', frappe.workspaces[private_workspace].title];
} else if (this.routes[route[0]]) {
// route
route = this.set_doctype_route(route);
diff --git a/frappe/public/js/frappe/ui/workspace_sidebar_loading_skeleton.html b/frappe/public/js/frappe/ui/workspace_sidebar_loading_skeleton.html
new file mode 100644
index 0000000000..4f20e3c21c
--- /dev/null
+++ b/frappe/public/js/frappe/ui/workspace_sidebar_loading_skeleton.html
@@ -0,0 +1,22 @@
+
\ No newline at end of file
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index a3a8f96b11..725296a121 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -243,9 +243,28 @@ Object.assign(frappe.utils, {
'=': '='
};
- return String(txt).replace(/[&<>"'`=/]/g, function(char) {
- return escape_html_mapping[char];
- });
+ return String(txt).replace(
+ /[&<>"'`=/]/g,
+ char => escape_html_mapping[char] || char
+ );
+ },
+
+ unescape_html: function(txt) {
+ let unescape_html_mapping = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ ''': "'",
+ '/': '/',
+ '`': '`',
+ '=': '='
+ };
+
+ return String(txt).replace(
+ /&|<|>|"|'|/|`|=/g,
+ char => unescape_html_mapping[char] || char
+ );
},
html2text: function(html) {
diff --git a/frappe/public/js/frappe/views/workspace/blocks/block.js b/frappe/public/js/frappe/views/workspace/blocks/block.js
index aed3c2f727..f4d4eca6cc 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/block.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/block.js
@@ -7,7 +7,7 @@ export default class Block {
make(block, block_name, widget_type = block) {
let block_data = this.config.page_data[block+'s'].items.find(obj => {
- return obj.label == block_name;
+ return frappe.utils.unescape_html(obj.label) == frappe.utils.unescape_html(block_name);
});
if (!block_data) return false;
this.wrapper.innerHTML = '';
@@ -28,12 +28,64 @@ export default class Block {
return true;
}
- rendered() {
- var e = this.wrapper.closest('.ce-block');
- e.classList.add("col-" + this.get_col());
+ rendered(wrapper) {
+ if (wrapper) this.wrapper = wrapper;
+ !this.readOnly && this.resizer();
+ let block = this.wrapper.closest('.ce-block');
+ this.set_col_class(block, this.get_col());
+ }
+
+ resizer() {
+ this.wrapper.className = this.wrapper.className + ' resizable';
+ var resizer = document.createElement('div');
+ resizer.className = 'resizer';
+ this.wrapper.parentElement.appendChild(resizer);
+ resizer.addEventListener('mousedown', init_drag, false);
+ let me = this;
+ var startX, startWidth;
+
+ function init_drag(e) {
+ startX = e.clientX;
+ startWidth = this.parentElement.offsetWidth;
+ document.documentElement.addEventListener('mousemove', do_drag, false);
+ document.documentElement.addEventListener('mouseup', stop_drag, false);
+ }
+
+ function do_drag(e) {
+ $(this).css("cursor", "col-resize");
+ $('.widget').css("pointer-events", "none");
+ $(me.wrapper.parentElement).find('.resizer').css("border-right", "3px solid var(--gray-400)");
+ un_focus();
+ if ((startWidth + e.clientX - startX) - startWidth > 60) {
+ startX = e.clientX;
+ me.increase_width();
+ } else if ((startWidth + e.clientX - startX) - startWidth < -60) {
+ startX = e.clientX;
+ me.decrease_width();
+ }
+ }
+
+ // disable text selection on mousedown (on drag)
+ function un_focus() {
+ if (document.selection) {
+ document.selection.empty();
+ } else {
+ window.getSelection().removeAllRanges();
+ }
+ }
+
+ function stop_drag() {
+ $(this).css("cursor", "default");
+ $('.widget').css("pointer-events", "auto");
+ $(me.wrapper.parentElement).find('.resizer').css("border-right", "0px solid transparent");
+
+ document.documentElement.removeEventListener('mousemove', do_drag, false);
+ document.documentElement.removeEventListener('mouseup', stop_drag, false);
+ }
}
new(block, widget_type = block) {
+ let me = this;
const dialog_class = get_dialog_constructor(widget_type);
let block_name = block+'_name';
this.dialog = new dialog_class({
@@ -53,13 +105,18 @@ export default class Block {
});
this.block_widget.customize(this.options);
this.wrapper.setAttribute(block_name, this.block_widget.label);
+ $(this.wrapper).find('.widget').addClass(`${widget_type} edit-mode`);
this.new_block_widget = this.block_widget.get_config();
- this.add_tune_button();
+ this.add_settings_button();
},
});
if (!this.readOnly && this.data && !this.data[block_name]) {
this.dialog.make();
+
+ this.dialog.dialog.get_close_btn().click(() => {
+ me.wrapper.closest('.ce-block').remove();
+ });
}
}
@@ -74,42 +131,203 @@ export default class Block {
this.new_block_widget = block_obj.get_config();
}
- add_tune_button() {
- let $widget_control = $(this.wrapper).find('.widget-control');
- frappe.utils.add_custom_button(
- frappe.utils.icon('dot-horizontal', 'xs'),
- (event) => {
- let evn = event;
- !$('.ce-settings.ce-settings--opened').length &&
- setTimeout(() => {
- this.api.toolbar.toggleBlockSettings();
- var position = $(evn.target).offset();
- $('.ce-settings.ce-settings--opened').offset({
- top: position.top + 25,
- left: position.left - 77
- });
- }, 50);
+ add_new_block_button() {
+ let $new_button = $(`
+ ${frappe.utils.icon('add-round', 'lg')}
+ `);
+
+ $new_button.appendTo(this.wrapper);
+
+ $new_button.click(event => {
+ event.stopPropagation();
+ let index = this.api.blocks.getCurrentBlockIndex() + 1;
+ this.api.blocks.insert('paragraph', {}, {}, index);
+ this.api.caret.setToBlock(index);
+ });
+ }
+
+ add_settings_button() {
+ let me = this;
+ this.dropdown_list = [
+ {
+ label: 'Delete',
+ title: 'Delete Block',
+ icon: frappe.utils.icon('delete-active', 'sm'),
+ action: () => this.api.blocks.delete()
+ },
+ {
+ label: 'Expand',
+ title: 'Expand Block',
+ icon: frappe.utils.icon('expand-alt', 'sm'),
+ action: () => this.increase_width()
+ },
+ {
+ label: 'Shrink',
+ title: 'Shrink Block',
+ icon: frappe.utils.icon('shrink', 'sm'),
+ action: () => this.decrease_width()
+ },
+ {
+ label: 'Move Up',
+ title: 'Move Up',
+ icon: frappe.utils.icon('up-arrow', 'sm'),
+ action: () => this.move_block('up')
},
- "tune-btn",
- `${__('Tune')}`,
- null,
- $widget_control,
- true
- );
+ {
+ label: 'Move Down',
+ title: 'Move Down',
+ icon: frappe.utils.icon('down-arrow', 'sm'),
+ action: () => this.move_block('down')
+ }
+ ];
+
+ let $widget_control = $(this.wrapper).find('.widget-control');
+
+ let $button = $(`
+
+
+
+
+ `);
+
+
+ let dropdown_item = function(label, title, icon, action) {
+ let html = $(`
+
+ ${icon}
+ ${label}
+
+ `);
+
+ html.click(event => {
+ event.stopPropagation();
+ action && action();
+ });
+
+ return html;
+ };
+
+ $button.click(event => {
+ event.stopPropagation();
+ $button.find('.dropdown-list').toggleClass('hidden');
+ });
+
+ $(document).click(() => {
+ $button.find('.dropdown-list').addClass('hidden');
+ });
+
+ $widget_control.prepend($button);
+
+ this.dropdown_list.forEach((item) => {
+ if ((item.label == 'Expand' || item.label == 'Shrink') &&
+ me.options && !me.options.allow_resize) {
+ return;
+ }
+ $button.find('.dropdown-list').append(dropdown_item(item.label, item.title, item.icon, item.action));
+ });
}
get_col() {
let col = this.col || 12;
- let class_name = "col-12";
+ let class_name = "col-xs-12";
let wrapper = this.wrapper.closest('.ce-block');
const col_class = new RegExp(/\bcol-.+?\b/, "g");
if (wrapper && wrapper.className.match(col_class)) {
wrapper.classList.forEach(function (cn) {
- cn.match(col_class) && (class_name = cn);
+ if (cn.match(col_class)) {
+ class_name = cn;
+ }
});
let parts = class_name.split("-");
- col = parseInt(parts[1]);
+ col = parseInt(parts[2]);
}
return col;
}
+
+ decrease_width() {
+ this.update_width('decrease');
+ }
+
+ increase_width() {
+ this.update_width('increase');
+ }
+
+ update_width(action) {
+ let min_width = this.options && this.options.min_width || 3;
+ const current_block_index = this.api.blocks.getCurrentBlockIndex();
+ if (current_block_index < 0) {
+ return;
+ }
+
+ let current_block = this.api.blocks.getBlockByIndex(current_block_index);
+ if (!current_block) {
+ return;
+ }
+
+ const current_block_element = current_block.holder;
+
+ let className = 'col-xs-12';
+ const colClass = new RegExp(/\bcol-.+?\b/, 'g');
+ if (current_block_element.className.match(colClass)) {
+ current_block_element.classList.forEach( cn => {
+ if (cn.match(colClass)) {
+ className = cn;
+ }
+ });
+ let parts = className.split('-');
+ let width = parseInt(parts[2]);
+
+ let condition = true;
+
+ if (action == 'increase') {
+ condition = width <= 11;
+ width = width + 1;
+ } else if (action == 'decrease') {
+ condition = width > min_width;
+ width = width - 1;
+ }
+
+ if (condition) {
+ this.set_col_class(current_block_element, width);
+ }
+ }
+ }
+
+ set_col_class(node, width) {
+ let classes = $.grep(node.classList, function (item) {
+ return item.indexOf("col-") !== 0;
+ });
+
+ node.classList = '';
+
+ classes.forEach(cl => {
+ node.classList.add(cl);
+ });
+
+ let col = 'col-xs-12';
+ if (width <= 12 && width >= 7) {
+ col = 'col-xs-' + width;
+ } else if (width == 6 || width == 5) {
+ node.classList.add('col-xs-12');
+ col = 'col-sm-' + width;
+ } else if (width == 4) {
+ node.classList.add('col-xs-12');
+ node.classList.add('col-sm-6');
+ col = 'col-md-' + width;
+ } else if (width == 3) {
+ node.classList.add('col-xs-12');
+ node.classList.add('col-sm-6');
+ node.classList.add('col-md-4');
+ col = 'col-lg-' + width;
+ }
+ node.classList.add(col);
+ }
+
+ move_block(direction) {
+ let current_index = this.api.blocks.getCurrentBlockIndex();
+ let new_index = current_index + (direction == 'down' ? 1 : -1);
+ this.api.blocks.move(new_index, current_index);
+ }
}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/blocks/card.js b/frappe/public/js/frappe/views/workspace/blocks/card.js
index 9b4a2ed14f..9ce6ce8b4d 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/card.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/card.js
@@ -3,7 +3,7 @@ export default class Card extends Block {
static get toolbox() {
return {
title: 'Card',
- icon: ''
+ icon: frappe.utils.icon('card', 'sm')
};
}
@@ -22,6 +22,7 @@ export default class Card extends Block {
allow_delete: this.allow_customization,
allow_hiding: false,
allow_edit: true,
+ allow_resize: true
};
}
@@ -35,7 +36,9 @@ export default class Card extends Block {
}
if (!this.readOnly) {
- this.add_tune_button();
+ $(this.wrapper).find('.widget').addClass('links edit-mode');
+ this.add_settings_button();
+ this.add_new_block_button();
}
return this.wrapper;
@@ -49,9 +52,9 @@ export default class Card extends Block {
return true;
}
- save(blockContent) {
+ save() {
return {
- card_name: blockContent.getAttribute('card_name'),
+ card_name: this.wrapper.getAttribute('card_name'),
col: this.get_col(),
new: this.new_block_widget
};
diff --git a/frappe/public/js/frappe/views/workspace/blocks/chart.js b/frappe/public/js/frappe/views/workspace/blocks/chart.js
index 02e6a66e6f..ccef1fa15f 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/chart.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/chart.js
@@ -3,7 +3,7 @@ export default class Chart extends Block {
static get toolbox() {
return {
title: 'Chart',
- icon: ''
+ icon: frappe.utils.icon('chart', 'sm')
};
}
@@ -21,7 +21,9 @@ export default class Chart extends Block {
allow_delete: this.allow_customization,
allow_hiding: false,
allow_edit: true,
- max_widget_count: 2,
+ allow_resize: true,
+ min_width: 6,
+ max_widget_count: 2
};
}
@@ -35,7 +37,9 @@ export default class Chart extends Block {
}
if (!this.readOnly) {
- this.add_tune_button();
+ $(this.wrapper).find('.widget').addClass('chart edit-mode');
+ this.add_settings_button();
+ this.add_new_block_button();
}
return this.wrapper;
@@ -49,9 +53,9 @@ export default class Chart extends Block {
return true;
}
- save(blockContent) {
+ save() {
return {
- chart_name: blockContent.getAttribute('chart_name'),
+ chart_name: this.wrapper.getAttribute('chart_name'),
col: this.get_col(),
new: this.new_block_widget
};
diff --git a/frappe/public/js/frappe/views/workspace/blocks/header.js b/frappe/public/js/frappe/views/workspace/blocks/header.js
index d88bc42af9..29ffb0a828 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/header.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/header.js
@@ -4,16 +4,8 @@ export default class Header extends Block {
constructor({ data, config, api, readOnly }) {
super({ config, api, readOnly });
- this._CSS = {
- block: this.api.styles.block,
- settingsButton: this.api.styles.settingsButton,
- settingsButtonActive: this.api.styles.settingsButtonActive,
- wrapper: 'ce-header',
- };
-
this._settings = this.config;
this._data = this.normalizeData(data);
- this.settingsButtons = [];
this._element = this.getTag();
this.data = data;
@@ -27,8 +19,7 @@ export default class Header extends Block {
data = {};
}
- newData.text = (data.text && __(data.text.replace(/(\n|\t)/gm, ""))) || '';
- newData.level = parseInt(data.level) || this.defaultLevel.number;
+ newData.text = data.text || '';
newData.col = parseInt(data.col) || 12;
return newData;
@@ -36,7 +27,6 @@ export default class Header extends Block {
render() {
this.wrapper = document.createElement('div');
- this.wrapper.contentEditable = this.readOnly ? 'false' : 'true';
if (!this.readOnly) {
let $widget_head = $(``);
let $widget_control = $(``);
@@ -45,27 +35,10 @@ export default class Header extends Block {
$widget_control.appendTo($widget_head);
$widget_head.appendTo(this.wrapper);
- this.wrapper.classList.add('widget', 'header');
+ this.wrapper.classList.add('widget', 'header', 'edit-mode');
- frappe.utils.add_custom_button(
- frappe.utils.icon('dot-horizontal', 'xs'),
- (event) => {
- let evn = event;
- !$('.ce-settings.ce-settings--opened').length &&
- setTimeout(() => {
- this.api.toolbar.toggleBlockSettings();
- var position = $(evn.target).offset();
- $('.ce-settings.ce-settings--opened').offset({
- top: position.top + 25,
- left: position.left - 77
- });
- }, 50);
- },
- "tune-btn",
- `${__('Tune')}`,
- null,
- $widget_control
- );
+ this.add_settings_button();
+ this.add_new_block_button();
frappe.utils.add_custom_button(
frappe.utils.icon('drag', 'xs'),
@@ -76,67 +49,14 @@ export default class Header extends Block {
$widget_control
);
- frappe.utils.add_custom_button(
- frappe.utils.icon('delete', 'xs'),
- () => this.api.blocks.delete(),
- "delete-header",
- `${__('Delete')}`,
- null,
- $widget_control
- );
-
return this.wrapper;
}
return this._element;
}
- renderSettings() {
- const holder = document.createElement('DIV');
-
- if (this.levels.length <= 1) {
- return holder;
- }
-
- this.levels.forEach(level => {
- const selectTypeButton = document.createElement('SPAN');
-
- selectTypeButton.classList.add(this._CSS.settingsButton);
-
- if (this.currentLevel.number === level.number) {
- selectTypeButton.classList.add(this._CSS.settingsButtonActive);
- }
-
- selectTypeButton.innerHTML = level.svg;
-
- selectTypeButton.dataset.level = level.number;
-
- selectTypeButton.addEventListener('click', () => {
- this.setLevel(level.number);
- });
-
- holder.appendChild(selectTypeButton);
-
- this.settingsButtons.push(selectTypeButton);
- });
-
- return holder;
- }
-
- setLevel(level) {
- this.data = {
- level: level,
- text: this.data.text,
- };
-
- this.settingsButtons.forEach(button => {
- button.classList.toggle(this._CSS.settingsButtonActive, parseInt(button.dataset.level) === level);
- });
- }
-
merge(data) {
const newData = {
- text: this.data.text + data.text,
- level: this.data.level,
+ text: this.data.text + data.text
};
this.data = newData;
@@ -146,31 +66,28 @@ export default class Header extends Block {
return blockData.text.trim() !== '';
}
- save(toolsContent) {
+ save() {
this.wrapper = this._element;
return {
- text: toolsContent.innerText,
- level: this.currentLevel.number,
+ text: this.wrapper.innerHTML.replace(/ /gi, ''),
col: this.get_col()
};
}
rendered() {
- var e = this._element.closest('.ce-block');
- e.classList.add("col-" + this.get_col());
- }
-
- static get conversionConfig() {
- return {
- export: 'text', // use 'text' property for other blocks
- import: 'text', // fill 'text' property from other block's export string
- };
+ super.rendered(this._element);
}
static get sanitize() {
return {
level: false,
- text: {},
+ text: {
+ br: true,
+ b: true,
+ i: true,
+ a: true,
+ span: true
+ },
};
}
@@ -180,7 +97,6 @@ export default class Header extends Block {
get data() {
this._data.text = this._element.innerHTML;
- this._data.level = this.currentLevel.number;
return this._data;
}
@@ -188,15 +104,11 @@ export default class Header extends Block {
set data(data) {
this._data = this.normalizeData(data);
- if (data.level !== undefined && this._element.parentNode) {
- const newHeader = this.getTag();
- newHeader.innerHTML = this._element.innerHTML;
- this._element.parentNode.replaceChild(newHeader, this._element);
- this._element = newHeader;
- }
-
if (data.text !== undefined) {
- this._element.innerHTML = this._data.text || '';
+ let text = this._data.text || '';
+ const contains_html_tag = /<[a-z][\s\S]*>/i.test(text);
+ this._element.innerHTML = contains_html_tag ?
+ text : `${text}`;
}
if (!this.readOnly && this.wrapper) {
@@ -205,11 +117,12 @@ export default class Header extends Block {
}
getTag() {
- const tag = document.createElement(this.currentLevel.tag);
+ const tag = document.createElement('DIV');
- tag.innerHTML = this._data.text || '';
+ let text = this._data.text || ' ';
+ tag.innerHTML = `${text}`;
- tag.classList.add(this._CSS.wrapper);
+ tag.classList.add('ce-header');
if (!this.readOnly) {
tag.contentEditable = true;
@@ -220,120 +133,10 @@ export default class Header extends Block {
return tag;
}
- get currentLevel() {
- let level = this.levels.find(levelItem => levelItem.number === this._data.level);
-
- if (!level) {
- level = this.defaultLevel;
- }
-
- return level;
- }
-
- get defaultLevel() {
- if (this._settings.defaultLevel) {
- const userSpecified = this.levels.find(levelItem => {
- return levelItem.number === this._settings.defaultLevel;
- });
-
- if (userSpecified) {
- return userSpecified;
- } else {
- // console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels');
- }
- }
-
- return this.levels[1];
- }
-
- get levels() {
- const availableLevels = [
- {
- number: 1,
- tag: 'H1',
- svg: '',
- },
- {
- number: 2,
- tag: 'H2',
- svg: '',
- },
- {
- number: 3,
- tag: 'H3',
- svg: '',
- },
- {
- number: 4,
- tag: 'H4',
- svg: '',
- },
- {
- number: 5,
- tag: 'H5',
- svg: '',
- },
- {
- number: 6,
- tag: 'H6',
- svg: '',
- },
- ];
-
- return this._settings.levels ? availableLevels.filter(
- l => this._settings.levels.includes(l.number)
- ) : availableLevels;
- }
-
- onPaste(event) {
- const content = event.detail.data;
-
- let level = this.defaultLevel.number;
-
- switch (content.tagName) {
- case 'H1':
- level = 1;
- break;
- case 'H2':
- level = 2;
- break;
- case 'H3':
- level = 3;
- break;
- case 'H4':
- level = 4;
- break;
- case 'H5':
- level = 5;
- break;
- case 'H6':
- level = 6;
- break;
- }
-
- if (this._settings.levels) {
- // Fallback to nearest level when specified not available
- level = this._settings.levels.reduce((prevLevel, currLevel) => {
- return Math.abs(currLevel - level) < Math.abs(prevLevel - level) ? currLevel : prevLevel;
- });
- }
-
- this.data = {
- level,
- text: content.innerHTML,
- };
- }
-
- static get pasteConfig() {
- return {
- tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
- };
- }
-
static get toolbox() {
return {
- icon: '',
title: 'Heading',
+ icon: frappe.utils.icon('header', 'sm')
};
}
}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/blocks/header_size.js b/frappe/public/js/frappe/views/workspace/blocks/header_size.js
new file mode 100644
index 0000000000..3694b8799b
--- /dev/null
+++ b/frappe/public/js/frappe/views/workspace/blocks/header_size.js
@@ -0,0 +1,117 @@
+export default class HeaderSize {
+
+ static get isInline() {
+ return true;
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ set state(state) {
+ this._state = state;
+ }
+
+ get title() {
+ return 'Header Size';
+ }
+
+ constructor({api}) {
+ this.api = api;
+ this.button = null;
+ this._state = true;
+ this.selectedText = null;
+ this.range = null;
+ this.headerLevels = [];
+ }
+
+ render() {
+ this.button = document.createElement('button');
+ this.button.type = 'button';
+ this.button.innerHTML = `${frappe.utils.icon('header', 'sm')}${frappe.utils.icon('small-down', 'xs')}`;
+ this.button.classList = 'header-inline-tool';
+
+ return this.button;
+ }
+
+ checkState(selection) {
+ let termWrapper = this.api.selection.findParentTag('SPAN');
+
+ for (const h of ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) {
+ if (termWrapper && termWrapper.classList.contains(h)) {
+ let num = h.match(/\d+/)[0];
+ $('.header-inline-tool svg:first-child').replaceWith(frappe.utils.icon(`header-${num}`, 'md'));
+ }
+ }
+
+ const text = selection.anchorNode;
+ if (!text) return;
+ }
+
+ change_size(range, size) {
+ if (!range) return;
+
+ let span = document.createElement('SPAN');
+
+ span.classList.add(`h${size}`);
+ span.innerText = range.toString();
+
+ this.remove_parent_tag(range, range.commonAncestorContainer, span);
+
+ range.extractContents();
+ range.insertNode(span);
+ this.api.inlineToolbar.close();
+ }
+
+ remove_parent_tag(range, parent_node, span) {
+ let diff = range.startContainer.data;
+ let selected_text = span.innerText;
+ let parent_tag = parent_node.parentElement;
+
+ if (diff !== selected_text) {
+ parent_tag = parent_node;
+ }
+
+ if (parent_tag.innerText == selected_text) {
+ if (!parent_tag.classList.contains('ce-header') && !parent_tag.classList.contains('ce-paragraph')) {
+ this.remove_parent_tag(range, parent_node.parentElement, span);
+ parent_tag.remove();
+ }
+ }
+ }
+
+ surround(range) {
+ this.selectedText = range.cloneContents();
+ this.actions.hidden = !this.actions.hidden;
+ this.range = !this.actions.hidden ? range : null;
+ this.state = !this.actions.hidden;
+ }
+
+ renderActions() {
+ this.actions = document.createElement('div');
+ this.actions.classList = 'header-level-select';
+
+ this.headerLevels = new Array(6).fill().map((_, idx) => {
+ const $header_level = document.createElement('div');
+ $header_level.classList.add(`h${idx+1}`, 'header-level');
+ $header_level.innerText = `Header ${idx+1}`;
+ return $header_level;
+ });
+
+ for (const [i, headerLevel] of this.headerLevels.entries()) {
+ this.actions.appendChild(headerLevel);
+ this.api.listeners.on(headerLevel, 'click', () => {
+ this.change_size(this.range, i+1);
+ });
+ }
+
+ this.actions.hidden = true;
+ return this.actions;
+ }
+
+ destroy() {
+ for (const headerLevel of this.headerLevels) {
+ this.api.listeners.off(headerLevel, 'click');
+ }
+ }
+}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/blocks/index.js b/frappe/public/js/frappe/views/workspace/blocks/index.js
index 00a9b8c83a..5fac17bd02 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/index.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/index.js
@@ -8,11 +8,11 @@ import Spacer from "./spacer";
import Onboarding from "./onboarding";
// import tunes
-import SpacingTune from "./spacing_tune";
+import HeaderSize from "./header_size";
-frappe.provide("frappe.wspace_block");
+frappe.provide("frappe.workspace_block");
-frappe.wspace_block.blocks = {
+frappe.workspace_block.blocks = {
header: Header,
paragraph: Paragraph,
card: Card,
@@ -22,6 +22,6 @@ frappe.wspace_block.blocks = {
onboarding: Onboarding,
};
-frappe.wspace_block.tunes = {
- spacing_tune: SpacingTune
+frappe.workspace_block.tunes = {
+ header_size: HeaderSize,
};
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
index 7176b7726d..c0ba529853 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js
@@ -4,7 +4,7 @@ export default class Onboarding extends Block {
static get toolbox() {
return {
title: 'Onboarding',
- icon: ''
+ icon: frappe.utils.icon('onboarding', 'sm')
};
}
@@ -21,19 +21,21 @@ export default class Onboarding extends Block {
allow_create: this.allow_customization,
allow_delete: this.allow_customization,
allow_hiding: false,
- allow_edit: true
+ allow_edit: true,
+ allow_resize: false
};
}
rendered() {
- var e = this.wrapper.closest('.ce-block');
+ let block = this.wrapper.closest('.ce-block');
if (this.readOnly && !$(this.wrapper).find('.onboarding-widget-box').is(':visible')) {
- $(e).hide();
+ $(block).hide();
}
- e.classList.add("col-" + this.get_col());
+ this.set_col_class(block, this.get_col());
}
new(block, widget_type = block) {
+ let me = this;
const dialog_class = get_dialog_constructor(widget_type);
let block_name = block+'_name';
this.dialog = new dialog_class({
@@ -54,13 +56,18 @@ export default class Onboarding extends Block {
});
this.block_widget.customize(this.options);
this.wrapper.setAttribute(block_name, this.block_widget.label || this.block_widget.onboarding_name);
+ $(this.wrapper).find('.widget').addClass(`${widget_type} edit-mode`);
this.new_block_widget = this.block_widget.get_config();
- this.add_tune_button();
+ this.add_settings_button();
},
});
if (!this.readOnly && this.data && !this.data[block_name]) {
this.dialog.make();
+
+ this.dialog.dialog.get_close_btn().click(() => {
+ me.wrapper.closest('.ce-block').remove();
+ });
}
}
@@ -105,7 +112,9 @@ export default class Onboarding extends Block {
}
if (!this.readOnly) {
- this.add_tune_button();
+ $(this.wrapper).find('.widget').addClass('onboarding edit-mode');
+ this.add_settings_button();
+ this.add_new_block_button();
}
$(this.wrapper).css("padding-bottom", "20px");
return this.wrapper;
@@ -119,9 +128,9 @@ export default class Onboarding extends Block {
return true;
}
- save(blockContent) {
+ save() {
return {
- onboarding_name: blockContent.getAttribute('onboarding_name'),
+ onboarding_name: this.wrapper.getAttribute('onboarding_name'),
col: this.get_col(),
new: this.new_block_widget
};
diff --git a/frappe/public/js/frappe/views/workspace/blocks/paragraph.js b/frappe/public/js/frappe/views/workspace/blocks/paragraph.js
index 9e5dfb68ff..70f97c44c1 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/paragraph.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/paragraph.js
@@ -27,6 +27,8 @@ export default class Paragraph extends Block {
}
onKeyUp(e) {
+ if (!this.wrapper) return;
+ this.show_hide_block_list(true);
if (e.code !== 'Backspace' && e.code !== 'Delete') {
return;
}
@@ -34,55 +36,86 @@ export default class Paragraph extends Block {
const {textContent} = this._element;
if (textContent === '') {
+ this.show_hide_block_list();
this._element.innerHTML = '';
}
}
+ show_hide_block_list(hide) {
+ let $wrapper = $(this.wrapper).hasClass('ce-paragraph') ? $(this.wrapper.parentElement) : $(this.wrapper);
+ let $block_list_container = $wrapper.find('.block-list-container.dropdown-list');
+ $block_list_container.removeClass('hidden');
+ hide && $block_list_container.addClass('hidden');
+ }
+
drawView() {
let div = document.createElement('DIV');
div.classList.add(this._CSS.wrapper, this._CSS.block, 'widget');
div.contentEditable = false;
- div.dataset.placeholder = this.api.i18n.t(this._placeholder);
if (!this.readOnly) {
div.contentEditable = true;
+ div.addEventListener('focus', () => {
+ const {textContent} = this._element;
+ if (textContent !== '') return;
+ this.show_hide_block_list();
+ });
+ div.addEventListener('blur', () => {
+ setTimeout(() => this.show_hide_block_list(true), 10);
+ });
+ div.dataset.placeholder = this.api.i18n.t(this._placeholder);
div.addEventListener('keyup', this.onKeyUp);
}
return div;
}
+ open_block_list() {
+ let dropdown_title = 'Templates';
+ let $block_list_container = $(`
+
+
${dropdown_title.toUpperCase()}
+
+ `);
+
+ let all_blocks = frappe.workspace_block.blocks;
+ Object.keys(all_blocks).forEach(key => {
+ let $block_list_item = $(`
+
+ ${all_blocks[key].toolbox.icon}
+ ${__(all_blocks[key].toolbox.title)}
+
+ `);
+
+ $block_list_item.click(event => {
+ event.stopPropagation();
+ const index = this.api.blocks.getCurrentBlockIndex();
+ this.api.blocks.delete();
+ this.api.blocks.insert(key, {}, {}, index);
+ this.api.caret.setToBlock(index);
+ });
+
+ $block_list_container.append($block_list_item);
+ });
+
+ $block_list_container.addClass('hidden');
+ $block_list_container.appendTo(this.wrapper);
+ }
+
render() {
this.wrapper = document.createElement('div');
- this.wrapper.contentEditable = this.readOnly ? 'false' : 'true';
if (!this.readOnly) {
- let $para_control = $(``);
+ let $para_control = $(``);
this.wrapper.appendChild(this._element);
this._element.classList.remove('widget');
$para_control.appendTo(this.wrapper);
- this.wrapper.classList.add('widget');
+ this.wrapper.classList.add('widget', 'paragraph', 'edit-mode');
- frappe.utils.add_custom_button(
- frappe.utils.icon('dot-horizontal', 'xs'),
- (event) => {
- let evn = event;
- !$('.ce-settings.ce-settings--opened').length &&
- setTimeout(() => {
- this.api.toolbar.toggleBlockSettings();
- var position = $(evn.target).offset();
- $('.ce-settings.ce-settings--opened').offset({
- top: position.top + 25,
- left: position.left - 77
- });
- }, 50);
- },
- "tune-btn",
- `${__('Tune')}`,
- null,
- $para_control
- );
+ this.open_block_list();
+ this.add_new_block_button();
+ this.add_settings_button();
frappe.utils.add_custom_button(
frappe.utils.icon('drag', 'xs'),
@@ -93,15 +126,6 @@ export default class Paragraph extends Block {
$para_control
);
- frappe.utils.add_custom_button(
- frappe.utils.icon('delete', 'xs'),
- () => this.api.blocks.delete(),
- "delete-paragraph",
- `${__('Delete')}`,
- null,
- $para_control
- );
-
return this.wrapper;
}
return this._element;
@@ -132,8 +156,7 @@ export default class Paragraph extends Block {
}
rendered() {
- var e = this._element.closest('.ce-block');
- e.classList.add("col-" + this.get_col());
+ super.rendered(this._element);
}
onPaste(event) {
@@ -144,20 +167,14 @@ export default class Paragraph extends Block {
this.data = data;
}
- static get conversionConfig() {
- return {
- export: 'text', // to convert Paragraph to other block, use 'text' property of saved data
- import: 'text' // to covert other block's exported string to Paragraph, fill 'text' property of tool data
- };
- }
-
static get sanitize() {
return {
text: {
br: true,
b: true,
i: true,
- a: true
+ a: true,
+ span: true
}
};
}
@@ -188,8 +205,8 @@ export default class Paragraph extends Block {
static get toolbox() {
return {
- icon: '',
- title: 'Text'
+ title: 'Text',
+ icon: frappe.utils.icon('text', 'sm')
};
}
}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/blocks/shortcut.js b/frappe/public/js/frappe/views/workspace/blocks/shortcut.js
index 96b8f47484..2be5da0d4b 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/shortcut.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/shortcut.js
@@ -3,7 +3,7 @@ export default class Shortcut extends Block {
static get toolbox() {
return {
title: 'Shortcut',
- icon: ''
+ icon: frappe.utils.icon('shortcut', 'sm')
};
}
@@ -13,17 +13,39 @@ export default class Shortcut extends Block {
constructor({ data, api, config, readOnly, block }) {
super({ data, api, config, readOnly, block });
- this.col = this.data.col ? this.data.col : "4";
+ this.col = this.data.col ? this.data.col : "3";
this.allow_customization = !this.readOnly;
this.options = {
allow_sorting: this.allow_customization,
allow_create: this.allow_customization,
allow_delete: this.allow_customization,
allow_hiding: false,
- allow_edit: true
+ allow_edit: true,
+ allow_resize: true
};
}
+ rendered() {
+ super.rendered();
+
+ this.remove_last_divider();
+ $(window).resize(() => {
+ this.remove_last_divider();
+ });
+ }
+
+ remove_last_divider() {
+ let block = this.wrapper.closest('.ce-block');
+ let container_offset_right = $('.layout-main-section')[0].offsetWidth;
+ let block_offset_right = block.offsetLeft + block.offsetWidth;
+
+ if (container_offset_right - block_offset_right <= 110) {
+ $(block).find('.divider').addClass('hidden');
+ } else {
+ $(block).find('.divider').removeClass('hidden');
+ }
+ }
+
render() {
this.wrapper = document.createElement('div');
this.new('shortcut');
@@ -34,7 +56,14 @@ export default class Shortcut extends Block {
}
if (!this.readOnly) {
- this.add_tune_button();
+ $(this.wrapper).find('.widget').addClass('shortcut edit-mode');
+ this.add_settings_button();
+ this.add_new_block_button();
+ } else {
+ let $shortcut_icon = frappe.utils.icon('arrow-up-right', 'xs', '', 'stroke: grey', 'ml-2');
+ $(this.wrapper).find('.widget .widget-title').append($shortcut_icon);
+
+ $(this.wrapper).append($(``));
}
return this.wrapper;
}
@@ -47,9 +76,9 @@ export default class Shortcut extends Block {
return true;
}
- save(blockContent) {
+ save() {
return {
- shortcut_name: blockContent.getAttribute('shortcut_name'),
+ shortcut_name: this.wrapper.getAttribute('shortcut_name'),
col: this.get_col(),
new: this.new_block_widget
};
diff --git a/frappe/public/js/frappe/views/workspace/blocks/spacer.js b/frappe/public/js/frappe/views/workspace/blocks/spacer.js
index 3309cad4a4..bb75cea873 100644
--- a/frappe/public/js/frappe/views/workspace/blocks/spacer.js
+++ b/frappe/public/js/frappe/views/workspace/blocks/spacer.js
@@ -3,7 +3,7 @@ export default class Spacer extends Block {
static get toolbox() {
return {
title: 'Spacer',
- icon: ''
+ icon: frappe.utils.icon('spacer', 'sm')
};
}
@@ -18,40 +18,24 @@ export default class Spacer extends Block {
render() {
this.wrapper = document.createElement('div');
+ this.wrapper.classList.add('widget', 'spacer');
if (!this.readOnly) {
let $spacer = $(`
`);
$spacer.appendTo(this.wrapper);
- this.wrapper.classList.add('widget', 'new-widget');
- this.wrapper.style.minHeight = 50 + 'px';
+ this.wrapper.classList.add('edit-mode');
+ this.wrapper.style.minHeight = 40 + 'px';
let $widget_control = $spacer.find('.widget-control');
- frappe.utils.add_custom_button(
- frappe.utils.icon('dot-horizontal', 'xs'),
- (event) => {
- let evn = event;
- !$('.ce-settings.ce-settings--opened').length &&
- setTimeout(() => {
- this.api.toolbar.toggleBlockSettings();
- var position = $(evn.target).offset();
- $('.ce-settings.ce-settings--opened').offset({
- top: position.top + 25,
- left: position.left - 77
- });
- }, 50);
- },
- "tune-btn",
- `${__('Tune')}`,
- null,
- $widget_control
- );
+ this.add_settings_button();
+ this.add_new_block_button();
frappe.utils.add_custom_button(
frappe.utils.icon('drag', 'xs'),
@@ -61,15 +45,6 @@ export default class Spacer extends Block {
null,
$widget_control
);
-
- frappe.utils.add_custom_button(
- frappe.utils.icon('delete', 'xs'),
- () => this.api.blocks.delete(),
- "delete-spacer",
- `${__('Delete')}`,
- null,
- $widget_control
- );
}
return this.wrapper;
}
diff --git a/frappe/public/js/frappe/views/workspace/blocks/spacing_tune.js b/frappe/public/js/frappe/views/workspace/blocks/spacing_tune.js
deleted file mode 100644
index 365f7f590e..0000000000
--- a/frappe/public/js/frappe/views/workspace/blocks/spacing_tune.js
+++ /dev/null
@@ -1,123 +0,0 @@
-export default class SpacingTune {
- static get isTune() {
- return true;
- }
-
- constructor({api, settings}) {
- this.api = api;
- this.settings = settings;
- this.CSS = {
- button: 'ce-settings__button',
- wrapper: 'ce-tune-layout',
- sidebar: 'cdx-settings-sidebar',
- animation: 'wobble',
- };
- this.data = { colWidth: 12 };
- this.wrapper = undefined;
- this.sidebar = undefined;
- }
-
- render() {
- let me = this;
- let layoutWrapper = document.createElement('div');
- layoutWrapper.classList.add(this.CSS.wrapper);
- let decreaseWidthButton = document.createElement('div');
- decreaseWidthButton.classList.add(this.CSS.button, 'ce-shrink-button');
- let increaseWidthButton = document.createElement('div');
- increaseWidthButton.classList.add(this.CSS.button, 'ce-expand-button');
-
- layoutWrapper.appendChild(decreaseWidthButton);
- layoutWrapper.appendChild(increaseWidthButton);
-
- decreaseWidthButton.innerHTML = ``;
- this.api.tooltip.onHover(decreaseWidthButton, 'Shrink', {
- placement: 'top',
- hidingDelay: 500,
- });
- this.api.listeners.on(
- decreaseWidthButton,
- 'click',
- () => me.decreaseWidth(),
- false
- );
-
- increaseWidthButton.innerHTML = ``;
- this.api.tooltip.onHover(increaseWidthButton, 'Expand', {
- placement: 'top',
- hidingDelay: 500,
- });
- this.api.listeners.on(
- increaseWidthButton,
- 'click',
- () => me.increaseWidth(),
- false
- );
-
- this.wrapper = layoutWrapper;
- return layoutWrapper;
- }
-
- decreaseWidth() {
- const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
-
- if (currentBlockIndex < 0) {
- return;
- }
-
- let currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
- if (!currentBlock) {
- return;
- }
-
- let currentBlockElement = currentBlock.holder;
-
- let className = 'col-12';
- let colClass = new RegExp(/\bcol-.+?\b/, 'g');
- if (currentBlockElement.className.match(colClass)) {
- currentBlockElement.classList.forEach( cn => {
- if (cn.match(colClass)) {
- className = cn;
- }
- });
- let parts = className.split('-');
- let width = parseInt(parts[1]);
- if (width >= 4) {
- currentBlockElement.classList.remove('col-'+width);
- width = width - 1;
- currentBlockElement.classList.add('col-'+width);
- }
- }
- }
-
- increaseWidth() {
- const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
-
- if (currentBlockIndex < 0) {
- return;
- }
-
- const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
- if (!currentBlock) {
- return;
- }
-
- const currentBlockElement = currentBlock.holder;
-
- let className = 'col-12';
- const colClass = new RegExp(/\bcol-.+?\b/, 'g');
- if (currentBlockElement.className.match(colClass)) {
- currentBlockElement.classList.forEach( cn => {
- if (cn.match(colClass)) {
- className = cn;
- }
- });
- let parts = className.split('-');
- let width = parseInt(parts[1]);
- if (width <= 11) {
- currentBlockElement.classList.remove('col-'+width);
- width = width + 1;
- currentBlockElement.classList.add('col-'+width);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js
index e6248f66cf..e28225904d 100644
--- a/frappe/public/js/frappe/views/workspace/workspace.js
+++ b/frappe/public/js/frappe/views/workspace/workspace.js
@@ -20,13 +20,11 @@ frappe.views.Workspace = class Workspace {
constructor(wrapper) {
this.wrapper = $(wrapper);
this.page = wrapper.page;
- this.blocks = frappe.wspace_block.blocks;
+ this.blocks = frappe.workspace_block.blocks;
this.is_read_only = true;
- this.new_page = null;
this.pages = {};
this.sorted_public_items = [];
this.sorted_private_items = [];
- this.deleted_sidebar_items = [];
this.current_page = {};
this.sidebar_items = {
'public': {},
@@ -52,7 +50,10 @@ frappe.views.Workspace = class Workspace {
}
async setup_pages(reload) {
+ !this.discard && this.create_page_skeleton();
+ !this.discard && this.create_sidebar_skeleton();
this.sidebar_pages = !this.discard ? await this.get_pages() : this.sidebar_pages;
+ this.cached_pages = $.extend(true, {}, this.sidebar_pages);
this.all_pages = this.sidebar_pages.pages;
this.has_access = this.sidebar_pages.has_access;
@@ -68,24 +69,13 @@ frappe.views.Workspace = class Workspace {
for (let page of this.all_pages) {
frappe.workspaces[frappe.router.slug(page.name)] = {title: page.title};
}
- if (this.new_page && this.new_page.name) {
- if (!frappe.workspaces[frappe.router.slug(this.new_page.label)]) {
- this.new_page = { name: this.all_pages[0].title, public: this.all_pages[0].public };
- }
- if (this.new_page.public) {
- frappe.set_route(`${frappe.router.slug(this.new_page.name)}`);
- } else {
- frappe.set_route(`private/${frappe.router.slug(this.new_page.name)}`);
- }
- this.new_page = null;
- }
this.make_sidebar();
reload && this.show();
}
}
get_pages() {
- return frappe.xcall("frappe.desk.desktop.get_wspace_sidebar_items");
+ return frappe.xcall("frappe.desk.desktop.get_workspace_sidebar_items");
}
sidebar_item_container(item) {
@@ -101,6 +91,7 @@ frappe.views.Workspace = class Workspace {
+
`);
}
@@ -119,8 +110,10 @@ frappe.views.Workspace = class Workspace {
});
// Scroll sidebar to selected page if it is not in viewport.
- !frappe.dom.is_element_in_viewport(this.sidebar.find('.selected'))
+ this.sidebar.find('.selected').length && !frappe.dom.is_element_in_viewport(this.sidebar.find('.selected'))
&& this.sidebar.find('.selected')[0].scrollIntoView();
+
+ this.remove_sidebar_skeleton();
}
build_sidebar_section(title, root_pages) {
@@ -164,7 +157,8 @@ frappe.views.Workspace = class Workspace {
let child_items = pages.filter(page => page.parent_page == item.title);
if (child_items.length > 0) {
- let child_container = $(``);
+ let child_container = $item_container.find('.sidebar-child-item');
+ child_container.addClass('hidden');
this.prepare_sidebar(child_items, child_container, $item_container);
}
@@ -179,18 +173,23 @@ frappe.views.Workspace = class Workspace {
}
add_drop_icon(item, sidebar_control, item_container) {
+ let drop_icon = 'small-down';
+ if (item_container.find(`[item-name="${this.current_page.name}"]`).length) {
+ drop_icon = 'small-up';
+ }
+
let $child_item_section = item_container.find('.sidebar-child-item');
- let $drop_icon = $(`${frappe.utils.icon("small-down", "sm")}`)
+ let $drop_icon = $(`${frappe.utils.icon(drop_icon, "sm")}`)
.appendTo(sidebar_control);
let pages = item.public ? this.public_pages : this.private_pages;
if (pages.some(e => e.parent_page == item.title)) {
$drop_icon.removeClass('hidden');
- $drop_icon.on('click', () => {
- let icon = $drop_icon.find("use").attr("href")==="#icon-small-down" ? "#icon-small-up" : "#icon-small-down";
- $drop_icon.find("use").attr("href", icon);
- $child_item_section.toggleClass("hidden");
- });
}
+ $drop_icon.on('click', () => {
+ let icon = $drop_icon.find("use").attr("href")==="#icon-small-down" ? "#icon-small-up" : "#icon-small-down";
+ $drop_icon.find("use").attr("href", icon);
+ $child_item_section.toggleClass("hidden");
+ });
}
show() {
@@ -203,21 +202,48 @@ frappe.views.Workspace = class Workspace {
let page = this.get_page_to_show();
this.page.set_title(`${__(page.name)}`);
+ this.update_selected_sidebar(this.current_page, false); //remove selected from old page
+ this.update_selected_sidebar(page, true); //add selected on new page
+
this.show_page(page);
}
+ update_selected_sidebar(page, add) {
+ let section = page.public ? 'public' : 'private';
+ if (this.sidebar && this.sidebar_items[section] && this.sidebar_items[section][page.name]) {
+ let $sidebar = this.sidebar_items[section][page.name];
+ let pages = page.public ? this.public_pages : this.private_pages;
+ let sidebar_page = pages.find(p => p.title == page.name);
+
+ if (add) {
+ $sidebar[0].firstElementChild.classList.add("selected");
+ if (sidebar_page) sidebar_page.selected = true;
+
+ // open child sidebar section if closed
+ $sidebar.parent().hasClass('hidden') &&
+ $sidebar.parent().removeClass('hidden');
+
+ this.current_page = { name: page.name, public: page.public };
+ localStorage.current_page = page.name;
+ localStorage.is_current_page_public = page.public;
+ } else {
+ $sidebar[0].firstElementChild.classList.remove("selected");
+ if (sidebar_page) sidebar_page.selected = false;
+ }
+ }
+ }
+
get_data(page) {
- return frappe.xcall("frappe.desk.desktop.get_desktop_page", {
+ return frappe.call("frappe.desk.desktop.get_desktop_page", {
page: page
}).then(data => {
- this.page_data = data;
+ this.page_data = data.message;
// caching page data
this.pages[page.name] && delete this.pages[page.name];
- this.pages[page.name] = data;
+ this.pages[page.name] = data.message;
if (!this.page_data || Object.keys(this.page_data).length === 0) return;
-
if (this.page_data.charts && this.page_data.charts.items.length === 0) return;
return frappe.dashboard_utils.get_dashboard_settings().then(settings => {
@@ -249,49 +275,35 @@ frappe.views.Workspace = class Workspace {
}
async show_page(page) {
- let section = this.current_page.public ? 'public' : 'private';
- if (this.sidebar_items && this.sidebar_items[section] && this.sidebar_items[section][this.current_page.name]) {
- this.sidebar_items[section][this.current_page.name][0].firstElementChild.classList.remove("selected");
- this.sidebar_items[page.public ? 'public':'private'][page.name][0].firstElementChild.classList.add("selected");
-
- if (this.sidebar_items[page.public ? 'public':'private'][page.name].parents('.sidebar-item-container')[0]) {
- this.sidebar_items[page.public ? 'public':'private'][page.name]
- .parents('.sidebar-item-container')
- .find('.drop-icon use')
- .attr("href", "#icon-small-up");
- }
- }
-
- this.current_page = { name: page.name, public: page.public };
- localStorage.current_page = page.name;
- localStorage.is_current_page_public = page.public;
-
if (!this.body.find('#editorjs')[0]) {
this.$page = $(`
`).appendTo(this.body);
}
- this.create_skeleton();
if (this.all_pages) {
+ this.create_page_skeleton();
+
let pages = page.public ? this.public_pages : this.private_pages;
- let this_page = pages.filter(p => p.title == page.name)[0];
- this.setup_actions(page);
- this.content = this_page && JSON.parse(this_page.content);
+ let current_page = pages.filter(p => p.title == page.name)[0];
+ this.content = current_page && JSON.parse(current_page.content);
this.add_custom_cards_in_content();
$('.item-anchor').addClass('disable-click');
- if (this.pages && this.pages[this_page.name]) {
- this.page_data = this.pages[this_page.name];
+ if (this.pages && this.pages[current_page.name]) {
+ this.page_data = this.pages[current_page.name];
} else {
- await this.get_data(this_page);
+ await frappe.after_ajax(() => this.get_data(current_page));
}
+ this.setup_actions(page);
+
this.prepare_editorjs();
$('.item-anchor').removeClass('disable-click');
- this.remove_skeleton();
+
+ this.remove_page_skeleton();
}
}
@@ -329,9 +341,7 @@ frappe.views.Workspace = class Workspace {
return;
}
- this.page.clear_primary_action();
- this.page.clear_secondary_action();
- this.page.clear_inner_toolbar();
+ this.clear_page_actions();
current_page.is_editable && this.page.set_secondary_action(__("Edit"), async () => {
if (!this.editor || !this.editor.readOnly) return;
@@ -341,7 +351,6 @@ frappe.views.Workspace = class Workspace {
this.initialize_editorjs_undo();
this.setup_customization_buttons(current_page);
this.show_sidebar_actions();
- this.make_sidebar_sortable();
this.make_blocks_sortable();
});
});
@@ -357,22 +366,25 @@ frappe.views.Workspace = class Workspace {
this.undo.readOnly = false;
}
- setup_customization_buttons(page) {
- let me = this;
+ clear_page_actions() {
this.page.clear_primary_action();
this.page.clear_secondary_action();
this.page.clear_inner_toolbar();
+ }
+
+ setup_customization_buttons(page) {
+ this.clear_page_actions();
page.is_editable && this.page.set_primary_action(
__("Save Customizations"),
() => {
- this.page.clear_primary_action();
- this.page.clear_secondary_action();
- this.page.clear_inner_toolbar();
- this.undo.readOnly = true;
- this.save_page();
- this.editor.readOnly.toggle();
- this.is_read_only = true;
+ this.clear_page_actions();
+ this.save_page(page).then((saved) => {
+ if (!saved) return;
+ this.undo.readOnly = true;
+ this.editor.readOnly.toggle();
+ this.is_read_only = true;
+ });
},
null,
__("Saving")
@@ -382,11 +394,10 @@ frappe.views.Workspace = class Workspace {
__("Discard"),
async () => {
this.discard = true;
- this.page.clear_primary_action();
- this.page.clear_secondary_action();
- this.page.clear_inner_toolbar();
+ this.clear_page_actions();
await this.editor.readOnly.toggle();
this.is_read_only = true;
+ this.sidebar_pages = this.cached_pages;
this.reload();
frappe.show_alert({ message: __("Customizations Discarded"), indicator: "info" });
}
@@ -395,34 +406,30 @@ frappe.views.Workspace = class Workspace {
page.name && this.page.add_inner_button(__("Settings"), () => {
frappe.set_route(`workspace/${page.name}`);
});
-
- Object.keys(this.blocks).forEach(key => {
- this.page.add_inner_button(`
-
-
- `, function() {
- const index = me.editor.blocks.getBlocksCount() + 1;
- me.editor.blocks.insert(key, {}, {}, index, true);
- me.editor.caret.setToLastBlock('start', 0);
- $('.ce-block:last-child')[0].scrollIntoView();
- }, __('Add Block'));
- });
}
show_sidebar_actions() {
this.sidebar.find('.standard-sidebar-section').addClass('show-control');
+ this.make_sidebar_sortable();
}
- add_sidebar_actions(item, sidebar_control) {
+ add_sidebar_actions(item, sidebar_control, is_new) {
if (!item.is_editable) {
- $(``)
- .appendTo(sidebar_control);
sidebar_control.parent().click(() => {
!this.is_read_only && frappe.show_alert({
message: __("Only Workspace Manager can sort or edit this page"),
indicator: 'info'
}, 5);
});
+
+ frappe.utils.add_custom_button(
+ frappe.utils.icon('duplicate', 'sm'),
+ () => this.duplicate_page(item),
+ "duplicate-page",
+ `${__('Duplicate Workspace')}`,
+ null,
+ sidebar_control
+ );
} else {
frappe.utils.add_custom_button(
frappe.utils.icon('drag', 'xs'),
@@ -432,24 +439,380 @@ frappe.views.Workspace = class Workspace {
null,
sidebar_control
);
- frappe.utils.add_custom_button(
- frappe.utils.icon('delete', 'xs'),
- () => this.delete_page(item),
- "delete-page",
- `${__('Delete')}`,
- null,
- sidebar_control
- );
+
+ !is_new && this.add_settings_button(item, sidebar_control);
+ }
+ }
+
+ get_parent_pages(page) {
+ this.public_parent_pages = ['', ...this.public_pages.filter(p => !p.parent_page).map(p => p.title)];
+ this.private_parent_pages = ['', ...this.private_pages.filter(p => !p.parent_page).map(p => p.title)];
+
+ if (page) {
+ return page.public ? this.public_parent_pages : this.private_parent_pages;
}
}
- delete_page(item) {
- frappe.confirm(__("Are you sure you want to delete page {0}?", [item.title]), () => {
- this.deleted_sidebar_items.push(item);
- this.sidebar.find(`.standard-sidebar-section [item-name="${item.title}"][item-public="${item.public}"]`).addClass('hidden');
+ edit_page(item) {
+ var me = this;
+ let old_item = item;
+ let parent_pages = this.get_parent_pages(item);
+ let idx = parent_pages.findIndex(x => x == item.title);
+ if (idx !== -1) parent_pages.splice(idx, 1);
+ const d = new frappe.ui.Dialog({
+ title: __('Update Details'),
+ fields: [
+ {
+ label: __('Title'),
+ fieldtype: 'Data',
+ fieldname: 'title',
+ reqd: 1,
+ default: item.title
+ },
+ {
+ label: __('Parent'),
+ fieldtype: 'Select',
+ fieldname: 'parent',
+ options: parent_pages,
+ default: item.parent_page
+ },
+ {
+ label: __('Public'),
+ fieldtype: 'Check',
+ fieldname: 'is_public',
+ depends_on: `eval:${this.has_access}`,
+ default: item.public,
+ onchange: function() {
+ d.set_df_property('parent', 'options',
+ this.get_value() ? me.public_parent_pages : me.private_parent_pages);
+ }
+ },
+ {
+ fieldtype: 'Column Break'
+ },
+ {
+ label: __('Icon'),
+ fieldtype: 'Icon',
+ fieldname: 'icon',
+ default: item.icon
+ },
+ ],
+ primary_action_label: __('Update'),
+ primary_action: (values) => {
+ let is_title_changed = values.title != old_item.title;
+ let is_section_changed = values.is_public != old_item.public;
+ if ((is_title_changed || is_section_changed) && !this.validate_page(values, old_item)) return;
+ d.hide();
+
+ frappe.call({
+ method: "frappe.desk.doctype.workspace.workspace.update_page",
+ args: {
+ name: old_item.name,
+ title: values.title,
+ icon: values.icon || '',
+ parent: values.parent || '',
+ public: values.is_public || 0,
+ },
+ callback: function(res) {
+ if (res.message) {
+ let message = `Workspace ${old_item.title} Edited Successfully`;
+ frappe.show_alert({ message: __(message), indicator: "green" });
+ }
+ }
+ });
+
+ this.update_sidebar(old_item, values);
+
+ if (this.make_page_selected) {
+ let pre_url = values.is_public ? '' : 'private/';
+ let route = pre_url + frappe.router.slug(values.title);
+ frappe.set_route(route);
+
+ this.make_page_selected = false;
+ }
+
+ this.make_sidebar();
+ this.show_sidebar_actions();
+ }
+ });
+ d.show();
+ }
+
+ update_sidebar(old_item, new_item) {
+ let is_section_changed = old_item.public != (new_item.is_public || 0);
+ let is_title_changed = old_item.title != new_item.title;
+ let new_updated_item = {...old_item};
+
+ let pages = old_item.public ? this.public_pages : this.private_pages;
+
+ let child_items = pages.filter(page => page.parent_page == old_item.title);
+
+ this.make_page_selected = old_item.selected;
+
+ new_updated_item.title = new_item.title;
+ new_updated_item.icon = new_item.icon;
+ new_updated_item.parent_page = new_item.parent || "";
+ new_updated_item.public = new_item.is_public;
+
+ if (is_title_changed || is_section_changed) {
+ if (new_item.is_public) {
+ new_updated_item.name = new_item.title;
+ new_updated_item.label = new_item.title;
+ new_updated_item.for_user = "";
+ } else {
+ let user = frappe.session.user;
+ new_updated_item.name = `${new_item.title}-${user}`;
+ new_updated_item.label = `${new_item.title}-${user}`;
+ new_updated_item.for_user = user;
+ }
+ }
+ this.update_cached_values(old_item, new_updated_item);
+
+ if (child_items.length) {
+ child_items.forEach(child => {
+ child.parent_page = new_item.title;
+ is_section_changed && this.update_child_sidebar(child, new_item);
+ });
+ }
+ }
+
+ update_child_sidebar(child, new_item) {
+ let old_child = {...child};
+ this.make_page_selected = child.selected;
+
+ child.public = new_item.is_public;
+ if (new_item.is_public) {
+ child.name = child.title;
+ child.label = child.title;
+ child.for_user = "";
+ } else {
+ let user = frappe.session.user;
+ child.name = `${child.title}-${user}`;
+ child.label = `${child.title}-${user}`;
+ child.for_user = user;
+ }
+
+ this.update_cached_values(old_child, child);
+ }
+
+ update_cached_values(old_item, new_item, duplicate, new_page) {
+ let [from_pages, to_pages] = old_item.public ?
+ [this.public_pages, this.private_pages] : [this.private_pages, this.public_pages];
+
+ let old_item_index = from_pages.findIndex(page => page.title == old_item.title);
+ duplicate && old_item_index++;
+
+ // update frappe.workspaces
+ if (frappe.workspaces[frappe.router.slug(old_item.name)] || new_page) {
+ !duplicate && delete frappe.workspaces[frappe.router.slug(old_item.name)];
+ if (new_item) {
+ frappe.workspaces[frappe.router.slug(new_item.name)] = {'title': new_item.title};
+ }
+ }
+
+ // update page block data
+ if (this.pages && this.pages[old_item.name] || new_page) {
+ if (new_item) {
+ this.pages[new_item.name] = this.pages[old_item.name] || {};
+ }
+ !duplicate && delete this.pages[old_item.name];
+ }
+
+ // update public and private pages
+ if (new_item) {
+ let is_section_changed = old_item.public != (new_item.is_public || new_item.public || 0);
+
+ if (is_section_changed) {
+ !duplicate && from_pages.splice(old_item_index, 1);
+ to_pages.push(new_item);
+ } else if (new_page) {
+ from_pages.push(new_item);
+ } else {
+ from_pages.splice(old_item_index, duplicate ? 0 : 1, new_item);
+ }
+ } else {
+ from_pages.splice(old_item_index, 1);
+ }
+
+ this.sidebar_pages.pages = [...this.public_pages, ...this.private_pages];
+ this.cached_pages = this.sidebar_pages;
+ }
+
+ add_settings_button(item, sidebar_control) {
+ this.dropdown_list = [
+ {
+ label: 'Edit',
+ title: 'Edit Workspace',
+ icon: frappe.utils.icon('edit', 'sm'),
+ action: () => this.edit_page(item)
+ },
+ {
+ label: 'Duplicate',
+ title: 'Duplicate Workspace',
+ icon: frappe.utils.icon('duplicate', 'sm'),
+ action: () => this.duplicate_page(item)
+ },
+ {
+ label: 'Delete',
+ title: 'Delete Workspace',
+ icon: frappe.utils.icon('delete-active', 'sm'),
+ action: () => this.delete_page(item)
+ }
+ ];
+
+ let $button = $(`
+
+ ${frappe.utils.icon('dot-horizontal', 'xs')}
+
+
+ `);
+
+ let dropdown_item = function(label, title, icon, action) {
+ let html = $(`
+
+ ${icon}
+ ${label}
+
+ `);
+
+ html.click(event => {
+ event.stopPropagation();
+ action && action();
+ });
+
+ return html;
+ };
+
+ $button.filter('.dropdown-btn').click(event => {
+ event.stopPropagation();
+ if ($button.filter('.dropdown-list.hidden').length) {
+ $('.dropdown-list:not(.hidden)').addClass('hidden');
+ }
+ $button.filter('.dropdown-list').toggleClass('hidden');
+ });
+
+ $(document).click(event => {
+ event.stopPropagation();
+ $('.dropdown-list:not(.hidden)').addClass('hidden');
+ });
+
+ sidebar_control.append($button);
+
+ this.dropdown_list.forEach((i) => {
+ $button.filter('.dropdown-list').append(dropdown_item(i.label, i.title, i.icon, i.action));
+ });
+ }
+
+ delete_page(page) {
+ frappe.confirm(__("Are you sure you want to delete page {0}?", [page.title]), () => {
+ frappe.call({
+ method: "frappe.desk.doctype.workspace.workspace.delete_page",
+ args: { page: page },
+ callback: function(res) {
+ if (res.message) {
+ let page = res.message;
+ let message = `Workspace ${page.title} Deleted Successfully`;
+ frappe.show_alert({ message: __(message), indicator: "green" });
+ }
+ }
+ });
+
+ this.page.clear_primary_action();
+ this.update_cached_values(page);
+
+ if (this.current_page.name == page.title && this.current_page.public == page.public) {
+ frappe.set_route('/');
+ }
+
+ this.make_sidebar();
+ this.show_sidebar_actions();
});
}
+ duplicate_page(page) {
+ var me = this;
+ let parent_pages = this.get_parent_pages(page);
+ const d = new frappe.ui.Dialog({
+ title: __('Create Duplicate'),
+ fields: [
+ {
+ label: __('Title'),
+ fieldtype: 'Data',
+ fieldname: 'title',
+ reqd: 1
+ },
+ {
+ label: __('Parent'),
+ fieldtype: 'Select',
+ fieldname: 'parent',
+ options: parent_pages,
+ default: page.parent_page
+ },
+ {
+ label: __('Public'),
+ fieldtype: 'Check',
+ fieldname: 'is_public',
+ depends_on: `eval:${this.has_access}`,
+ default: page.public,
+ onchange: function() {
+ d.set_df_property('parent', 'options',
+ this.get_value() ? me.public_parent_pages : me.private_parent_pages);
+ }
+ },
+ {
+ fieldtype: 'Column Break'
+ },
+ {
+ label: __('Icon'),
+ fieldtype: 'Icon',
+ fieldname: 'icon',
+ default: page.icon
+ },
+ ],
+ primary_action_label: __('Duplicate'),
+ primary_action: (values) => {
+ if (!this.validate_page(values)) return;
+ d.hide();
+ frappe.call({
+ method: "frappe.desk.doctype.workspace.workspace.duplicate_page",
+ args: {
+ page_name: page.name,
+ new_page: values
+ },
+ callback: function(res) {
+ if (res.message) {
+ let new_page = res.message;
+ let message = `Duplicate of ${page.title} named as ${new_page.title} is created successfully`;
+ frappe.show_alert({ message: __(message), indicator: "green" });
+ }
+ }
+ });
+
+ let new_page = {...page};
+
+ new_page.title = values.title;
+ new_page.public = values.is_public || 0;
+ new_page.name = values.title + (new_page.public ? '' : '-' + frappe.session.user);
+ new_page.label = new_page.name;
+ new_page.icon = values.icon;
+ new_page.parent_page = values.parent || '';
+ new_page.for_user = new_page.public ? '' : frappe.session.user;
+ new_page.is_editable = !new_page.public;
+ new_page.selected = true;
+
+ this.update_cached_values(page, new_page, true);
+
+ let pre_url = values.is_public ? '' : 'private/';
+ let route = pre_url + frappe.router.slug(values.title);
+ frappe.set_route(route);
+
+ me.make_sidebar();
+ me.show_sidebar_actions();
+ }
+ });
+ d.show();
+ }
+
make_sidebar_sortable() {
let me = this;
$('.nested-container').each( function() {
@@ -463,35 +826,75 @@ frappe.views.Workspace = class Workspace {
onEnd: function (evt) {
let is_public = $(evt.item).attr('item-public') == '1';
me.prepare_sorted_sidebar(is_public);
+ me.update_sorted_sidebar();
}
});
});
}
prepare_sorted_sidebar(is_public) {
+ let pages = is_public ? this.public_pages : this.private_pages;
if (is_public) {
- this.sorted_public_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').last());
+ this.sorted_public_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').last(), pages);
} else {
- this.sorted_private_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').first());
+ this.sorted_private_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').first(), pages);
}
+
+ this.sidebar_pages.pages = [...this.public_pages, ...this.private_pages];
+ this.cached_pages = this.sidebar_pages;
}
- sort_sidebar($sidebar_section) {
+ sort_sidebar($sidebar_section, pages) {
let sorted_items = [];
- for (let page of $sidebar_section.find('.sidebar-item-container')) {
+ Array.from($sidebar_section.find('.sidebar-item-container')).forEach((page, i) => {
let parent_page = "";
+
if (page.closest('.nested-container').classList.contains('sidebar-child-item')) {
parent_page = page.parentElement.parentElement.attributes["item-name"].value;
}
+
sorted_items.push({
title: page.attributes['item-name'].value,
parent_page: parent_page,
public: page.attributes['item-public'].value
});
- }
+
+ let $drop_icon = $(page).find('.sidebar-item-control .drop-icon').first();
+ if ($(page).find('.sidebar-child-item > *').length != 0) {
+ $drop_icon.removeClass('hidden');
+ } else {
+ $drop_icon.addClass('hidden');
+ }
+
+ let from_index = pages.findIndex(p => p.title == page.attributes['item-name'].value);
+ let element = pages[from_index];
+ element.parent_page = parent_page;
+ if (from_index != i) {
+ pages.splice(from_index, 1);
+ pages.splice(i, 0, element);
+ }
+ });
return sorted_items;
}
+ update_sorted_sidebar() {
+ if (this.sorted_public_items || this.sorted_private_items) {
+ frappe.call({
+ method: "frappe.desk.doctype.workspace.workspace.sort_pages",
+ args: {
+ sb_public_items: this.sorted_public_items,
+ sb_private_items: this.sorted_private_items,
+ },
+ callback: function(res) {
+ if (res.message) {
+ let message = `Sidebar Updated Successfully`;
+ frappe.show_alert({ message: __(message), indicator: "green" });
+ }
+ }
+ });
+ }
+ }
+
make_blocks_sortable() {
let me = this;
this.page_sortable = Sortable.create(this.page.main.find(".codex-editor__redactor").get(0), {
@@ -508,11 +911,10 @@ frappe.views.Workspace = class Workspace {
}
initialize_new_page() {
- this.public_parent_pages = ['', ...this.public_pages.filter(page => !page.parent_page).map(page => page.title)];
- this.private_parent_pages = ['', ...this.private_pages.filter(page => !page.parent_page).map(page => page.title)];
var me = this;
+ this.get_parent_pages();
const d = new frappe.ui.Dialog({
- title: __('Set Title'),
+ title: __('New Workspace'),
fields: [
{
label: __('Title'),
@@ -551,81 +953,115 @@ frappe.views.Workspace = class Workspace {
d.hide();
this.initialize_editorjs_undo();
this.setup_customization_buttons({is_editable: true});
- this.title = values.title;
- this.icon = values.icon;
- this.parent = values.parent;
- this.public = values.is_public;
+
+ let name = values.title + (values.is_public ? '' : '-' + frappe.session.user);
+ let blocks = [{
+ type: "header",
+ data: { text: values.title }
+ }];
+
+ let new_page = {
+ content: JSON.stringify(blocks),
+ name: name,
+ label: name,
+ title: values.title,
+ public: values.is_public || 0,
+ for_user: values.is_public ? '' : frappe.session.user,
+ icon: values.icon,
+ parent_page: values.parent || '',
+ is_editable: true,
+ selected: true
+ };
+
this.editor.render({
- blocks: [
- {
- type: "header",
- data: {
- text: this.title,
- level: 4
- }
- }
- ]
+ blocks: blocks
}).then(async () => {
if (this.editor.configuration.readOnly) {
this.is_read_only = false;
await this.editor.readOnly.toggle();
}
- this.add_page_to_sidebar(values);
+
+ frappe.call({
+ method: "frappe.desk.doctype.workspace.workspace.new_page",
+ args: {
+ new_page: new_page
+ },
+ callback: function(res) {
+ if (res.message) {
+ let message = `Workspace ${new_page.title} Created Successfully`;
+ frappe.show_alert({ message: __(message), indicator: "green" });
+ }
+ }
+ });
+
+ this.update_cached_values(new_page, new_page, true, true);
+
+ let pre_url = new_page.public ? '' : 'private/';
+ let route = pre_url + frappe.router.slug(new_page.title);
+ frappe.set_route(route);
+
+ this.make_sidebar();
this.show_sidebar_actions();
- this.make_sidebar_sortable();
- this.make_blocks_sortable();
- this.prepare_sorted_sidebar(values.is_public);
});
}
});
d.show();
}
- validate_page(values) {
+ validate_page(new_page, old_page) {
let message = "";
- let pages = values.is_public ? this.public_pages : this.private_pages;
+ let [from_pages, to_pages] = new_page.is_public ?
+ [this.private_pages, this.public_pages] : [this.public_pages, this.private_pages];
+
+ let section = this.sidebar_categories[new_page.is_public];
- if (pages && pages.filter(p => p.title == values.title)[0]) {
- message = "Page with title '{0}' already exist.";
- } else if (frappe.router.doctype_route_exist(frappe.router.slug(values.title))) {
+ if (to_pages && to_pages.filter(p => p.title == new_page.title)[0]) {
+ message = `Page with title ${new_page.title} already exist.`;
+ }
+
+ if (frappe.router.doctype_route_exist(frappe.router.slug(new_page.title))) {
message = "Doctype with same route already exist. Please choose different title.";
}
+ let child_pages = old_page && from_pages.filter(p => p.parent_page == old_page.title);
+ if (child_pages) {
+ child_pages.every(child_page => {
+ if (to_pages && to_pages.find(p => p.title == child_page.title)) {
+ message = `One of the child page with name ${child_page.title} already exist in ${section} Section. Please update the name of the child page first before moving`;
+ cur_dialog.hide();
+ return false;
+ }
+ return true;
+ });
+ }
+
if (message) {
- frappe.throw(__(message, [__(values.title)]));
+ frappe.throw(__(message));
return false;
}
return true;
}
- add_page_to_sidebar({title, icon, parent, is_public}) {
+ add_page_to_sidebar(page) {
let $sidebar = $('.standard-sidebar-section');
- let item = {
- title: title,
- icon: icon,
- parent_page: parent,
- public: is_public
- };
+ let item = {...page};
+
+ item.selected = true;
+ item.is_editable = true;
+
let $sidebar_item = this.sidebar_item_container(item);
- $sidebar_item.addClass('is-draggable');
- frappe.utils.add_custom_button(
- frappe.utils.icon('drag', 'xs'),
- null,
- "drag-handle",
- `${__('Drag')}`,
- null,
- $sidebar_item.find('.sidebar-item-control')
- );
+ this.add_sidebar_actions(item, $sidebar_item.find('.sidebar-item-control'), true);
+
$sidebar_item.find('.sidebar-item-control .drag-handle').css('margin-right', '8px');
- let $sidebar_section = is_public ? $sidebar[1] : $sidebar[0];
+ let sidebar_section = item.is_public ? $sidebar[1] : $sidebar[0];
- if (!parent) {
- !is_public && $sidebar.first().removeClass('hidden');
- $sidebar_item.appendTo($sidebar_section);
+ if (!item.parent) {
+ !item.is_public && $sidebar.first().removeClass('hidden');
+ $sidebar_item.appendTo(sidebar_section);
} else {
- let $item_container = $($sidebar_section).find(`[item-name="${parent}"]`);
+ let $item_container = $(sidebar_section).find(`[item-name="${item.parent}"]`);
let $child_section = $item_container.find('.sidebar-child-item');
let $drop_icon = $item_container.find('.drop-icon');
if (!$child_section[0]) {
@@ -635,22 +1071,31 @@ frappe.views.Workspace = class Workspace {
}
$sidebar_item.appendTo($child_section);
$child_section.removeClass('hidden');
+ $item_container.find('.drop-icon.hidden').removeClass('hidden');
$item_container.find('.drop-icon use').attr("href", "#icon-small-up");
}
+
+ let section = item.is_public ? 'public' : 'private';
+ if (this.sidebar_items && this.sidebar_items[section] && !this.sidebar_items[section][item.title]) {
+ this.sidebar_items[section][item.title] = $sidebar_item;
+ }
}
initialize_editorjs(blocks) {
this.tools = {
header: {
class: this.blocks['header'],
- inlineToolbar: true,
+ inlineToolbar: ['HeaderSize', 'bold', 'italic', 'link'],
config: {
- defaultLevel: 4
+ default_size: 4
}
},
paragraph: {
class: this.blocks['paragraph'],
- inlineToolbar: true
+ inlineToolbar: ['HeaderSize', 'bold', 'italic', 'link'],
+ config: {
+ placeholder: 'Choose a block or continue typing'
+ }
},
chart: {
class: this.blocks['chart'],
@@ -677,7 +1122,7 @@ frappe.views.Workspace = class Workspace {
}
},
spacer: this.blocks['spacer'],
- spacingTune: frappe.wspace_block.tunes['spacing_tune'],
+ HeaderSize: frappe.workspace_block.tunes['header_size'],
};
this.editor = new EditorJS({
data: {
@@ -685,27 +1130,18 @@ frappe.views.Workspace = class Workspace {
},
tools: this.tools,
autofocus: false,
- tunes: ['spacingTune'],
readOnly: true,
logLevel: 'ERROR'
});
}
- save_page() {
- frappe.dom.freeze();
- this.create_skeleton();
- let save = true;
- if (!this.title && this.current_page) {
- let pages = this.current_page.public ? this.public_pages : this.private_pages;
- this.title = this.current_page.name;
- this.public = pages.filter(p => p.title == this.title)[0].public;
- save = false;
- } else {
- this.current_page = { name: this.title, public: this.public };
- }
+ save_page(page) {
let me = this;
- this.editor.save().then((outputData) => {
+ this.current_page = { name: page.title, public: page.public };
+
+ return this.editor.save().then((outputData) => {
let new_widgets = {};
+
outputData.blocks.forEach(item => {
if (item.data.new) {
if (!new_widgets[item.type]) {
@@ -718,34 +1154,36 @@ frappe.views.Workspace = class Workspace {
let blocks = outputData.blocks.filter(
item => item.type != 'card' ||
- (item.data.card_name !== 'Custom Documents' &&
- item.data.card_name !== 'Custom Reports')
+ (item.data.card_name !== 'Custom Documents' &&
+ item.data.card_name !== 'Custom Reports')
);
+ if (page.content == JSON.stringify(blocks)) {
+ this.setup_customization_buttons(page);
+ frappe.show_alert({ message: __("No changes made on the page"), indicator: "warning" });
+ return false;
+ }
+
+ this.create_page_skeleton();
+ page.content = JSON.stringify(blocks);
frappe.call({
method: "frappe.desk.doctype.workspace.workspace.save_page",
args: {
- title: me.title,
- icon: me.icon || '',
- parent: me.parent || '',
- public: me.public || 0,
- sb_public_items: me.sorted_public_items,
- sb_private_items: me.sorted_private_items,
- deleted_pages: me.deleted_sidebar_items,
+ title: page.title,
+ public: page.public || 0,
new_widgets: new_widgets,
- blocks: JSON.stringify(blocks),
- save: save
+ blocks: JSON.stringify(blocks)
},
callback: function(res) {
- frappe.dom.unfreeze();
if (res.message) {
- me.new_page = res.message;
- me.pages[res.message.label] && delete me.pages[res.message.label];
+ me.discard = true;
+ me.update_cached_values(page, page);
me.reload();
frappe.show_alert({ message: __("Page Saved Successfully"), indicator: "green" });
}
}
});
+ return true;
}).catch((error) => {
error;
// console.log('Saving failed: ', error);
@@ -753,26 +1191,34 @@ frappe.views.Workspace = class Workspace {
}
reload() {
- this.title = '';
- this.icon = '';
- this.parent = '';
- this.public = false;
this.sorted_public_items = [];
this.sorted_private_items = [];
- this.deleted_sidebar_items = [];
- this.create_skeleton();
this.setup_pages(true);
this.discard = false;
this.undo.readOnly = true;
}
- create_skeleton() {
- this.$page.prepend(frappe.render_template('workspace_loading_skeleton'));
- this.$page.find('.codex-editor').addClass('hidden');
+ create_page_skeleton() {
+ if ($('.layout-main-section').find('.workspace-skeleton').length) return;
+
+ $('.layout-main-section').prepend(frappe.render_template('workspace_loading_skeleton'));
+ $('.layout-main-section').find('.codex-editor').addClass('hidden');
+ }
+
+ remove_page_skeleton() {
+ $('.layout-main-section').find('.codex-editor').removeClass('hidden');
+ $('.layout-main-section').find('.workspace-skeleton').remove();
+ }
+
+ create_sidebar_skeleton() {
+ if ($('.list-sidebar').find('.workspace-sidebar-skeleton').length) return;
+
+ $('.list-sidebar').prepend(frappe.render_template('workspace_sidebar_loading_skeleton'));
+ $('.desk-sidebar').addClass('hidden');
}
- remove_skeleton() {
- this.$page.find('.codex-editor').removeClass('hidden');
- this.$page.find('.workspace-skeleton').remove();
+ remove_sidebar_skeleton() {
+ $('.desk-sidebar').removeClass('hidden');
+ $('.list-sidebar').find('.workspace-sidebar-skeleton').remove();
}
};
diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js
index e6ae64d9dc..aabb3526b0 100644
--- a/frappe/public/js/frappe/widgets/base_widget.js
+++ b/frappe/public/js/frappe/widgets/base_widget.js
@@ -34,16 +34,6 @@ export default class Widget {
this.action_area
);
- options.allow_delete &&
- frappe.utils.add_custom_button(
- frappe.utils.icon('delete', 'xs'),
- () => this.delete(),
- "",
- `${__('Delete')}`,
- null,
- this.action_area
- );
-
if (options.allow_hiding) {
if (this.hidden) {
this.widget.removeClass("hidden");
@@ -71,27 +61,11 @@ export default class Widget {
frappe.utils.add_custom_button(
frappe.utils.icon("edit", "xs"),
() => this.edit(),
- null,
+ "edit-button",
`${__('Edit')}`,
null,
this.action_area
);
-
- if (options.allow_resize) {
- const title = this.width == 'Full'? `${__('Collapse')}` : `${__('Expand')}`;
- frappe.utils.add_custom_button(
- '',
- () => this.toggle_width(),
- "resize-button",
- title,
- null,
- this.action_area
- );
-
- this.resize_button = this.action_area.find(
- ".resize-button"
- );
- }
}
make() {
@@ -100,9 +74,7 @@ export default class Widget {
}
make_widget() {
- this.widget = $(`
+ this.widget = $(`
`);
this.title_field = this.widget.find(".widget-title");
diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js
index ec602b8522..6c34fac45a 100644
--- a/frappe/public/js/frappe/widgets/chart_widget.js
+++ b/frappe/public/js/frappe/widgets/chart_widget.js
@@ -28,7 +28,7 @@ export default class ChartWidget extends Widget {
}
set_chart_title() {
- const max_chars = this.widget.width() < 600 ? 20 : 60;
+ const max_chars = this.widget.width() < 600 ? 40 : 60;
this.set_title(max_chars);
}
diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js
index cc771b96b5..3320e88bfb 100644
--- a/frappe/public/js/frappe/widgets/links_widget.js
+++ b/frappe/public/js/frappe/widgets/links_widget.js
@@ -80,7 +80,9 @@ export default class LinksWidget extends Widget {
return $(`
+ } ${disabled_dependent(item)}" type="${item.type}" title="${
+ item.label ? item.label : item.name
+ }">
${get_link_for_item(item)}
`);
diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js
index 5676a834fe..01d41a0cf9 100644
--- a/frappe/public/js/frappe/widgets/widget_dialog.js
+++ b/frappe/public/js/frappe/widgets/widget_dialog.js
@@ -9,6 +9,7 @@ class WidgetDialog {
this.setup_dialog_events();
this.dialog.show();
+ window.cur_dialog = this.dialog;
this.editing && this.set_default_values();
}
@@ -181,19 +182,16 @@ class CardDialog extends WidgetDialog {
fieldtype: "Select",
in_list_view: 1,
label: "Link Type",
- options: ["DocType", "Page", "Report"],
- onchange: (e) => {
- me.link_to = e.currentTarget.value;
- }
+ options: ["DocType", "Page", "Report"]
},
{
fieldname: "link_to",
fieldtype: "Dynamic Link",
in_list_view: 1,
label: "Link To",
- options: "link_type",
- get_options: () => {
- return me.link_to;
+ get_options: (df) => {
+ return df.doc.link_type;
+
}
},
{
@@ -506,7 +504,7 @@ class NumberCardDialog extends WidgetDialog {
setup_dialog_events() {
if (!this.document_type) {
- if (this.default_values['doctype']) {
+ if (this.default_values && this.default_values['doctype']) {
this.document_type = this.default_values['doctype'];
this.setup_filter(this.default_values['doctype']);
this.set_aggregate_function_fields();
@@ -518,7 +516,7 @@ class NumberCardDialog extends WidgetDialog {
set_aggregate_function_fields() {
let aggregate_function_fields = [];
- if (this.document_type) {
+ if (this.document_type && frappe.get_meta(this.document_type)) {
frappe.get_meta(this.document_type).fields.map(df => {
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
if (df.fieldtype == 'Currency') {
@@ -537,7 +535,7 @@ class NumberCardDialog extends WidgetDialog {
if (data.new_or_existing == 'Existing Card') {
data.name = data.card;
}
- data.stats_filter = JSON.stringify(this.filter_group.get_filters());
+ data.stats_filter = this.filter_group && JSON.stringify(this.filter_group.get_filters());
data.document_type = this.document_type;
return data;
diff --git a/frappe/public/scss/common/buttons.scss b/frappe/public/scss/common/buttons.scss
index de3a4cfc20..62479e7a7a 100644
--- a/frappe/public/scss/common/buttons.scss
+++ b/frappe/public/scss/common/buttons.scss
@@ -62,7 +62,7 @@
background-color: var(--control-bg);
color: var(--text-color);
&:hover, &:active {
- background-color: var(--gray-300);
+ background-color: var(--gray-400);
color: var(--text-color);
}
}
diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss
index 8cab845d35..6e5ebdb694 100644
--- a/frappe/public/scss/desk/dark.scss
+++ b/frappe/public/scss/desk/dark.scss
@@ -87,6 +87,8 @@
--highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500);
+ --shadow-base: 0px 4px 8px rgba(114, 176, 233, 0.06), 0px 0px 4px rgba(112, 172, 228, 0.12);
+
// input
--input-disabled-bg: none;
diff --git a/frappe/public/scss/desk/desktop.scss b/frappe/public/scss/desk/desktop.scss
index 6ab01a744c..549ed6eee9 100644
--- a/frappe/public/scss/desk/desktop.scss
+++ b/frappe/public/scss/desk/desktop.scss
@@ -107,13 +107,20 @@ body {
}
}
+.divider {
+ height: 30%;
+ position: absolute;
+ top: 18px;
+ right: 0;
+ border-right: 1px solid var(--gray-400);
+}
+
.widget {
@include flex(flex, null, null, column);
min-height: 1px;
- padding: 15px;
+ padding: 7px;
border-radius: var(--border-radius-md);
height: 100%;
- box-shadow: var(--card-shadow);
background-color: var(--card-bg);
.btn {
@@ -143,6 +150,7 @@ body {
font-weight: 500;
line-height: 1.3em;
color: var(--heading-color);
+ cursor: default;
svg {
flex: none;
@@ -329,9 +337,28 @@ body {
}
&.onboarding-widget-box {
- margin-top: var(--margin-xs);
margin-bottom: var(--margin-2xl);
- padding: var(--padding-lg);
+ padding: var(--padding-lg) !important;
+ background-color: var(--bg-color);
+
+ &.edit-mode:hover {
+ background-color: var(--fg-color);
+
+ .onboarding-step {
+ &.active,
+ &:hover {
+ background-color: var(--bg-color);
+
+ .step-index.step-pending {
+ background-color: var(--fg-color);
+ }
+ }
+
+ .step-index {
+ background-color: var(--bg-color);
+ }
+ }
+ }
.widget-head {
display: flex;
@@ -390,12 +417,6 @@ body {
.step-index.step-pending {
display: flex;
}
-
- &.active {
- .step-index.step-pending {
- background-color: var(--fg-color);
- }
- }
}
&.complete {
@@ -418,7 +439,11 @@ body {
&.active,
&:hover {
- background-color: var(--bg-light-gray);
+ background-color: var(--fg-color);
+
+ .step-index {
+ background-color: var(--bg-color);
+ }
.step-skip {
visibility: visible;
@@ -434,7 +459,7 @@ body {
height: 20px;
width: 20px;
color: var(--text-on-light-gray);
- background-color: var(--bg-light-gray);
+ background-color: var(--fg-color);
margin-right: var(--margin-sm);
border-radius: var(--border-radius-full);
@@ -447,7 +472,7 @@ body {
display: none;
background-color: var(--primary);
.icon use {
- stroke: var(--white);
+ stroke: var(--var(--fg-color));
}
}
@@ -496,7 +521,7 @@ body {
}
}
- @media (max-width: map-get($grid-breakpoints, "sm")) {
+ @media (max-width: map-get($grid-breakpoints, "md")) {
.widget-body {
flex-direction: column;
.onboarding-steps-wrapper {
@@ -513,9 +538,19 @@ body {
&.shortcut-widget-box {
cursor: pointer;
- .widget-head {
- margin-top: var(--margin-xs);
- margin-bottom: 5px;
+ &:hover {
+ .widget-title {
+ color: var(--blue-500) !important;
+ }
+
+ svg.icon-xs {
+ stroke: var(--blue-500) !important;
+ }
+ }
+
+ .widget-title {
+ cursor: pointer !important;
+ font-size: var(--text-base) !important;
}
.indicator-pill {
@@ -631,8 +666,8 @@ body {
width: 18px;
.icon-xs {
- width: 8px;
- height: 7px;
+ width: 10px;
+ height: 10px;
}
}
@@ -757,6 +792,25 @@ body {
}
}
+.workspace-sidebar-skeleton {
+ transition: ease;
+ .sidebar-box {
+ height: 40px;
+ margin-bottom: 10px;
+ margin-left: 10px;
+ background-color: var(--skeleton-bg);
+
+ &.child {
+ margin-left: 30px;
+ }
+
+ &.section {
+ height: 25px;
+ margin-left: 0px;
+ }
+ }
+}
+
[data-page-route="Workspaces"] {
@media (min-width: map-get($grid-breakpoints, "lg")) {
.layout-main {
@@ -764,7 +818,6 @@ body {
.layout-side-section, .layout-main-section-wrapper {
height: 100%;
overflow-y: auto;
- padding-right: 25px;
scrollbar-color: var(--gray-200) transparent;
[data-theme="dark"] & {
scrollbar-color: var(--gray-800) transparent;
@@ -783,7 +836,12 @@ body {
}
.layout-side-section {
- margin-right: 20px;
+ padding-right: 15px;
+ }
+
+ .layout-main-section {
+ padding: var(--padding-md);
+ margin-bottom: var(--margin-sm);
}
.desk-sidebar {
@@ -792,9 +850,15 @@ body {
}
}
+ .layout-main-section {
+ background-color: var(--fg-color);
+ padding: var(--padding-sm);
+ border-radius: var(--border-radius-lg);
+ }
+
.block-menu-item-icon svg{
- width: 12px;
- height: 12px;
+ width: 18px;
+ height: 18px;
margin-right: 5px;
}
@@ -803,7 +867,6 @@ body {
padding: 0px;
.sidebar-item-control {
-
> * {
align-self: center;
margin-left: 3px;
@@ -816,7 +879,7 @@ body {
display: none;
}
- .delete-page {
+ .setting-btn, .duplicate-page {
display: none;
}
@@ -824,13 +887,13 @@ body {
padding: 10px 12px 10px 2px;
}
- .sidebar-info {
- display: none;
- }
-
svg {
margin-right: 0;
}
+
+ .dropdown-list {
+ top: 42px;
+ }
}
.sidebar-item-label {
@@ -846,6 +909,7 @@ body {
}
.sidebar-item-container {
+ position: relative;
.sidebar-item-container{
margin-left: 10px;
@@ -863,19 +927,14 @@ body {
display: inline-block;
}
- .delete-page {
- display: inline-block;
- margin-right: 8px;
- }
-
- .sidebar-info {
+ .setting-btn, .duplicate-page {
display: inline-block;
margin-right: 8px;
}
.drop-icon {
padding: 10px 8px 10px 2px;
- margin-left: -4px;
+ margin-left: -8px;
}
}
@@ -899,11 +958,81 @@ body {
margin: 0px -7px;
padding-bottom: 20px !important;
- .ce-block{
+ .ce-block {
width: 100%;
padding-left: 0;
padding-right: 0;
+ .ce-header b {
+ font-weight: 600 !important;
+ }
+
+ .new-block-button {
+ position: absolute;
+ top: 14px;
+ left: -22px;
+ cursor: pointer;
+ visibility: hidden;
+ opacity: 0;
+ transition: visibility 0s, opacity 0.5s ease-in-out;
+ }
+
+ .edit-mode {
+ .widget-control > *, .paragraph-control > * {
+ width: 0px;
+ visibility: hidden;
+ opacity: 0;
+ transition: visibility 0s, opacity 0.5s ease-in-out;
+ }
+
+ .link-item {
+ pointer-events: none;
+ }
+ }
+
+ &:hover {
+ .widget-control > *, .new-block-button {
+ width: auto;
+ visibility: visible;
+ opacity: 1;
+ }
+ }
+
+ &.ce-block--focused {
+ .widget {
+ box-shadow: var(--shadow-base) !important;
+
+ .widget-control > * {
+ width: auto;
+ visibility: visible;
+ opacity: 1;
+ }
+
+ &.shortcut, &.header {
+ background-color: var(--fg-color) !important;
+ }
+
+ &.onboarding {
+ background-color: var(--fg-color);
+
+ .onboarding-step {
+ &.active,
+ &:hover {
+ background-color: var(--bg-color);
+
+ .step-index.step-pending {
+ background-color: var(--fg-color);
+ }
+ }
+
+ .step-index {
+ background-color: var(--bg-color);
+ }
+ }
+ }
+ }
+ }
+
&.ce-block--selected {
.ce-block__content {
background-color: inherit;
@@ -923,50 +1052,125 @@ body {
pointer-events: none;
}
+ .resizer {
+ width: 10px;
+ height: 100%;
+ position:absolute;
+ right: 0;
+ bottom: 0;
+ cursor: col-resize;
+ border-color: transparent;
+ transition: border-color 0.3s ease-in-out;
+
+ &:hover {
+ border-right: 3px solid var(--gray-400) !important;
+ }
+ }
+
.ce-header {
- padding: 0 !important;
+ padding-left: 7px !important;
margin-bottom: 0 !important;
flex: 1;
+
+ &:focus {
+ outline: none;
+ }
+ }
+
+ .block-list-container {
+ left: 20px;
+ top: 55px !important;
+ width: 200px !important;
}
- .widget{
+ .dropdown-title {
+ padding: 6px 10px;
+ font-size: smaller;
+ cursor: default;
+ }
+
+ .ce-paragraph[data-placeholder]:empty::before {
+ opacity: 1;
+ }
+
+ .widget {
+ &.edit-mode {
+ padding: 7px 12px;
+
+ &:hover {
+ box-shadow: var(--shadow-base);
+ background-color: var(--fg-color);
+ }
+
+ &.spacer {
+ align-items: inherit;
+ color: var(--text-muted);
+ border: 1px dashed var(--gray-400);
+ cursor: pointer;
+
+ .widget-control > * {
+ width: auto;
+ }
+
+ .spacer-left {
+ min-width: 74px;
+ }
+ }
+ }
+
+ &.spacer {
+ height: 18px !important;
+ }
+
+ &.ce-paragraph {
+ display: block;
+ }
+
+ &.paragraph {
+ cursor: text;
+
+ .ce-paragraph {
+ padding: 2px;
+ }
+
+ .paragraph-control {
+ display: flex;
+ flex-direction: row-reverse;
+ position: absolute;
+ right: 20px;
+ gap: 5px;
+ background-color: var(--card-bg);
+ padding-left: 5px;
+
+ .drag-handle {
+ cursor: all-scroll;
+ cursor: grabbing;
+ }
+ }
+ }
+
&.header {
display: flex;
justify-content: center;
flex: 1;
- padding-left: 15px !important;
- padding-right: 15px !important;
- min-height: 50px;
+ padding-left: 0px !important;
+ min-height: 40px;
box-shadow: none;
background-color: var(--control-bg);
color: var(--text-muted);
- }
-
- &:focus {
- outline: none;
- }
+ cursor: text;
- &.new-widget {
- align-items: inherit;
+ .ce-header {
+ padding-left: 14px !important;
+ }
}
- &.ce-paragraph {
- display: block;
+ &.shortcut {
+ background-color: var(--control-bg);
}
- .paragraph-control {
- display: flex;
- flex-direction: row-reverse;
- position: absolute;
- right: 20px;
- gap: 5px;
- background-color: var(--card-bg);
- padding-left: 5px;
-
- .drag-handle {
- cursor: all-scroll;
- cursor: grabbing;
- }
+ &:focus {
+ outline: none;
}
}
}
@@ -978,14 +1182,21 @@ body {
}
.ce-toolbar {
+
+ &.ce-toolbar--opened {
+ display: none;
+ }
+
svg {
fill: currentColor;
}
.icon {
stroke: none;
- width: fit-content;
- height: fit-content;
+
+ &.icon--plus {
+ width: 14px;
+ }
}
.ce-settings {
@@ -993,6 +1204,10 @@ body {
.ce-settings__button, .cdx-settings-button {
color: #707684;
+
+ .icon {
+ width: 14px;
+ }
}
.cdx-settings-button--active {
@@ -1024,6 +1239,10 @@ body {
.icon {
fill: currentColor;
}
+
+ svg {
+ stroke: none;
+ }
}
@media (min-width: 1199px) {
@@ -1037,25 +1256,63 @@ body {
}
}
- @media (max-width: 1199px) {
- .ce-block.col-4 {
- flex: 0 0 50%;
- max-width: 50%;
- }
- }
+ }
- @media (max-width: 750px) {
- .ce-block.col-4 {
- flex: 0 0 100%;
- max-width: 100%;
- }
- }
- @media (max-width: 750px) {
- .ce-block.col-6 {
- flex: 0 0 100%;
- max-width: 100%;
- }
+ .cdx-marker {
+ background: rgba(245,235,111,0.29);
+ padding: 3px 0;
+ }
+
+ .header-inline-tool {
+ border: none;
+ background-color: transparent;
+ margin-bottom: 2px;
+ }
+
+ .header-level-select {
+ display: flex;
+ flex-direction: column;
+ padding: 6px;
+ }
+
+ .header-level-select .header-level {
+ border: none;
+ background-color: transparent;
+ border-radius: var(--border-radius-sm);
+ padding: 6px;
+ margin: 2px 0px;
+
+ &:hover {
+ background-color: var(--fg-hover-color);
}
+ }
+
+ .dropdown-btn {
+ position: relative;
+ }
+
+ .dropdown-list {
+ position: absolute;
+ background-color: var(--fg-color);
+ box-shadow: var(--shadow-base) !important;
+ border-radius: var(--border-radius-sm);
+ padding: 6px;
+ top: 30px;
+ right: 0;
+ width: 150px;
+ z-index: 1;
+ }
+ .dropdown-list .dropdown-item {
+ cursor: pointer;
+ padding: 6px 10px;
+ font-size: small;
+ border-radius: var(--border-radius-sm);
+ margin: 1px 0px;
}
+
+ .dropdown-item-icon {
+ margin-right: 5px;
+ }
+
}
diff --git a/frappe/website/workspace/website/website.json b/frappe/website/workspace/website/website.json
index bd06f0a131..a0d9a817d4 100644
--- a/frappe/website/workspace/website/website.json
+++ b/frappe/website/workspace/website/website.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Website\", \"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Blog Post\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Blogger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Web Page\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Web Form\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Website Settings\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Blog\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Web Site\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Portal\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Knowledge Base\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Website\",\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"
Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Blog Post\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Blogger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Page\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Form\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Website Settings\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"
Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Setup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Blog\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Web Site\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Portal\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Knowledge Base\",\"col\":4}}]",
"creation": "2020-03-02 14:13:51.089373",
"docstatus": 0,
"doctype": "Workspace",
@@ -232,7 +232,7 @@
"type": "Link"
}
],
- "modified": "2021-08-05 12:16:03.154033",
+ "modified": "2022-01-13 17:49:41.527194",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",
@@ -241,7 +241,7 @@
"public": 1,
"restrict_to_domain": "",
"roles": [],
- "sequence_id": 28,
+ "sequence_id": 28.0,
"shortcuts": [
{
"color": "Green",