Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I can edit a block inline in the preview #5

Open
16 tasks
djay opened this issue May 13, 2024 · 3 comments
Open
16 tasks

I can edit a block inline in the preview #5

djay opened this issue May 13, 2024 · 3 comments
Labels

Comments

@djay
Copy link
Member

djay commented May 13, 2024

User stories (As an editor...)

Out of scope

  • I can edit multiple text fields in the same block
  • I can edit a link field in a block inline #68
  • I can edit rich text either inline or in the sidebar
  • I can Use keyboard shortcuts to format text (e.g. bullets)
  • I can DND an image onto an image block on the preview
  • as an integrator I can specify which slate builtin styles I support and add extra custom styles (with schema)

Technical

  • Switch on contentEditable in frontend
  • Track keyboard strokes/changes/selections
  • Send changes to current editing back from admin UI

Image

Image

Image

@djay djay added the epic label May 13, 2024
@djay djay changed the title I can edit a block inline I can edit a block inline in the preview May 13, 2024
@djay
Copy link
Member Author

djay commented May 14, 2024

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,

  • adding or removing text will get sent to the adminui as changes to the json representation
  • a selection event on the html will result in a translation from the html, to a selection of the json structure.
    • that is sent over the bridge as a start and end of the json.
  • Then if that results in a formatting of that text
    • a change in the json that is sent back,
    • and a where the selection should now be is sent back
  • the html rerendered from the new json and the cursor position set again.

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
Copy link
Member Author

djay commented May 15, 2024

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

  • could have a generic empty image for when the block isn't in focus and then replace with a UI outside/ontop the iframe on focus
    • means UI isnt there most of the time and not when scrolling
  • could maybe have another iframe inside the frontend which goes back to the editor UI?
    • CSP nightmare?
  • OR try and put the basics into the frontend but in a very isolated way so its not likely to get messed with by the frondend CSS/JS?

@djay
Copy link
Member Author

djay commented Jul 2, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant