add js to quote part of a post
This commit is contained in:
		@@ -94,6 +94,7 @@
 | 
			
		||||
{% if editing %}
 | 
			
		||||
  {% set postclass = postclass + " editing" %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% set post_permalink = url_for("threads.thread", slug = post['thread_slug'], after = post['id'], _anchor = ("post-" + (post['id'] | string))) %}
 | 
			
		||||
<div class=" {{ postclass }}" id="post-{{ post['id'] }}">
 | 
			
		||||
  <div class="usercard">
 | 
			
		||||
    <div class="usercard-inner">
 | 
			
		||||
@@ -109,7 +110,6 @@
 | 
			
		||||
 | 
			
		||||
  <div class="post-content-container" {{ "id=latest-post" if is_latest else "" }}>
 | 
			
		||||
    <div class="post-info">
 | 
			
		||||
      {% set post_permalink = url_for("threads.thread", slug = post['thread_slug'], after = post['id'], _anchor = ("post-" + (post['id'] | string))) %}
 | 
			
		||||
      <a href="{{ post_permalink }}" title="Permalink"><i>
 | 
			
		||||
        {% if (post['edited_at'] | int) > (post['created_at'] | int) %}
 | 
			
		||||
          Edited on {{ timestamp(post['edited_at']) }}
 | 
			
		||||
@@ -157,7 +157,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="post-content">
 | 
			
		||||
      {% if not editing %}
 | 
			
		||||
        <div class="post-inner">{{ post['content'] | safe }}</div>
 | 
			
		||||
        <div class="post-inner" data-post-permalink="{{ post_permalink }}" data-author-username="{{ post.username }}">{{ post['content'] | safe }}</div>
 | 
			
		||||
        {% if render_sig and post['signature_rendered'] %}
 | 
			
		||||
          <div class="signature-container">
 | 
			
		||||
            <hr>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,125 @@
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function supportsPopover() {
 | 
			
		||||
    return Object.hasOwn(HTMLElement.prototype, "popover");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (supportsPopover()){
 | 
			
		||||
    let quotedPostContainer = null;
 | 
			
		||||
    function isQuoteSelectionValid() {
 | 
			
		||||
      const selection = document.getSelection();
 | 
			
		||||
 | 
			
		||||
      if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const range = selection.getRangeAt(0);
 | 
			
		||||
      const commonAncestor = range.commonAncestorContainer;
 | 
			
		||||
 | 
			
		||||
      const ancestorElement = commonAncestor.nodeType === Node.TEXT_NODE
 | 
			
		||||
      ? commonAncestor.parentNode
 | 
			
		||||
      : commonAncestor;
 | 
			
		||||
 | 
			
		||||
      const container = ancestorElement.closest(".post-inner");
 | 
			
		||||
      if (!container) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      const success = container.contains(ancestorElement);
 | 
			
		||||
      if (success) {
 | 
			
		||||
        quotedPostContainer = container;
 | 
			
		||||
      }
 | 
			
		||||
      return success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let quotePopover = null;
 | 
			
		||||
    let isSelecting = false;
 | 
			
		||||
 | 
			
		||||
    document.addEventListener("mousedown", () => {
 | 
			
		||||
      isSelecting = true;
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    document.addEventListener("mouseup", () => {
 | 
			
		||||
      isSelecting = false;
 | 
			
		||||
      handlePossibleSelection();
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    document.addEventListener("keyup", (e) => {
 | 
			
		||||
      if (e.shiftKey && (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End')) {
 | 
			
		||||
        handlePossibleSelection();
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    function handlePossibleSelection() {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        const valid = isQuoteSelectionValid();
 | 
			
		||||
        if (isSelecting || !valid) {
 | 
			
		||||
          removePopover();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const selection = document.getSelection();
 | 
			
		||||
        const selectionStr = selection.toString().trim();
 | 
			
		||||
        if (selection.isCollapsed || selectionStr === "") {
 | 
			
		||||
          removePopover();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        showPopover();
 | 
			
		||||
      }, 50)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function removePopover() {
 | 
			
		||||
      quotePopover?.hidePopover();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createPopover() {
 | 
			
		||||
      quotePopover = document.createElement("div");
 | 
			
		||||
      quotePopover.popover = "auto";
 | 
			
		||||
      quotePopover.className = "quote-popover";
 | 
			
		||||
 | 
			
		||||
      const quoteButton = document.createElement("button");
 | 
			
		||||
      quoteButton.textContent = "Quote fragment"
 | 
			
		||||
      quoteButton.className = "reduced"
 | 
			
		||||
      quotePopover.appendChild(quoteButton);
 | 
			
		||||
 | 
			
		||||
      document.body.appendChild(quotePopover);
 | 
			
		||||
      return quoteButton;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showPopover() {
 | 
			
		||||
      if (!quotePopover) {
 | 
			
		||||
        const quoteButton = createPopover();
 | 
			
		||||
        quoteButton.addEventListener("click", () => {
 | 
			
		||||
          console.log("Quoting:", document.getSelection().toString());
 | 
			
		||||
          const postPermalink = quotedPostContainer.dataset.postPermalink;
 | 
			
		||||
          const authorUsername = quotedPostContainer.dataset.authorUsername;
 | 
			
		||||
          console.log(postPermalink, authorUsername);
 | 
			
		||||
          if (ta.value.trim() !== "") {
 | 
			
		||||
            ta.value += "\n"
 | 
			
		||||
          }
 | 
			
		||||
          ta.value += `[url=${postPermalink}]${authorUsername} said:[/url]\n[quote]${document.getSelection().toString()}[/quote]\n`;
 | 
			
		||||
          ta.scrollIntoView()
 | 
			
		||||
          ta.focus();
 | 
			
		||||
 | 
			
		||||
          document.getSelection().empty();
 | 
			
		||||
          removePopover();
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const range = document.getSelection().getRangeAt(0);
 | 
			
		||||
      const rect = range.getBoundingClientRect();
 | 
			
		||||
      const scrollY = window.scrollY || window.pageYOffset;
 | 
			
		||||
 | 
			
		||||
      quotePopover.style.setProperty("top", `${rect.top + scrollY - 55}px`)
 | 
			
		||||
      quotePopover.style.setProperty("left", `${rect.left + rect.width/2}px`)
 | 
			
		||||
 | 
			
		||||
      if (!quotePopover.matches(':popover-open')) {
 | 
			
		||||
        quotePopover.showPopover();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const deleteDialog = document.getElementById("delete-dialog");
 | 
			
		||||
  const deleteDialogCloseButton = document.getElementById("post-delete-dialog-close");
 | 
			
		||||
  let deletionTargetPostContainer;
 | 
			
		||||
 
 | 
			
		||||
@@ -310,6 +310,10 @@ button:active, input[type=submit]:active, .linkbutton:active {
 | 
			
		||||
button:disabled, input[type=submit]:disabled, .linkbutton:disabled {
 | 
			
		||||
  background-color: rgb(209.535, 211.565, 211.46);
 | 
			
		||||
}
 | 
			
		||||
button.reduced, input[type=submit].reduced, .linkbutton.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
button.critical, input[type=submit].critical, .linkbutton.critical {
 | 
			
		||||
  color: white;
 | 
			
		||||
  background-color: red;
 | 
			
		||||
@@ -323,6 +327,10 @@ button.critical:active, input[type=submit].critical:active, .linkbutton.critical
 | 
			
		||||
button.critical:disabled, input[type=submit].critical:disabled, .linkbutton.critical:disabled {
 | 
			
		||||
  background-color: rgb(174.675, 156.825, 156.825);
 | 
			
		||||
}
 | 
			
		||||
button.critical.reduced, input[type=submit].critical.reduced, .linkbutton.critical.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
button.warn, input[type=submit].warn, .linkbutton.warn {
 | 
			
		||||
  background-color: #fbfb8d;
 | 
			
		||||
}
 | 
			
		||||
@@ -335,6 +343,10 @@ button.warn:active, input[type=submit].warn:active, .linkbutton.warn:active {
 | 
			
		||||
button.warn:disabled, input[type=submit].warn:disabled, .linkbutton.warn:disabled {
 | 
			
		||||
  background-color: rgb(217.55, 217.55, 209.85);
 | 
			
		||||
}
 | 
			
		||||
button.warn.reduced, input[type=submit].warn.reduced, .linkbutton.warn.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type=file]::file-selector-button {
 | 
			
		||||
  background-color: rgb(177, 206, 204.5);
 | 
			
		||||
@@ -349,6 +361,10 @@ input[type=file]::file-selector-button:active {
 | 
			
		||||
input[type=file]::file-selector-button:disabled {
 | 
			
		||||
  background-color: rgb(209.535, 211.565, 211.46);
 | 
			
		||||
}
 | 
			
		||||
input[type=file]::file-selector-button.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p {
 | 
			
		||||
  margin: 15px 0;
 | 
			
		||||
@@ -371,6 +387,10 @@ p {
 | 
			
		||||
.pagebutton:disabled {
 | 
			
		||||
  background-color: rgb(209.535, 211.565, 211.46);
 | 
			
		||||
}
 | 
			
		||||
.pagebutton.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.currentpage {
 | 
			
		||||
  border: none;
 | 
			
		||||
@@ -632,6 +652,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
 | 
			
		||||
.tab-button:disabled {
 | 
			
		||||
  background-color: rgb(209.535, 211.565, 211.46);
 | 
			
		||||
}
 | 
			
		||||
.tab-button.reduced {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
.tab-button.active {
 | 
			
		||||
  background-color: #beb1ce;
 | 
			
		||||
  padding-top: 8px;
 | 
			
		||||
@@ -737,3 +761,13 @@ ul, ol {
 | 
			
		||||
.babycode-button > * {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.quote-popover {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transform: translateX(-50%);
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  background-color: rgba(0, 0, 0, 0.5019607843);
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,11 @@ $accordion_color: color.adjust($accent_color, $hue: 140, $lightness: -10%, $satu
 | 
			
		||||
  &:disabled {
 | 
			
		||||
    background-color: color.scale($color, $lightness: 30%, $saturation: -90%);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.reduced {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin navbar($color) {
 | 
			
		||||
@@ -746,3 +751,13 @@ ul, ol {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.quote-popover {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transform: translateX(-50%);
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  background-color: #00000080;
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user