Bläddra i källkod

Merge pull request #15335 from shariquerik/wspace-new-design

feat: Workspace 2.0 new design 🎉
version-14
Rushabh Mehta 3 år sedan
committed by GitHub
förälder
incheckning
d8476de6b0
Ingen känd nyckel hittad för denna signaturen i databasen GPG-nyckel ID: 4AEE18F83AFDEB23
39 ändrade filer med 2435 tillägg och 1362 borttagningar
  1. +6
    -6
      cypress/integration/control_icon.js
  2. +25
    -27
      cypress/integration/workspace.js
  3. +3
    -3
      frappe/automation/workspace/tools/tools.json
  4. +2
    -2
      frappe/boot.py
  5. +3
    -3
      frappe/core/workspace/build/build.json
  6. +3
    -3
      frappe/core/workspace/settings/settings.json
  7. +3
    -3
      frappe/core/workspace/users/users.json
  8. +3
    -3
      frappe/custom/workspace/customization/customization.json
  9. +19
    -45
      frappe/desk/desktop.py
  10. +3
    -2
      frappe/desk/doctype/workspace/workspace.json
  11. +151
    -49
      frappe/desk/doctype/workspace/workspace.py
  12. +3
    -3
      frappe/integrations/workspace/integrations/integrations.json
  13. +4
    -4
      frappe/patches/v14_0/update_workspace2.py
  14. +629
    -400
      frappe/public/icons/timeless/symbol-defs.svg
  15. +1
    -0
      frappe/public/js/desk.bundle.js
  16. +1
    -1
      frappe/public/js/frappe/form/controls/dynamic_link.js
  17. +3
    -3
      frappe/public/js/frappe/router.js
  18. +22
    -0
      frappe/public/js/frappe/ui/workspace_sidebar_loading_skeleton.html
  19. +22
    -3
      frappe/public/js/frappe/utils/utils.js
  20. +247
    -29
      frappe/public/js/frappe/views/workspace/blocks/block.js
  21. +7
    -4
      frappe/public/js/frappe/views/workspace/blocks/card.js
  22. +9
    -5
      frappe/public/js/frappe/views/workspace/blocks/chart.js
  23. +24
    -221
      frappe/public/js/frappe/views/workspace/blocks/header.js
  24. +117
    -0
      frappe/public/js/frappe/views/workspace/blocks/header_size.js
  25. +5
    -5
      frappe/public/js/frappe/views/workspace/blocks/index.js
  26. +18
    -9
      frappe/public/js/frappe/views/workspace/blocks/onboarding.js
  27. +61
    -44
      frappe/public/js/frappe/views/workspace/blocks/paragraph.js
  28. +35
    -6
      frappe/public/js/frappe/views/workspace/blocks/shortcut.js
  29. +7
    -32
      frappe/public/js/frappe/views/workspace/blocks/spacer.js
  30. +0
    -123
      frappe/public/js/frappe/views/workspace/blocks/spacing_tune.js
  31. +637
    -191
      frappe/public/js/frappe/views/workspace/workspace.js
  32. +4
    -34
      frappe/public/js/frappe/widgets/base_widget.js
  33. +1
    -1
      frappe/public/js/frappe/widgets/chart_widget.js
  34. +3
    -1
      frappe/public/js/frappe/widgets/links_widget.js
  35. +8
    -10
      frappe/public/js/frappe/widgets/widget_dialog.js
  36. +1
    -1
      frappe/public/scss/common/buttons.scss
  37. +2
    -0
      frappe/public/scss/desk/dark.scss
  38. +340
    -83
      frappe/public/scss/desk/desktop.scss
  39. +3
    -3
      frappe/website/workspace/website/website.json

+ 6
- 6
cypress/integration/control_icon.js Visa fil

@@ -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');
});
});



+ 25
- 27
cypress/integration/workspace.js Visa fil

@@ -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();


+ 3
- 3
frappe/automation/workspace/tools/tools.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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",


+ 2
- 2
frappe/boot.py Visa fil

@@ -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")



+ 3
- 3
frappe/core/workspace/build/build.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Elements</b></span>\",\"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": "",


+ 3
- 3
frappe/core/workspace/settings/settings.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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",


+ 3
- 3
frappe/core/workspace/users/users.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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",


+ 3
- 3
frappe/custom/workspace/customization/customization.json Visa fil

@@ -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 &amp; 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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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",


+ 19
- 45
frappe/desk/desktop.py Visa fil

@@ -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 = \


+ 3
- 2
frappe/desk/doctype/workspace/workspace.json Visa fil

@@ -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",


+ 151
- 49
frappe/desk/doctype/workspace/workspace.py Visa fil

@@ -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')



+ 3
- 3
frappe/integrations/workspace/integrations/integrations.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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"
}

+ 4
- 4
frappe/patches/v14_0/update_workspace2.py Visa fil

@@ -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)


+ 629
- 400
frappe/public/icons/timeless/symbol-defs.svg
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
frappe/public/js/desk.bundle.js Visa fil

@@ -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";



+ 1
- 1
frappe/public/js/frappe/form/controls/dynamic_link.js Visa fil

@@ -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);


+ 3
- 3
frappe/public/js/frappe/router.js Visa fil

@@ -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);


+ 22
- 0
frappe/public/js/frappe/ui/workspace_sidebar_loading_skeleton.html Visa fil

@@ -0,0 +1,22 @@
<div class="workspace-sidebar-skeleton">
<div class="widget-group-body">
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
</div>
<div class="widget-group-body">
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box child skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
<div class="widget sidebar-box skeleton-card"></div>
</div>
</div>

+ 22
- 3
frappe/public/js/frappe/utils/utils.js Visa fil

@@ -243,9 +243,28 @@ Object.assign(frappe.utils, {
'=': '&#x3D;'
};

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 = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
'&#x2F;': '/',
'&#x60;': '`',
'&#x3D;': '='
};

return String(txt).replace(
/&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F;|&#x60;|&#x3D;/g,
char => unescape_html_mapping[char] || char
);
},

html2text: function(html) {


+ 247
- 29
frappe/public/js/frappe/views/workspace/blocks/block.js Visa fil

@@ -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 = $(`
<div class="new-block-button">${frappe.utils.icon('add-round', 'lg')}</div>
`);

$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 = $(`
<div class="dropdown-btn">
<button class="btn btn-secondary btn-xs setting-btn" title="${__('Setting')}">
${frappe.utils.icon('dot-horizontal', 'xs')}
</button>
<div class="dropdown-list hidden"></div>
</div>
`);


let dropdown_item = function(label, title, icon, action) {
let html = $(`
<div class="dropdown-item" title="${title}">
<span class="dropdown-item-icon">${icon}</span>
<span class="dropdown-item-label">${label}</span>
</div>
`);

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);
}
}

+ 7
- 4
frappe/public/js/frappe/views/workspace/blocks/card.js Visa fil

@@ -3,7 +3,7 @@ export default class Card extends Block {
static get toolbox() {
return {
title: 'Card',
icon: '<svg height="20" width="20" viewBox="2 2 20 20"><path d="M7 15h3a1 1 0 000-2H7a1 1 0 000 2zM19 5H5a3 3 0 00-3 3v9a3 3 0 003 3h14a3 3 0 003-3V8a3 3 0 00-3-3zm1 12a1 1 0 01-1 1H5a1 1 0 01-1-1v-6h16zm0-8H4V8a1 1 0 011-1h14a1 1 0 011 1z"/></svg>'
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
};


+ 9
- 5
frappe/public/js/frappe/views/workspace/blocks/chart.js Visa fil

@@ -3,7 +3,7 @@ export default class Chart extends Block {
static get toolbox() {
return {
title: 'Chart',
icon: '<svg height="18" width="18" viewBox="0 0 512 512"><path d="M117.547 234.667H10.88c-5.888 0-10.667 4.779-10.667 10.667v256C.213 507.221 4.992 512 10.88 512h106.667c5.888 0 10.667-4.779 10.667-10.667v-256a10.657 10.657 0 00-10.667-10.666zM309.12 0H202.453c-5.888 0-10.667 4.779-10.667 10.667v490.667c0 5.888 4.779 10.667 10.667 10.667H309.12c5.888 0 10.667-4.779 10.667-10.667V10.667C319.787 4.779 315.008 0 309.12 0zM501.12 106.667H394.453c-5.888 0-10.667 4.779-10.667 10.667v384c0 5.888 4.779 10.667 10.667 10.667H501.12c5.888 0 10.667-4.779 10.667-10.667v-384c0-5.889-4.779-10.667-10.667-10.667z"/></svg>'
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
};


+ 24
- 221
frappe/public/js/frappe/views/workspace/blocks/header.js Visa fil

@@ -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 = $(`<div class="widget-head"></div>`);
let $widget_control = $(`<div class="widget-control"></div>`);
@@ -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(/&nbsp;/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 : `<span class="h${this._settings.default_size}">${text}</span>`;
}

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 || '&nbsp';
tag.innerHTML = `<span class="h${this._settings.default_size}"><b>${text}</b></span>`;

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: '<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.14 1.494V4.98h4.62V1.494c0-.498.098-.871.293-1.12A.927.927 0 0 1 7.82 0c.322 0 .583.123.782.37.2.246.3.62.3 1.124v9.588c0 .503-.101.88-.303 1.128a.957.957 0 0 1-.779.374.921.921 0 0 1-.77-.378c-.193-.251-.29-.626-.29-1.124V6.989H2.14v4.093c0 .503-.1.88-.302 1.128a.957.957 0 0 1-.778.374.921.921 0 0 1-.772-.378C.096 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.285.374A.922.922 0 0 1 1.06 0c.321 0 .582.123.782.37.199.246.299.62.299 1.124zm11.653 9.985V5.27c-1.279.887-2.14 1.33-2.583 1.33a.802.802 0 0 1-.563-.228.703.703 0 0 1-.245-.529c0-.232.08-.402.241-.511.161-.11.446-.25.854-.424.61-.259 1.096-.532 1.462-.818a5.84 5.84 0 0 0 .97-.962c.282-.355.466-.573.552-.655.085-.082.246-.123.483-.123.267 0 .481.093.642.28.161.186.242.443.242.77v7.813c0 .914-.345 1.371-1.035 1.371-.307 0-.554-.093-.74-.28-.187-.186-.28-.461-.28-.825z"/></svg>',
},
{
number: 2,
tag: 'H2',
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm10.99 9.288h3.527c.351 0 .62.072.804.216.185.144.277.34.277.588 0 .22-.073.408-.22.56-.146.154-.368.23-.665.23h-4.972c-.338 0-.601-.093-.79-.28a.896.896 0 0 1-.284-.659c0-.162.06-.377.182-.645s.255-.478.399-.631a38.617 38.617 0 0 1 1.621-1.598c.482-.444.827-.735 1.034-.875.369-.261.676-.523.922-.787.245-.263.432-.534.56-.81.129-.278.193-.549.193-.815 0-.288-.069-.546-.206-.773a1.428 1.428 0 0 0-.56-.53 1.618 1.618 0 0 0-.774-.19c-.59 0-1.054.26-1.392.777-.045.068-.12.252-.226.554-.106.302-.225.534-.358.696-.133.162-.328.243-.585.243a.76.76 0 0 1-.56-.223c-.149-.148-.223-.351-.223-.608 0-.31.07-.635.21-.972.139-.338.347-.645.624-.92a3.093 3.093 0 0 1 1.054-.665c.426-.169.924-.253 1.496-.253.69 0 1.277.108 1.764.324.315.144.592.343.83.595.24.252.425.544.558.875.133.33.2.674.2 1.03 0 .558-.14 1.066-.416 1.523-.277.457-.56.815-.848 1.074-.288.26-.771.666-1.45 1.22-.677.554-1.142.984-1.394 1.29a3.836 3.836 0 0 0-.331.44z"/></svg>',
},
{
number: 3,
tag: 'H3',
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm11.61 4.919c.418 0 .778-.123 1.08-.368.301-.245.452-.597.452-1.055 0-.35-.12-.65-.36-.902-.241-.252-.566-.378-.974-.378-.277 0-.505.038-.684.116a1.1 1.1 0 0 0-.426.306 2.31 2.31 0 0 0-.296.49c-.093.2-.178.388-.255.565a.479.479 0 0 1-.245.225.965.965 0 0 1-.409.081.706.706 0 0 1-.5-.22c-.152-.148-.228-.345-.228-.59 0-.236.071-.484.214-.745a2.72 2.72 0 0 1 .627-.746 3.149 3.149 0 0 1 1.024-.568 4.122 4.122 0 0 1 1.368-.214c.44 0 .842.06 1.205.18.364.12.679.294.947.52.267.228.47.49.606.79.136.3.204.622.204.967 0 .454-.099.843-.296 1.168-.198.324-.48.64-.848.95.354.19.653.408.895.653.243.245.426.516.548.813.123.298.184.619.184.964 0 .413-.083.812-.248 1.198-.166.386-.41.73-.732 1.031a3.49 3.49 0 0 1-1.147.708c-.443.17-.932.256-1.467.256a3.512 3.512 0 0 1-1.464-.293 3.332 3.332 0 0 1-1.699-1.64c-.142-.314-.214-.573-.214-.777 0-.263.085-.475.255-.636a.89.89 0 0 1 .637-.242c.127 0 .25.037.367.112a.53.53 0 0 1 .232.27c.236.63.489 1.099.759 1.405.27.306.65.46 1.14.46a1.714 1.714 0 0 0 1.46-.824c.17-.273.256-.588.256-.947 0-.53-.145-.947-.436-1.249-.29-.302-.694-.453-1.212-.453-.09 0-.231.01-.422.028-.19.018-.313.027-.367.027-.25 0-.443-.062-.579-.187-.136-.125-.204-.299-.204-.521 0-.218.081-.394.245-.528.163-.134.406-.2.728-.2h.28z"/></svg>',
},
{
number: 4,
tag: 'H4',
svg: '<svg width="20" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm13.003 10.09v-1.252h-3.38c-.427 0-.746-.097-.96-.29-.213-.193-.32-.456-.32-.788 0-.085.016-.171.048-.259.031-.088.078-.18.141-.276.063-.097.128-.19.195-.28.068-.09.15-.2.25-.33l3.568-4.774a5.44 5.44 0 0 1 .576-.683.763.763 0 0 1 .542-.212c.682 0 1.023.39 1.023 1.171v5.212h.29c.346 0 .623.047.832.142.208.094.313.3.313.62 0 .26-.086.45-.256.568-.17.12-.427.179-.768.179h-.41v1.252c0 .346-.077.603-.23.771-.152.168-.356.253-.612.253a.78.78 0 0 1-.61-.26c-.154-.173-.232-.427-.232-.764zm-2.895-2.76h2.895V4.91L12.26 8.823z"/></svg>',
},
{
number: 5,
tag: 'H5',
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm14.16 2.645h-3.234l-.388 2.205c.644-.344 1.239-.517 1.783-.517.436 0 .843.082 1.222.245.38.164.712.39.998.677.286.289.51.63.674 1.025.163.395.245.82.245 1.273 0 .658-.148 1.257-.443 1.797-.295.54-.72.97-1.276 1.287-.556.318-1.197.477-1.923.477-.813 0-1.472-.15-1.978-.45-.506-.3-.865-.643-1.076-1.031-.21-.388-.316-.727-.316-1.018 0-.177.073-.345.22-.504a.725.725 0 0 1 .556-.238c.381 0 .665.22.85.66.182.404.427.719.736.943.309.225.654.337 1.035.337.35 0 .656-.09.919-.272.263-.182.466-.431.61-.749.142-.318.214-.678.214-1.082 0-.436-.078-.808-.232-1.117a1.607 1.607 0 0 0-.62-.69 1.674 1.674 0 0 0-.864-.229c-.39 0-.67.048-.837.143-.168.095-.41.262-.725.5-.316.239-.576.358-.78.358a.843.843 0 0 1-.592-.242c-.173-.16-.259-.344-.259-.548 0-.022.025-.177.075-.463l.572-3.26c.063-.39.181-.675.354-.852.172-.177.454-.265.844-.265h3.595c.708 0 1.062.27 1.062.81a.711.711 0 0 1-.26.572c-.172.145-.426.218-.762.218z"/></svg>',
},
{
number: 6,
tag: 'H6',
svg: '<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zM12.53 7.058a3.093 3.093 0 0 1 1.004-.814 2.734 2.734 0 0 1 1.214-.264c.43 0 .827.08 1.19.24.365.161.684.39.957.686.274.296.485.645.635 1.048a3.6 3.6 0 0 1 .223 1.262c0 .637-.145 1.216-.437 1.736-.292.52-.699.926-1.221 1.218-.522.292-1.114.438-1.774.438-.76 0-1.416-.186-1.967-.557-.552-.37-.974-.919-1.265-1.645-.292-.726-.438-1.613-.438-2.662 0-.855.088-1.62.265-2.293.176-.674.43-1.233.76-1.676.33-.443.73-.778 1.2-1.004.47-.226 1.006-.339 1.608-.339.579 0 1.089.113 1.53.34.44.225.773.506.997.84.224.335.335.656.335.964 0 .185-.07.354-.21.505a.698.698 0 0 1-.536.227.874.874 0 0 1-.529-.18 1.039 1.039 0 0 1-.36-.498 1.42 1.42 0 0 0-.495-.655 1.3 1.3 0 0 0-.786-.247c-.24 0-.479.069-.716.207a1.863 1.863 0 0 0-.6.56c-.33.479-.525 1.333-.584 2.563zm1.832 4.213c.456 0 .834-.186 1.133-.56.298-.373.447-.862.447-1.468 0-.412-.07-.766-.21-1.062a1.584 1.584 0 0 0-.577-.678 1.47 1.47 0 0 0-.807-.234c-.28 0-.548.074-.804.224-.255.149-.461.365-.617.647a2.024 2.024 0 0 0-.234.994c0 .61.158 1.12.475 1.527.316.407.714.61 1.194.61z"/></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: '<svg width="10" height="14" viewBox="0 0 10 14"><path d="M7.6 8.15H2.25v4.525a1.125 1.125 0 0 1-2.25 0V1.125a1.125 1.125 0 1 1 2.25 0V5.9H7.6V1.125a1.125 1.125 0 0 1 2.25 0v11.55a1.125 1.125 0 0 1-2.25 0V8.15z"></path></svg>',
title: 'Heading',
icon: frappe.utils.icon('header', 'sm')
};
}
}

+ 117
- 0
frappe/public/js/frappe/views/workspace/blocks/header_size.js Visa fil

@@ -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');
}
}
}

+ 5
- 5
frappe/public/js/frappe/views/workspace/blocks/index.js Visa fil

@@ -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,
};

+ 18
- 9
frappe/public/js/frappe/views/workspace/blocks/onboarding.js Visa fil

@@ -4,7 +4,7 @@ export default class Onboarding extends Block {
static get toolbox() {
return {
title: 'Onboarding',
icon: '<svg width="24" height="24" viewBox="2 0 20 24" fill="none"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 11.09v5.455" stroke="#1F272E" fill="none"/><path d="M12.41 7.455a.41.41 0 11-.82 0 .41.41 0 01.82 0z" stroke="#1F272E"/></svg>'
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
};


+ 61
- 44
frappe/public/js/frappe/views/workspace/blocks/paragraph.js Visa fil

@@ -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 = $(`
<div class="block-list-container dropdown-list">
<div class="dropdown-title">${dropdown_title.toUpperCase()}</div>
</div>
`);

let all_blocks = frappe.workspace_block.blocks;
Object.keys(all_blocks).forEach(key => {
let $block_list_item = $(`
<div class="block-list-item dropdown-item">
<span class="dropdown-item-icon">${all_blocks[key].toolbox.icon}</span>
<span class="dropdown-item-label">${__(all_blocks[key].toolbox.title)}</span>
</div>
`);

$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 = $(`<div class="paragraph-control"></div>`);
let $para_control = $(`<div class="widget-control paragraph-control"></div>`);

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: '<svg viewBox="0.2 -0.3 9 11.4" width="12" height="14"><path d="M0 2.77V.92A1 1 0 01.2.28C.35.1.56 0 .83 0h7.66c.28.01.48.1.63.28.14.17.21.38.21.64v1.85c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26a1 1 0 01-.21-.66V1.69H5.6v7.58h.5c.25 0 .45.08.6.23.17.16.25.35.25.6s-.08.45-.24.6a.87.87 0 01-.62.22H3.21a.87.87 0 01-.61-.22.78.78 0 01-.24-.6c0-.25.08-.44.24-.6a.85.85 0 01.61-.23h.5V1.7H1.73v1.08c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26A1 1 0 010 2.77z"/></svg>',
title: 'Text'
title: 'Text',
icon: frappe.utils.icon('text', 'sm')
};
}
}

+ 35
- 6
frappe/public/js/frappe/views/workspace/blocks/shortcut.js Visa fil

@@ -3,7 +3,7 @@ export default class Shortcut extends Block {
static get toolbox() {
return {
title: 'Shortcut',
icon: '<svg height="18" width="18" viewBox="0 0 122.88 115.71"><path d="M116.56 3.69l-3.84 53.76-17.69-15c-19.5 8.72-29.96 23.99-30.51 43.77-17.95-26.98-7.46-50.4 12.46-65.97L64.96 3l51.6.69zM28.3 0h14.56v19.67H32.67c-4.17 0-7.96 1.71-10.72 4.47-2.75 2.75-4.46 6.55-4.46 10.72l-.03 46c.03 4.16 1.75 7.95 4.5 10.71 2.76 2.76 6.56 4.48 10.71 4.48h58.02c4.15 0 7.95-1.72 10.71-4.48 2.76-2.76 4.48-6.55 4.48-10.71V73.9h17.01v11.33c0 7.77-3.2 17.04-8.32 22.16-5.12 5.12-12.21 8.32-19.98 8.32H28.3c-7.77 0-14.86-3.2-19.98-8.32C3.19 102.26 0 95.18 0 87.41l.03-59.1c-.03-7.79 3.16-14.88 8.28-20C13.43 3.19 20.51 0 28.3 0z" fill-rule="evenodd" clip-rule="evenodd"/></svg>'
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($(`<div class="divider"></div>`));
}
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
};


+ 7
- 32
frappe/public/js/frappe/views/workspace/blocks/spacer.js Visa fil

@@ -3,7 +3,7 @@ export default class Spacer extends Block {
static get toolbox() {
return {
title: 'Spacer',
icon: '<svg width="18" height="18" viewBox="0 0 400 400"><path d="M377.87 24.126C361.786 8.042 342.417 0 319.769 0H82.227C59.579 0 40.211 8.042 24.125 24.126 8.044 40.212.002 59.576.002 82.228v237.543c0 22.647 8.042 42.014 24.123 58.101 16.086 16.085 35.454 24.127 58.102 24.127h237.542c22.648 0 42.011-8.042 58.102-24.127 16.085-16.087 24.126-35.453 24.126-58.101V82.228c-.004-22.648-8.046-42.016-24.127-58.102zm-12.422 295.645c0 12.559-4.47 23.314-13.415 32.264-8.945 8.945-19.698 13.411-32.265 13.411H82.227c-12.563 0-23.317-4.466-32.264-13.411-8.945-8.949-13.418-19.705-13.418-32.264V82.228c0-12.562 4.473-23.316 13.418-32.264 8.947-8.946 19.701-13.418 32.264-13.418h237.542c12.566 0 23.319 4.473 32.265 13.418 8.945 8.947 13.415 19.701 13.415 32.264v237.543h-.001z"/></svg>'
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 = $(`
<div class="widget-head">
<div></div>
<div class="spacer-left"></div>
<div>Spacer</div>
<div class="widget-control"></div>
</div>
`);
$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;
}


+ 0
- 123
frappe/public/js/frappe/views/workspace/blocks/spacing_tune.js Visa fil

@@ -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 = `<svg version="1.1" height="10" x="0px" y="0px" viewBox="-674 380 17 10" style="enable-background:new -674 380 17 10;" xml:space="preserve"><path d="M-674,383.9h3.6l-1.7-1.7c-0.4-0.4-0.4-1.2,0-1.6c0.4-0.4,1.1-0.4,1.6,0l3.2,3.2c0.6,0.2,0.8,0.8,0.6,1.4 c-0.1,0.1-0.1,0.3-0.2,0.4l-3.8,3.8c-0.4,0.4-1.1,0.4-1.5,0c-0.4-0.4-0.4-1.1,0-1.5l1.8-1.8h-3.6V383.9z"/><path d="M-657,386.1h-3.6l1.7,1.7c0.4,0.4,0.4,1.2,0,1.6c-0.4,0.4-1.1,0.4-1.6,0l-3.2-3.2c-0.6-0.2-0.8-0.8-0.6-1.4 c0.1-0.1,0.1-0.3,0.2-0.4l3.8-3.8c0.4-0.4,1.1-0.4,1.5,0c0.4,0.4,0.4,1.1,0,1.5l-1.8,1.8h3.6V386.1z"/></svg>`;
this.api.tooltip.onHover(decreaseWidthButton, 'Shrink', {
placement: 'top',
hidingDelay: 500,
});
this.api.listeners.on(
decreaseWidthButton,
'click',
() => me.decreaseWidth(),
false
);

increaseWidthButton.innerHTML = `<svg width="17" height="10" viewBox="0 0 17 10"><path d="M13.568 5.925H4.056l1.703 1.703a1.125 1.125 0 0 1-1.59 1.591L.962 6.014A1.069 1.069 0 0 1 .588 4.26L4.38.469a1.069 1.069 0 0 1 1.512 1.511L4.084 3.787h9.606l-1.85-1.85a1.069 1.069 0 1 1 1.512-1.51l3.792 3.791a1.069 1.069 0 0 1-.475 1.788L13.514 9.16a1.125 1.125 0 0 1-1.59-1.591l1.644-1.644z"/></svg>`;
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);
}
}
}
}

+ 637
- 191
frappe/public/js/frappe/views/workspace/workspace.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 4
- 34
frappe/public/js/frappe/widgets/base_widget.js Visa fil

@@ -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(
'<i class="fa fa-expand" aria-hidden="true"></i>',
() => 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 = $(`<div class="widget
${ this.shadow ? "widget-shadow" : " " }
" data-widget-name="${this.name ? this.name : ''}">
this.widget = $(`<div class="widget" data-widget-name="${this.name ? this.name : ''}">
<div class="widget-head">
<div class="widget-label">
<div class="widget-title"></div>
@@ -110,10 +82,8 @@ export default class Widget {
</div>
<div class="widget-control"></div>
</div>
<div class="widget-body">
</div>
<div class="widget-footer">
</div>
<div class="widget-body"></div>
<div class="widget-footer"></div>
</div>`);

this.title_field = this.widget.find(".widget-title");


+ 1
- 1
frappe/public/js/frappe/widgets/chart_widget.js Visa fil

@@ -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);
}



+ 3
- 1
frappe/public/js/frappe/widgets/links_widget.js Visa fil

@@ -80,7 +80,9 @@ export default class LinksWidget extends Widget {

return $(`<a href="${route}" class="link-item ellipsis ${
item.onboard ? "onboard-spotlight" : ""
} ${disabled_dependent(item)}" type="${item.type}">
} ${disabled_dependent(item)}" type="${item.type}" title="${
item.label ? item.label : item.name
}">
<span class="indicator-pill no-margin ${get_indicator_color(item)}"></span>
${get_link_for_item(item)}
</a>`);


+ 8
- 10
frappe/public/js/frappe/widgets/widget_dialog.js Visa fil

@@ -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;


+ 1
- 1
frappe/public/scss/common/buttons.scss Visa fil

@@ -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);
}
}


+ 2
- 0
frappe/public/scss/desk/dark.scss Visa fil

@@ -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;



+ 340
- 83
frappe/public/scss/desk/desktop.scss Visa fil

@@ -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;
}

}

+ 3
- 3
frappe/website/workspace/website/website.json Visa fil

@@ -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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"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",


Laddar…
Avbryt
Spara