PoC for ABP CmsKit using GrapesJs

9/11/2024 12:50:17 PM

Intro

This post demonstrates how to switch out the Markdown editor of ABPs CMS Kit with GrapesJS - based on an issue we created here.

We are just showing a proof of concept here.

You can find the source code here: https://github.com/Chrobyte/abp-grapesjs

Bootstrapping the app

So, we need an ABP application with CMS Kit. Let's do that.

First we create a new app using abp new Cb.Abp.GrapesJs -t app -u blazor-server --theme leptonx-lite.

After that, we install the cms kit.

Do not forget to run DbMigrator

Installing GrapesJS

We will make use of ABP's bundling system.

Step 1 - package.json, resource mapping and a script

add these npm packages to your package.json?.

...
  "grapesjs": "0.21.13",
  "grapesjs-blocks-basic": "1.0.2",
  "grapesjs-component-countdown": "1.0.2",
  "grapesjs-custom-code": "1.0.2",
  "grapesjs-parser-postcss": "1.0.3",
  "grapesjs-plugin-export": "1.0.12",
  "grapesjs-plugin-forms": "2.0.6",
  "grapesjs-preset-webpage": "1.0.3",
  "grapesjs-style-bg": "2.0.2",
  "grapesjs-tabs": "1.0.6",
  "grapesjs-tooltip": "0.1.8",
  "grapesjs-touch": "0.1.1",
  "grapesjs-tui-image-editor": "1.0.2",
  "grapesjs-typed": "2.0.1",
  "grapick": "0.1.13"
...

change abp.resourcemapping.js to this:

module.exports = {
    aliases: {
      "@node_modules": "./node_modules",
      "@libs": "./wwwroot/libs"
    },
    clean: [
      
    ],
    mappings: {
      "@node_modules/grapesjs/dist/grapes.min.js": "@libs/grapesjs/js",
      "@node_modules/grapesjs/dist/css/grapes.min.css": "@libs/grapesjs/css",
      "@node_modules/grapesjs/locale": "@libs/grapesjs/locale",
      "@node_modules/grapesjs-blocks-basic/dist": "@libs/grapesjs-blocks-basic",
      "@node_modules/grapesjs-component-countdown/dist": "@libs/grapesjs-component-countdown",
      "@node_modules/grapesjs-component-countdown/dist": "@libs/grapesjs-component-countdown",
      "@node_modules/grapesjs-custom-code/dist": "@libs/grapesjs-custom-code",
      "@node_modules/grapesjs-parser-postcss/dist": "@libs/grapesjs-parser-postcss",
      "@node_modules/grapesjs-plugin-export/dist": "@libs/grapesjs-plugin-export",
      "@node_modules/grapesjs-plugin-forms/dist": "@libs/grapesjs-plugin-forms",
      "@node_modules/grapesjs-preset-webpage/dist": "@libs/grapesjs-preset-webpage",
      "@node_modules/grapesjs-style-bg/dist": "@libs/grapesjs-style-bg",
      "@node_modules/grapesjs-style-gradient/dist": "@libs/grapesjs-style-gradient",
      "@node_modules/grapesjs-tabs/dist": "@libs/grapesjs-tabs",
      "@node_modules/grapesjs-tooltip/dist": "@libs/grapesjs-tooltip",
      "@node_modules/grapesjs-touch/dist": "@libs/grapesjs-touch",
      "@node_modules/grapesjs-tui-image-editor/dist": "@libs/grapesjs-tui-image-editor",
      "@node_modules/grapesjs-typed/dist": "@libs/grapesjs-typed",
      "@node_modules/grapick/dist": "@libs/grapick"
    }
};

now, under your wwwroot folder, add another folder called scripts and create a file called grapes-script.js with the following content:

// grapes.js

let editor = null;

window.initializeGrapes = function(elementId) {
    editor = grapesjs.init({
      container: '#' + elementId,
      fromElement: true,
      showOffsets: true,
      assetManager: {
        embedAsBase64: true,
      },
      selectorManager: { componentFirst: true },
      styleManager: {
        sectors: [{
          name: 'General',
          properties: [
            {
              extend: 'float',
              type: 'radio',
              default: 'none',
              options: [
                { value: 'none', className: 'fa fa-times' },
                { value: 'left', className: 'fa fa-align-left' },
                { value: 'right', className: 'fa fa-align-right' }
              ],
            },
            'display',
            { extend: 'position', type: 'select' },
            'top',
            'right',
            'left',
            'bottom',
          ],
        }, {
          name: 'Dimension',
          open: false,
          properties: [
            'width',
            {
              id: 'flex-width',
              type: 'integer',
              name: 'Width',
              units: ['px', '%'],
              property: 'flex-basis',
              toRequire: 1,
            },
            'height',
            'max-width',
            'min-height',
            'margin',
            'padding'
          ],
        }, {
          name: 'Typography',
          open: false,
          properties: [
            'font-family',
            'font-size',
            'font-weight',
            'letter-spacing',
            'color',
            'line-height',
            {
              extend: 'text-align',
              options: [
                { id: 'left', label: 'Left', className: 'fa fa-align-left' },
                { id: 'center', label: 'Center', className: 'fa fa-align-center' },
                { id: 'right', label: 'Right', className: 'fa fa-align-right' },
                { id: 'justify', label: 'Justify', className: 'fa fa-align-justify' }
              ],
            },
            {
              property: 'text-decoration',
              type: 'radio',
              default: 'none',
              options: [
                { id: 'none', label: 'None', className: 'fa fa-times' },
                { id: 'underline', label: 'underline', className: 'fa fa-underline' },
                { id: 'line-through', label: 'Line-through', className: 'fa fa-strikethrough' }
              ],
            },
            'text-shadow'
          ],
        }, {
          name: 'Decorations',
          open: false,
          properties: [
            'opacity',
            'border-radius',
            'border',
            'box-shadow',
            'background', // { id: 'background-bg', property: 'background', type: 'bg' }
          ],
        }, {
          name: 'Extra',
          open: false,
          buildProps: [
            'transition',
            'perspective',
            'transform'
          ],
        }, {
          name: 'Flex',
          open: false,
          properties: [{
            name: 'Flex Container',
            property: 'display',
            type: 'select',
            defaults: 'block',
            list: [
              { value: 'block', name: 'Disable' },
              { value: 'flex', name: 'Enable' }
            ],
          }, {
            name: 'Flex Parent',
            property: 'label-parent-flex',
            type: 'integer',
          }, {
            name: 'Direction',
            property: 'flex-direction',
            type: 'radio',
            defaults: 'row',
            list: [{
              value: 'row',
              name: 'Row',
              className: 'icons-flex icon-dir-row',
              title: 'Row',
            }, {
              value: 'row-reverse',
              name: 'Row reverse',
              className: 'icons-flex icon-dir-row-rev',
              title: 'Row reverse',
            }, {
              value: 'column',
              name: 'Column',
              title: 'Column',
              className: 'icons-flex icon-dir-col',
            }, {
              value: 'column-reverse',
              name: 'Column reverse',
              title: 'Column reverse',
              className: 'icons-flex icon-dir-col-rev',
            }],
          }, {
            name: 'Justify',
            property: 'justify-content',
            type: 'radio',
            defaults: 'flex-start',
            list: [{
              value: 'flex-start',
              className: 'icons-flex icon-just-start',
              title: 'Start',
            }, {
              value: 'flex-end',
              title: 'End',
              className: 'icons-flex icon-just-end',
            }, {
              value: 'space-between',
              title: 'Space between',
              className: 'icons-flex icon-just-sp-bet',
            }, {
              value: 'space-around',
              title: 'Space around',
              className: 'icons-flex icon-just-sp-ar',
            }, {
              value: 'center',
              title: 'Center',
              className: 'icons-flex icon-just-sp-cent',
            }],
          }, {
            name: 'Align',
            property: 'align-items',
            type: 'radio',
            defaults: 'center',
            list: [{
              value: 'flex-start',
              title: 'Start',
              className: 'icons-flex icon-al-start',
            }, {
              value: 'flex-end',
              title: 'End',
              className: 'icons-flex icon-al-end',
            }, {
              value: 'stretch',
              title: 'Stretch',
              className: 'icons-flex icon-al-str',
            }, {
              value: 'center',
              title: 'Center',
              className: 'icons-flex icon-al-center',
            }],
          }, {
            name: 'Flex Children',
            property: 'label-parent-flex',
            type: 'integer',
          }, {
            name: 'Order',
            property: 'order',
            type: 'integer',
            defaults: 0,
            min: 0
          }, {
            name: 'Flex',
            property: 'flex',
            type: 'composite',
            properties: [{
              name: 'Grow',
              property: 'flex-grow',
              type: 'integer',
              defaults: 0,
              min: 0
            }, {
              name: 'Shrink',
              property: 'flex-shrink',
              type: 'integer',
              defaults: 0,
              min: 0
            }, {
              name: 'Basis',
              property: 'flex-basis',
              type: 'integer',
              units: ['px', '%', ''],
              unit: '',
              defaults: 'auto',
            }],
          }, {
            name: 'Align',
            property: 'align-self',
            type: 'radio',
            defaults: 'auto',
            list: [{
              value: 'auto',
              name: 'Auto',
            }, {
              value: 'flex-start',
              title: 'Start',
              className: 'icons-flex icon-al-start',
            }, {
              value: 'flex-end',
              title: 'End',
              className: 'icons-flex icon-al-end',
            }, {
              value: 'stretch',
              title: 'Stretch',
              className: 'icons-flex icon-al-str',
            }, {
              value: 'center',
              title: 'Center',
              className: 'icons-flex icon-al-center',
            }],
          }]
        }
        ],
      },
      plugins: [
        'gjs-blocks-basic',
        'grapesjs-plugin-forms',
        'grapesjs-component-countdown',
        'grapesjs-plugin-export',
        'grapesjs-tabs',
        'grapesjs-custom-code',
        'grapesjs-touch',
        'grapesjs-parser-postcss',
        'grapesjs-tooltip',
        'grapesjs-tui-image-editor',
        'grapesjs-typed',
        'grapesjs-style-bg',
        'grapesjs-preset-webpage',
      ],
    });
};

window.getGrapesContent = function () {
  var html = editor.getHtml();
  var css = editor.getCss();
  return { html: html, css: css };
};

window.setGrapesContent = function (html, css) {
  editor.setComponents(html);
  editor.setStyle(css);
};

Step 2 - create and use bundle contributors

Create a new folder called Bundling on the root of your Blazor project to then add the two files below.

add GrapesJsScriptContributor.cs:

using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;

namespace Cb.Abp.GrapesJs.Blazor.Bundling;

public class GrapesJsScriptContributor : BundleContributor
{
    public override void ConfigureBundle(BundleConfigurationContext context)
    {
        context.Files.AddIfNotContains("/scripts/grapes-script.js");
        
        context.Files.AddIfNotContains("/libs/grapesjs/js/grapes.min.js");

        // languages
        context.Files.AddIfNotContains("/libs/grapes/locale/ar.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/bs.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/ca.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/de.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/el.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/en.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/fa.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/fr.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/he.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/index.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/it.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/ko.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/nb.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/nl.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/pl.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/pt.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/se.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/tr.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/vi.js");
        context.Files.AddIfNotContains("/libs/grapes/locale/zh.js");

        context.Files.AddIfNotContains("/libs/grapesjs-blocks-basic/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-blocks-basic/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-blocks-basic/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-component-countdown/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-component-countdown/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-component-countdown/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-custom-code/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-custom-code/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-custom-code/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-parser-postcss/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-parser-postcss/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-parser-postcss/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-plugin-export/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-plugin-export/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-plugin-export/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-plugin-forms/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-plugin-forms/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-plugin-forms/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-preset-webpage/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-preset-webpage/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-preset-webpage/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-style-bg/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-style-bg/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-style-bg/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-style-gradient/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-style-gradient/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-style-gradient/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-tabs/grapesjs-tabs.min.js");
        context.Files.AddIfNotContains("/libs/grapesjs-tabs/grapesjs-tabs.min.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-tooltip/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-tooltip/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-tooltip/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-touch/grapesjs-touch.min.js");
        context.Files.AddIfNotContains("/libs/grapesjs-touch/grapesjs-touch.min.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-tui-image-editor/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-tui-image-editor/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-tui-image-editor/index.js.map");

        context.Files.AddIfNotContains("/libs/grapesjs-typed/index.js");
        context.Files.AddIfNotContains("/libs/grapesjs-typed/index.d.js");
        context.Files.AddIfNotContains("/libs/grapesjs-typed/index.js.map");

        context.Files.AddIfNotContains("/libs/grapick/grapick.min.js");
    }
}

add GrapesJsStyleContributor.cs:

using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;

namespace Cb.Abp.GrapesJs.Blazor.Bundling;

public class GrapesJsStyleContributor : BundleContributor
{
    public override void ConfigureBundle(BundleConfigurationContext context)
    {
        context.Files.AddIfNotContains("/libs/grapesjs/css/grapes.min.css");
        context.Files.AddIfNotContains("/libs/grapick/grapick.min.css");
    }
}

Now, we will use these two contributors by updating the content of our blazor module.

private void ConfigureBundles()
    {
        Configure<AbpBundlingOptions>(options =>
        {
            // MVC UI
            options.StyleBundles.Configure(
                LeptonXLiteThemeBundles.Styles.Global,
                bundle =>
                {
                    bundle.AddFiles("/global-styles.css");
                }
            );

            //BLAZOR UI
            options.StyleBundles.Configure(
                BlazorLeptonXLiteThemeBundles.Styles.Global,
                bundle =>
                {
                    bundle.AddContributors(typeof(GrapesJsStyleContributor)); // ADD THIS
                    bundle.AddFiles("/blazor-global-styles.css");
                    //You can remove the following line if you don't use Blazor CSS isolation for components
                    bundle.AddFiles(new BundleFile("/Cb.Abp.GrapesJs.Blazor.styles.css", true));
                }
            );

             // ADD THIS
            options.ScriptBundles.Configure(
                BlazorLeptonXLiteThemeBundles.Scripts.Global,
                bundle =>
                {
                    bundle.AddContributors(typeof(GrapesJsScriptContributor));
                });
        });
    }

Step 3 - install libs

Now we need to run abp-install-libs so that we get our new npm packages.

Use GrapesJs

Now comes the exiting part.

As a matter of fact, I just realized that the free CMS Kit only has ASP .NET MVC :-| But we are using Blazor. Therefore we will just punch together something new in Blazor. Shame on me >.< It will be ugly - but after all we are just doing a PoC here.

First, Add Blazorise.Markdown to your *.csproj.

<PackageReference Include="Blazorise.Markdown" Version="1.5.2" />

Step 1 - a new component

Now, we'll create a new Component for the GrapesJS Editor under Components/CmsKit.

MyCmsContentBuilderComponent.razor

@inherits GrapesJsComponentBase

<div id="gjs" style="height: 250px; overflow: hidden; width: 100%;" class="gjs-editor-cont"></div>

MyCmsContentBuilderComponent.razor.cs

using Blazorise.Markdown;
using Blazorise;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
using Volo.Abp.Content;
using Volo.Abp;
using Volo.CmsKit.Admin.MediaDescriptors;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents;
using Volo.CmsKit.Web.Contents;
using Volo.CmsKit.Web.Pages.CmsKit.Components.Contents;
using System.Linq;
using Volo.CmsKit.Admin.Contents;

namespace Cb.Abp.GrapesJs.Blazor.Components.CmsKit;

public partial class MyCmsContentBuilderComponent
{
    [Inject]
    protected IJSRuntime JsRuntime { get; set; } = default!;

    [Parameter]
    public string Value { get; set; } = default!;

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }

    [Parameter]
    public string Css { get; set; } = default!;

    [Parameter]
    public EventCallback<string> CssChanged { get; set; }

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JsRuntime.InvokeVoidAsync("initializeGrapes", "gjs");

            await JsRuntime.InvokeVoidAsync("setGrapesContent", Value ?? string.Empty, Css ?? string.Empty);
        }
    }

    protected virtual Task OnValueChanged(string value)
    {
        Value = value;
        ValueChanged.InvokeAsync(value);
        return Task.CompletedTask;
    }

    public async Task<GrapesContent> GetGrapesContent()
    {
        var content = await JsRuntime.InvokeAsync<GrapesContent>("getGrapesContent");
        return content;
    }

    public class GrapesContent
    {
        public string Html { get; set; } = default!;
        public string Css { get; set; } = default!;
    }
}

Step 2 - Preparing the menu

You know the deal.

GrapesJsMenus.cs

namespace Cb.Abp.GrapesJs.Blazor.Menus;

public class GrapesJsMenus
{
    private const string Prefix = "GrapesJs";
    public const string Home = Prefix + ".Home";

    // ADD this
    public class CmsTest
    {
        private const string _myPrefix = Prefix + ".CmsTest";

        public const string Create = _myPrefix + ".Create";
        public const string Update = _myPrefix + ".Update";
    }
}

GrapesJsMenuContributor.cs

using System.Threading.Tasks;
using Cb.Abp.GrapesJs.Localization;
using Cb.Abp.GrapesJs.MultiTenancy;
using Volo.Abp.Identity.Blazor;
using Volo.Abp.SettingManagement.Blazor.Menus;
using Volo.Abp.TenantManagement.Blazor.Navigation;
using Volo.Abp.UI.Navigation;

namespace Cb.Abp.GrapesJs.Blazor.Menus;

public class GrapesJsMenuContributor : IMenuContributor
{
    public async Task ConfigureMenuAsync(MenuConfigurationContext context)
    {
        if (context.Menu.Name == StandardMenus.Main)
        {
            await ConfigureMainMenuAsync(context);
        }
    }

    private Task ConfigureMainMenuAsync(MenuConfigurationContext context)
    {
        var administration = context.Menu.GetAdministration();
        var l = context.GetLocalizer<GrapesJsResource>();

        context.Menu.Items.Insert(
            0,
            new ApplicationMenuItem(
                GrapesJsMenus.Home,
                l["Menu:Home"],
                "/",
                icon: "fas fa-home",
                order: 0
            )
        );

        // ADD this
        context.Menu.Items.Insert(
            1,
            new ApplicationMenuItem(
                GrapesJsMenus.CmsTest.Create,
                l["Create"],
                "~/my-cms-page-create",
                icon: "fas fa-home",
                order: 0
            )
        );

        // ADD this
        context.Menu.Items.Insert(
            2,
            new ApplicationMenuItem(
                GrapesJsMenus.CmsTest.Update,
                l["Update"],
                "~/my-cms-page-update",
                icon: "fas fa-home",
                order: 0
            )
        );

        if (MultiTenancyConsts.IsEnabled)
        {
            administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 3);
        }
        else
        {
            administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
        }

        administration.SetSubItemOrder(IdentityMenuNames.GroupName, 4);
        administration.SetSubItemOrder(SettingManagementMenus.GroupName, 5);

        return Task.CompletedTask;
    }
}

Step 3 - Create page

under Components/Pages/CmsKit

CmsPageCreate.razor

@page "/my-cms-page-create"

@attribute [Authorize(CmsKitAdminPermissions.Pages.Create)]

@using Blazorise.Components
@using Blazorise.Markdown
@using Cb.Abp.GrapesJs.Blazor.Components.CmsKit
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.AspNetCore.Components.Web
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
@using Volo.CmsKit.Admin.Pages
@using Volo.CmsKit.Localization
@using Volo.CmsKit.Permissions
@using Volo.CmsKit.Admin.Web.Pages.CmsKit
@using Volo.Abp.DependencyInjection;


@inherits GrapesJsComponentBase


@inject AbpBlazorMessageLocalizerHelper<CmsKitResource> LH

@* ************************* PAGE HEADER ************************* *@
<PageHeader Title="@L["Pages"]">

</PageHeader>

<Card>
    <CardBody>
        <Form>
            <Validations @ref="@ValidationsRef" Model="@Page" ValidateOnLoad="false">
                <Validation MessageLocalizer="@LH.Localize">
                    <Field>
                        <FieldLabel>@L["Title"] *</FieldLabel>
                        <TextEdit @bind-Text="Page.Title" Autofocus="true">
                            <Feedback>
                                <ValidationError/>
                            </Feedback>
                        </TextEdit>
                    </Field>
                </Validation>
                <Validation MessageLocalizer="@LH.Localize">
                    <Field>
                        <FieldLabel>@L["Slug"] *</FieldLabel>
                        <Tooltip Text="@L["PageSlugInformation"]">
                            <TextEdit @bind-Text="Page.Slug" Autofocus="false">
                                <Feedback>
                                    <ValidationError/>
                                </Feedback>
                            </TextEdit>
                        </Tooltip>
                    </Field>
                </Validation>
                <ExtensionProperties TEntityType="CreatePageInputDto" TResourceType="CmsKitResource" Entity="@Page" LH="@LH" ModalType="ExtensionPropertyModalType.CreateModal" />
            </Validations>
            
            <Tabs @bind-SelectedTab="@SelectedTab">
                <Items>
                    <Tab Name="content">@L["Content"]</Tab>
                    <Tab Name="script">@L["Script"]</Tab>
                </Items>
                <Content>
                    <TabPanel Name="content">
                        <MyCmsContentBuilderComponent @ref="@ContentBuilder" @bind-Value="@Page.Content" @bind-Css="@Page.Style" />
                    </TabPanel>
                    <TabPanel Name="script">
                        <Markdown @bind-Value="@Page.Script" LineNumbers="true" HideIcons="@HideIcons" AutoDownloadFontAwesome="false" UploadImage="false">
                            <Toolbar>
                                <MarkdownToolbarButton Action="MarkdownAction.Bold"/>
                            </Toolbar>
                        </Markdown>
                    </TabPanel>
                </Content>
            </Tabs>
        </Form>
    </CardBody>
    <CardFooter>
         <Button Color="Color.Primary" Clicked="@CreatePageAsync">@L["Submit"]</Button>
    </CardFooter>
</Card>

CmsPageCreate.razor.cs

using Blazorise;
using Cb.Abp.GrapesJs.Blazor.Components.CmsKit;
using Microsoft.AspNetCore.Components;
using System;
using System.Threading.Tasks;
using Volo.CmsKit.Admin.Pages;

namespace Cb.Abp.GrapesJs.Blazor.Components.Pages.CmsKit;

public partial class CmsPageCreate
{
    [Inject]
    public IPageAdminAppService PageAdminAppService { get; set; } = default!;

    [Inject]
    public NavigationManager NavigationManager { get; set; } = default!;

    public CreatePageInputDto Page { get; set; } = new CreatePageInputDto();

    private Validations ValidationsRef = default!;

    private readonly string[] HideIcons = ["bold"];

    private string SelectedTab = "content";

    public MyCmsContentBuilderComponent ContentBuilder { get; set; } = default!;

    protected async Task CreatePageAsync()
    {
        try
        {
            var content = await ContentBuilder.GetGrapesContent();
            Page.Content = content.Html;
            Page.Style = content.Css;

            if (await ValidationsRef.ValidateAll())
            {
                await PageAdminAppService.CreateAsync(Page);
            }
        }
        catch (Exception e)
        {
            await HandleErrorAsync(e);
        }
    }
}

Step 4 - Update page

under Components/Pages/CmsKit

In order to call this - we need to pass the id to url :-|

Please check the following links as I cannot put the code in here anymore due to something crashing right now -.- https://github.com/Chrobyte/abp-grapesjs/blob/master/src/Cb.Abp.GrapesJs.Blazor/Components/Pages/CmsKit/CmsPageUpdate.razor

https://github.com/Chrobyte/abp-grapesjs/blob/master/src/Cb.Abp.GrapesJs.Blazor/Components/Pages/CmsKit/CmsPageUpdate.razor.cs

Result

Last Modification : 9/11/2024 1:47:23 PM


abp

Cookies

This site uses cookies.
This website uses cookies. Some cookies are technically necessary, others are used to analyze user behavior in order to optimize the offer. You can find an explanation in our Privacy Policy.