oqtane / oqtane.framework

CMS & Application Framework for Blazor & .NET MAUI
http://www.oqtane.org
MIT License
1.84k stars 531 forks source link

[ENH] I am trying to add quill.js and vvveb.js page builder to oqtane #4484

Closed papyr closed 1 month ago

papyr commented 1 month ago

integrating a dragdrop page builder into oqtane

Describe the enhancement - It would be nice to allow editors to create content from the frontent like so.. with quill.js and vvveb.js / gape.js

hello i was trying to integrate into CMS, added a ref to quill maybe it will help others, I dont know how to make it a nuget package with options to add on.. can you help refine this so it can handle the structured aspect with low frictor to the developer

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
    <PackageReference Include="Quill" Version="1.3.7" />
  </ItemGroup>
</Project>

# model

namespace QuillIntegration.Models
{
    public class ContentModel
    {
        public int Id { get; set; }
        public string Content { get; set; }
    }
}

ContentController.cs

using Microsoft.AspNetCore.Mvc;
using QuillIntegration.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using QuillIntegration.Data;

namespace QuillIntegration.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ContentController : ControllerBase
    {
        private readonly QuillIntegrationContext _context;

        public ContentController(QuillIntegrationContext context)
        {
            _context = context;
        }

        // GET api/content
        [HttpGet]
        public async Task<ActionResult<IEnumerable<ContentModel>>> GetContents()
        {
            return await _context.ContentModels.ToListAsync();
        }

        // GET api/content/5
        [HttpGet("{id}")]
        public async Task<ActionResult<ContentModel>> GetContent(int id)
        {
            var content = await _context.ContentModels.FindAsync(id);

            if (content == null)
            {
                return NotFound();
            }

            return content;
        }

        // POST api/content
        [HttpPost]
        public async Task<ActionResult<ContentModel>> PostContent(ContentModel content)
        {
            _context.ContentModels.Add(content);
            await _context.SaveChangesAsync();

            return CreatedAtAction(nameof(GetContent), new { id = content.Id }, content);
        }

        // PUT api/content/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutContent(int id, ContentModel content)
        {
            if (id != content.Id)
            {
                return BadRequest();
            }

            _context.Entry(content).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!await ContentExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // DELETE api/content/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteContent(int id)
        {
            var content = await _context.ContentModels.FindAsync(id);

            if (content == null)
            {
                return NotFound();
            }

            _context.ContentModels.Remove(content);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private async Task<bool> ContentExists(int id)
        {
            return await _context.ContentModels.AnyAsync(e => e.Id == id);
        }
    }
}

using Microsoft.EntityFrameworkCore;
using QuillIntegration.Models;

namespace QuillIntegration.Data
{
    public class QuillIntegrationContext : DbContext
    {
        public QuillIntegrationContext(DbContextOptions<QuillIntegrationContext> options)
            : base(options)
        {
        }

        public DbSet<ContentModel> ContentModels { get; set; }
    }
}

my test view Views/Home/TestIndex.cshtml

how can I improve this to be rich editor?

@{
    ViewData["Title"] = "Home Page";
}

<div id="editor" style="height: 300px;"></div>

<script src="~/lib/quill/dist/quill.min.js"></script>
<script>
    var quill = new Quill('#editor', {
        modules: {
            toolbar: [
                [{ header: [1, 2, false] }],
                ['bold', 'italic', 'underline'],
                ['image', 'code-block']
            ]
        },
        placeholder: 'Type your content here...',
        theme: 'snow'
    });

    document.getElementById('save-button').addEventListener('click', function() {
        var content = quill.getContents();
        var contentString = JSON.stringify(content);
        fetch('/api/Content', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ Content: contentString })
        })
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('Error:', error));
    });
</script>

using my Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using QuillIntegration.Data;

namespace QuillIntegration
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<QuillIntegrationContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
mdmontesinos commented 1 month ago

Quill.js is already the default RichTextEditor used in Oqtane. See the Html/Text module.

sbwalker commented 1 month ago

@papyr Oqtane 5.2 now has a provider model for Text Editors (https://github.com/oqtane/oqtane.framework/discussions/4441). The default Text Editor in Oqtane is based on QuillJS (1.3.4). If you want to create an alternate QuillJS Text Editor you can implement the ITextEditor interface. In regards to vvveb.js I would suggest you add this functionality to a custom Theme.

papyr commented 1 month ago

ok thanks guys, how do we add more component to that library of drag drop, like a chart or a graph widget so that a front end user can add that widget.

Can you share a recommendation and some steps on how we can do that.

Unlike previous orchard or DNN, oqtane seems to be easier & suitable to allow business entities to capture and display transaction or business data for small business such as sales & farmers-markets/venues/vendors, can you please share some guidance on how to go about this, when there is master-child/FK relationship, and a picture/visual of the classes needed to be added in each layer of your architecture and help would be greatly appreciated by the developers.

Would this be a widget, module..

I may also create a T4 template and issue a pull request if it works out for me..

sbwalker commented 1 month ago

@papyr Oqtane already has a templating capability:

This will scaffold a completely functional project for you with all of the layers/assets as well as a build script which deploys the assemblies to correct location when you compile.