[Vorple] Wrapping output paragraphs in <p> tags

I’d like to have a bit more control on typography (margins and such), so I want to have Vorple’s output wrapped in <p> tags (instead of having 2 line breaks acting as a pseudo-paragraph break). It could also be useful to fade in each paragraph sequentially. (For simplicity we can assume there’ll never be more than 2 line breaks in a row.)

Since all the output is wrapped in <span>s, we need to detect every <span> that ends with a line break followed by a <span> containing only a line break, then move all the contents up to this point in a <p> tag.

Any idea on the best and most practical way to do that? I could use a Vorple event listener to grab the output before line and char inputs and parse it, I think? And then I’d have to move that output in a <p>.

(Maybe another solution: hide the Vorple container, use a MutationObserver to watch the ouput, and move the output in a place I have total control?)

Or maybe, staying withing Inform: create a <p> tag, move focus within it, and hack into Inform line-break system to create a new <p> and move focus inside it every time Inform writes a line break? But it’s seems rather complicated to mess with Inform’s line-break system, and it may interact strangely when we manually change output focus or place HTML elements manually with Vorple. So it looks a bit fragile to me.

It also becomes more complicated if we have to take care of changes in output focus, and if there is output after a key press that is appended to the current paragraph instead of being in a new one.

So any advice?

I guess I could hack myself a solution, but I prefer hearing other people first.

2 Likes

It’s a good idea, but it’s not possible to make the spans display as blocks, because there can be multiple spans in a single paragraphs (when there are links or different styles in a paragraph). And I think it would get fiddly to set the spacing precisely anyway.


After thinking about it a bit more, I believe it’s the Haven library that manages the display? So it may be easier to modify Haven to make it output paragraphs.

There isn’t any simple and clean way because there’s no real concept of a paragraph break in Inform. The event listener route is probably the best way, this should get you started:

function brToP(event) {
  let p = $('<p>');
  $('.turn.previous').append(p);
  $('.turn.previous span:not(.prompt-prefix):not(.prompt-input)').each(function() {
    if (this.childNodes.length === 1 && /^\s*\n\s*$/m.test(this.childNodes[0].textContent)) {
      p = $('<p>');
      p.appendTo($('.turn.previous'));
      $(this).remove();
    } else {
      p.append(this);
    }
  });

  // remove the last empty paragraph
  const $lastPara = $('.turn.previous p:last');
  if ($lastPara.text() === "") {
    $lastPara.remove();
  }
}

vorple.addEventListener(["expectCommand", "expectKeypress"], brToP);

You might need to tweak it at least by removing line break characters from the ends of the span elements.

I finally got time to try all this, so first of all, sorry for the delay. And I wasn’t expecting a full code snippet, only advice, so thanks for that!

I’ve made some change to the code:

  • I decided to hide the #vorple element and move its contents each turn to a #displayed-vorple element. Like that, I have better control of what is displayed, and I avoid the flicker while the page is rearranging the elements into paragraphs.
  • Your code looped through the spans that are descendants of .turn.previous, so it didn’t take care of other elements (divs, images, links, lists and so on). So I instead loop over all the direct children of .turn.previous, and only wrap the inline ones into paragraphs.
  • I don’t append a <p> directly when encountering a span with a line break. I instead keep track of whether one has been encountered and append the paragraph only when there’s content to be added. That way, I don’t end up with empty paragraphs when there are more than 2 line breaks in a row (i.e. I collapse series of mores than 2 line breaks into a single paragraph break).
  • For some reason I felt like using Vanilla JS instead of jQuery for DOM manipulation.
let currentParagraph = null
let isParagraphBreakPending = false

function brToP(event) {
    const displayedVorpleContainer = document.getElementById("displayed-vorple")
    const previousTurn = document.querySelector("#vorple .turn.previous")

    let child
    while (child = previousTurn.firstElementChild) {
        const displayStyle = window.getComputedStyle(child).display
        if (displayStyle === "inline" || displayStyle === "inline-block" || displayStyle == "inline-flex") {
            if (
                child.tagName === "SPAN"
                && child.childNodes.length === 1
                && /^\s*\n\s*$/m.test(child.firstChild.textContent)
            ) {
                isParagraphBreakPending = true
                child.remove()
            } else {
                if (isParagraphBreakPending) {
                    isParagraphBreakPending = false
                    currentParagraph = document.createElement("p")
                    displayedVorpleContainer.append(currentParagraph)
                }
                currentParagraph.append(child)
            }
        } else {
            isParagraphBreakPending = true
            displayedVorpleContainer.append(child)
        }
    }
    // Move the line input form to #displayed-vorple.
    displayedVorpleContainer.append(document.querySelector("#vorple form#lineinput"))
}

Things that are not taken care of right now:

  • Multiple output focus (e.g. if we want to have multiple windows). It should be quite easy to solve, just generalise brToP to take an element as an argument so that it can wrap the contents of any elements into paragraphs.
  • When waiting for a key press, the output is only processed at the next line input. This is because we need to fetch the contents of .turn.current instead of .turn.previous.

I’m also wondering if there can be side effects to moving Vorple’s output to another element. For example, it looks like Vorple still removes the last class from the line .lineinput, or still handles the line input form correctly, so it seems to be OK.


Of course, that’s why I asked! But basically I just wanted to collapse multiple line breaks into a paragraph break, which your code did nicely (along with the small additional things I added). I’m aware there won’t be a universal solution for this, and there’ll surely be edge cases not handled, but if I’m careful, there likely won’t be any issue (or at least I hope there won’t).

I don’t remove them yet, since they don’t seem to render when at the end of a paragraph, but I’ll keep that in mind if an issue arises.

Once again, thanks for your help!

1 Like