collective / volto-hydra

A volto addon to let you edit content in realtime when you have many frontends written in any framework
1 stars 2 forks source link

I can edit a block inline in the preview #5

Open djay opened 4 months ago

djay commented 4 months ago

User stories (As an editor...)

Out of scope

Technical

Image

Image

Image

djay commented 4 months ago

As @JeffersonBledsoe pointed out plone api gives rich text as an json. like

{
      "@type": "slate", 
      "plaintext": "refer the complaint to the NSW Police Force or NSW Crime Commission ask the NSW Police Force or NSW Crime Commission to investigate the complaint, and we\u2019ll then review their final report ask for more information or make more inquiries do an investigation ourselves refer it to another agency take no more action", 
      "value": [
        {
          "children": [
            {
              "children": [
                {
                  "text": "refer the complaint to the NSW Police Force or NSW Crime Commission"
                }
              ], 
              "type": "li"
            }, 
            {
              "children": [
                {
                  "text": "ask the NSW Police Force or NSW Crime Commission to investigate the complaint, and we\u2019ll then review their final report"
                }
              ], 
              "type": "li"
            }, 
            {
              "children": [
                {
                  "text": "ask for more information or make more inquiries"
                }
              ], 
              "type": "li"
            }, 
            {
              "children": [
                {
                  "text": "do an investigation ourselves"
                }
              ], 
              "type": "li"
            }, 
            {
              "children": [
                {
                  "text": "refer it to another agency "
                }
              ], 
              "type": "li"
            }, 
            {
              "children": [
                {
                  "text": "take no more action   "
                }
              ], 
              "type": "li"
            }
          ], 
          "type": "ul"
        }
      ]
    }

Slate will turn that into html and the the current code works on it as based on the html generated.

<div
  role="textbox"
  aria-multiline="true"
  spellcheck="true"
  data-slate-editor="true"
  data-slate-node="value"
  contenteditable="true"
  zindex="-1"
  style="position: relative; white-space: pre-wrap; overflow-wrap: break-word"
>
  <p data-slate-node="element">
    <span data-slate-node="text"
      ><span data-slate-leaf="true"
        ><span data-slate-string="true">This is editable </span></span
      ></span
    ><span data-slate-node="text"
      ><span data-slate-leaf="true"
        ><strong><span data-slate-string="true">rich</span></strong></span
      ></span
    ><span data-slate-node="text"
      ><span data-slate-leaf="true"
        ><span data-slate-string="true"> text, </span></span
      ></span
    >
  </p>
  <blockquote data-slate-node="element">
    <span data-slate-node="text"
      ><span data-slate-leaf="true"
        ><span data-slate-string="true">A wise quote.</span></span
      ></span
    >
  </blockquote>
  <p data-slate-node="element" style="text-align: center">
    <span data-slate-node="text"
      ><span data-slate-leaf="true"
        ><span data-slate-string="true">Try it out for yourself!</span></span
      ></span
    >
  </p>
</div>

But sine the restapi only gives us the json version and it's up to the frontend really to say how a given part of the rich text should be rendered we will need a method to deal with this.

I'd propose its done in a similar way to blocks. When the block goes into to edit mode the frontend should mark up where the starts and ends of the nodes on the json so that a mapping can be made back to the json representation.

Upon clicking and the html goes into editable mode,

This allows all the rendering of the all the UI to be on the adminui side and all the tracking of html events to be in the iframebridge code, and all the frontend has to do is turn the richtext to html and mark it up with codes. What this also means is that the adminui could allow ttw adding a of an extra text style into the slate toolbar and the frontend can be in charge of how that style appears.

the other consequence of this is that if there is a richtext version on the sidebar (which there should be) then that is going to appear different to the inline version and any custom formats will have to be shown generically.

In terms of code this means what slate does now will be split between two code bases. Some in the iframebridge listening to events and doing the translating and the rest of the slate bar running in the adminui but dealing with json changes not html changes.

djay commented 4 months ago

The UI for a blank image or video block could be a little more tricky? Does this UI need to be there for every blank block or only on focus?

Image

djay commented 3 months ago

@MAX-786 @JeffersonBledsoe let me try and simplify what I said above.

if the block has

{
      "@type": "slate", 
      "plaintext": "A line", 
      "value": [
        {
          "children": [
            {
              "children": [
                {
                  "text": "a bullet"
                }
              ], 
              "type": "li"
            }, 
          ], 
          "type": "ul"
        }
      ]
    }

Then hydra js will first have to add some kind of id to the nodes

{
      "@type": "slate", 
      "plaintext": "A line", 
      "value": [
        {
          "children": [
            {
              "children": [
                {
                  "text": "a bullet"
                }
              ], 
              "type": "li"
              "id":2
            }, 
          ], 
          "type": "ul"
          "id": 1
        }
      ]
    }

then this is passed to the frontend to render. if they want it to be editable they add in a data attribute saying what field in the block this slate refers to and also the ids of each of the nodes. It is possible to have a single block with many different slate fields in it. I think there is also concept of a simple text field with no markup as well but thats just a simplified version of this with no nodes.

<div data-hydra-editable="value">
   A line<ul data-hydra-node="1"><li data-hydra-node="2">a bullet</li><ul>
</div>

The hydra.js adds in the contenteditable and put in the listeners

<div data-hydra-editable="value" contenteditable="true">
   A line<ul data-hydra-node="1"><li data-hydra-node="2">a bullet</li><ul>
</div>

Now if some change is made to the text on the frontend then hydra.js should be able to translate that back into json and send that change over to the admin UI. it should always be just a matter of counting the chars since the last node.

so if the word bullet was removed then hydra.js can work out that chars 2-7 were removed of node 2 and so make the same change to the json structure it kept in memory and send that to the admin UI

{
      "@type": "slate", 
      "plaintext": "A line", 
      "value": [
        {
          "children": [
            {
              "children": [
                {
                  "text": "a "
                }
              ], 
              "type": "li"
            }, 
          ], 
          "type": "ul"
        }
      ]
    }

If section of the text is selected then it will need to send over which nodes (after translation) were selected and/or teh offset of the start and end of that text from the node, to the admin UI and that will respond back with what buttons that might enable, like the link icon, or bold etc. e.g like selected node 1: chars 3-7.

If someone clicks on the bold button then that needs to be processed on the admin UI side and that will then send back an updated json which the frontend then will rerender. AdminUI should also indicate where the cursor should be now relative to the start of the nearest node and hydra.js will move the cursor there. I guess the only tricky part of this whole translation process is that if the frontend decides to put in addional html elements that aren't nodes. So these might need to marked as non editable so counting of letters from the nearest start of a node still works. but I think this is niche case that can be handled later. Mostly you'd expect the fronend to customised html elements and classes etc, not the text.

if someone makes a change to the text in sidebar instead then the changed json is sent to the frontend to be rendered again. but shouldn't need to position the cursor because the focus is now on the sidebar.

But if you look at the slate code what it's doing is not that different. its translating back and forth between the html version and the json version of the markup. in our case hydra.js is doing the translation and listening for changes. One issue with this of course is that how the markup looks like in the sidebar and how it looks like in the frontend could be very different but that should be fine most of the time. (later on the frontend will be able to specify custom slate styles that can be applied and these would have to be indicated in a generic way on the sidebar since it won't know how to display those).

After this is working then can listen to various shortcuts like paste, or enter to create a new block or slash to bring up the change block menu. and most of those should be a matter of sending the cursor/selection and to the adminUI and asking what should happen. Like paste, the admin UI keeps track of the clipboard.

If someone hits enter this will need to be a special event and the admin UI should decide what happens in this case. because if its at the end of a bullet then admin UI decides this should create a new bullet and if its in the middle of plain text then it creates a whole new block and the adminUI should decide if that should create a new block and what kind of block that should be etc.

So mostly I think the frontend can be pretty dumb, just recieving new json and rendering it and let hydra deal with what is being edited, where the cursor is and whats selected. it just has to decide to what fields it wants to make editable by adding in the extra data attributes when it renders them.

djay commented 1 month ago

@MAX-786 I notice in your frontend you have many differences compared to the documenation.

<div data-slate-editor="true" data-slate-node="value" contenteditable="false" zindex="-1" style="position: relative; white-space: pre-wrap; overflow-wrap: break-word;"><ol data-slate-node="element" data-hydra-node="1"><li data-slate-node="element" data-hydra-node="2"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="3"><span data-slate-string="true">Add a new page inside Test Page. asda</span></span></span></li><li data-slate-node="element" data-hydra-node="4"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="5"><span data-slate-string="true">Go to edit mode.</span></span></span></li><li data-slate-node="element" data-hydra-node="6"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="7"><span data-slate-string="true">jjjhj</span></span></span></li><li data-slate-node="element" data-hydra-node="8"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="9"><span data-slate-string="true">Click on an empty block and border, add btn &amp; quanta Toolbar appears on top of it. </span></span></span></li><li data-slate-node="element" data-hydra-node="10"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="11"><span data-slate-string="true">Click on "+" to add a new block below current block. Currently we only support </span></span></span><strong data-slate-node="element" data-hydra-node="12"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="13"><span data-slate-zero-width="z" data-slate-length="0"></span></span></span><em data-slate-node="element" data-hydra-node="14"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="15"><span data-slate-string="true">TEXT</span></span></span></em><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="16"><span data-slate-string="true">,</span></span></span></strong><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="17"><span data-slate-string="true"> </span></span></span><strong data-slate-node="element" data-hydra-node="18"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="19"><span data-slate-zero-width="z" data-slate-length="0"></span></span></span><em data-slate-node="element" data-hydra-node="20"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="21"><span data-slate-string="true">IMAGE</span></span></span></em><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="22"><span data-slate-zero-width="z" data-slate-length="0"></span></span></span></strong><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="23"><span data-slate-string="true"> and </span></span></span><strong data-slate-node="element" data-hydra-node="24"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="25"><span data-slate-string="true">TEASER</span></span></span></strong><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="26"><span data-slate-string="true"> block types.</span></span></span></li><li data-slate-node="element" data-hydra-node="27"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="28"><span data-slate-string="true">Edit Text block's text from sidebar by typing in Body field.</span></span></span></li><li data-slate-node="element" data-hydra-node="29"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="30"><span data-slate-string="true">Add image in Image block from sidebar by using 'image' widget.</span></span></span></li><li data-slate-node="element" data-hydra-node="31"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="32"><span data-slate-string="true"> Select Order Tab in Sidebar and you can rearrange Blocks.</span></span></span></li><li data-slate-node="element" data-hydra-node="33"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="34"><span data-slate-string="true">Click on a block in Order Tab and this will open up block's settings in sidebar and frontend will scroll this block into view.</span></span></span></li><li data-slate-node="element" data-hydra-node="35"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="36"><span data-slate-string="true">Click on '3 dots' in quanta Toolbar to open dropdown menu and click 'Remove' to delete a block.</span></span></span></li><li data-slate-node="element" data-hydra-node="37"><span data-slate-node="text"><span data-slate-leaf="true" data-hydra-node="38"><span data-slate-string="true">To be continued... :)</span></span></span></li></ol></div>

Can we get rid of all these or is there some reason why they needed?