No internet connection
  1. Home
  2. Ideas

Line edits & quote backlinks

By Suspended Reason @suspendedreason2021-02-23 01:17:28.418Z

Continuing this conversation with @KajMagnus on listeners for page loading.

By the time I'm done with a day's programming at my job, most of my coding energy has been sapped, and I end up code a bit quick and dirty, so the script isn't elegant, but the basic functionality is pretty simple and should give an idea. Some work will be done using the innerHTML, instead of the innerText, if I want to preserve formatting

Monkeypatching into loadPageJson is working now, but my main issue has become that, once a DOM element's innerText is updated manually with the script, it will persevere on other discussion pages; if the script adds backlinks to a page, that new HTML will stick around even when the user navigates (albeit directly) to a different discussion thread (e.g. via sidebar).

Here's a link to a discussion on our forum] for full context; if the script is running, the quoted

Newtonian mechanics weren’t “wrong” in some binary so much as they were incomplete; they correctly mapped most cases humans cared about, and worked just fine for architecture or artillery fire.

would be backlinked to the relevant text section in the top-level post.

const backlink = (doc) => {
  const op = doc.getElementById('post-1');
  if (!op) return false;

  // Due to deletions & moved comments, there may be several
  // missing posts ids in a row. We'll use 3 sequential missing
  // comments as a heuristic for reaching the end of the comments.
  let i = 2;
  let allCommentsAccounted = false;
  let sequentialMissingComments = 0;
  const comments = [];
  while (!allCommentsAccounted) {
    const comment = doc.getElementById(`post-${i}`);
    if (comment && comment.innerHTML) {
      comments.push(comment);
      sequentialMissingComments = 0;
    } else {
      sequentialMissingComments++;
    }
    i++;
    if (sequentialMissingComments > 3) {
      allCommentsAccounted = true;
    }
  }
  
  // Extracting all blockquotes from comments
  const commentsPlainText = {};
  comments.forEach((comment) => {
    const blockquotes = comment.getElementsByTagName('blockquote');
    if (blockquotes && blockquotes.length > 0) {
      commentsPlainText[comment.id] = [].slice.call(blockquotes).map((b) => b.innerText);
    }
  })
  
  // Updating Op and comment HTML with <mark> tags, anchors
  let newOpHTML = op.innerHTML;
  Object.entries(commentsPlainText).forEach(([commentId, plainTextArray]) => {
    const newComment = doc.getElementById(commentId);
    if (newComment) {
      let newCommentHTML = newComment.innerHTML;
      plainTextArray.forEach((plainText) => {
        if (op.innerText.includes(plainText)) {
          const id = `backlink-${commentId}`;
          const url = doc.URL.split('#')[0] + '#' + commentId;
          newOpHTML = newOpHTML.replace(plainText, `<mark id='${id}'><a href='${url}'>${plainText}</a></mark>`);
          newCommentHTML = newCommentHTML.replace(plainText, `<a href='#${id}'>${plainText}</a>`)
        }
      });
      doc.getElementById(commentId).innerHTML = newCommentHTML;
    }
  })
  if (newOpHTML !== op.innerHTML) {
    doc.getElementById('post-1').innerHTML = newOpHTML;
    localStorage.setItem('reload', 'true');
  }
};

// Monkey-patching into the Talkyard "page loaded" listener,
// adding backlinks after DOM data is rendered
const origLoadPageJson = debiki2.Server.loadPageJson;
debiki2.Server.loadPageJson = function(path, origOnDone) {
  origLoadPageJson(path, function(response) {
    origOnDone(response);
    if (localStorage.getItem('reload') === 'true') {
      localStorage.setItem('reload', 'false');
      location.reload();
    } else {
      backlink(document);
    }
  });
}

// Runs on initial page load
setTimeout(() => {
  backlink(document);
}, 0)

Ideally I'd be self-hosting and just edit the relevant codebase sections myself, so I wouldn't have to do DOM manipulation nonsense from an HTML <script> element, but I'm low on time/bandwidth and, to be honest, getting things running & making changes across TY's multiple repos seems a little intimidating.

I think having fuzzy text-matching is a good idea, in case minor edits get made. There has to be some system for handling the linking/UI if multiple commenters end up quote-replying to the same section of text—a modal popup with a list of the quoting comments might be ideal. Some worry that this would get complicated if quote-replying users chose different but overlapping sections of text—where & how do you insert the <mark> tags then?

An alternate approach that might simplify some of these issues is to insert a positioned icon or marker to the side of the text body. This would look more like the comment icon in Notion:

EDIT: Right now, because of the DOM manipulation persevering across page changes, I'm saving a boolean to localStorage that reflects whether or not DOM manipulation (i.e. the manual insertion of line edits & quote backlinks) took place on the previous page. If it has, I force a page reload. It's far from ideal, so looking for a better way.

  • 0 replies