diff --git a/files/assets/css/main.css b/files/assets/css/main.css index b9dcbceb9..752600569 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -1721,6 +1721,7 @@ button.close { .modal-dialog-centered.modal-dialog-scrollable { flex-direction: column; justify-content: center; + height: 100%; } .modal-dialog-centered.modal-dialog-scrollable .modal-content { max-height: none; @@ -6086,4 +6087,60 @@ g { .post-actions { margin-top: -5px; } -} \ No newline at end of file +} + +.ghostdiv +{ + display: block; + white-space: pre-wrap; + word-break: break-word; + /* Attempt to copy the textarea/input padding */ + padding: 15px; +} + +#speed-carot-modal +{ + background-color: var(--gray-700); + min-width: 190px; + max-width: 190px; + max-height: 300px; + overflow-y: auto; + overflow-x: hidden; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.3); + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2); +} + +#speed-carot-modal .speed-modal-option +{ + border-bottom: 1px solid #606060; + padding: 4px; + cursor: pointer; +} + +#speed-carot-modal .speed-modal-option:hover, +#speed-carot-modal .speed-modal-option:focus, +#speed-carot-modal .speed-modal-option.selected +{ + background-color: rgba(255, 255, 255, 0.2); +} + + +#speed-carot-modal .speed-modal-image +{ + object-fit: contain; + width: 32px; + height: 32px; +} + +#speed-carot-modal .speed-modal-option span +{ + overflow: hidden; + display: inline-block; + vertical-align: middle; + margin-left: 10px; + width: 125px; + max-width: 125px; + text-overflow: ellipsis; +} + diff --git a/files/assets/js/emoji_modal.js b/files/assets/js/emoji_modal.js index 4f2e2749e..3e6ce5149 100644 --- a/files/assets/js/emoji_modal.js +++ b/files/assets/js/emoji_modal.js @@ -10,7 +10,8 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . -Copyright (C) 2022 Dr Steven Transmisia, anti-evil engineer +Copyright (C) 2022 Dr Steven Transmisia, anti-evil engineer, + 2022 Nekobit, king autist */ // Status @@ -372,6 +373,176 @@ function emojiAddToInput(event) localStorage.setItem("favorite_emojis", JSON.stringify(favorite_emojis)); } +(function() { + const insertAt = (str, sub, pos) => `${str.slice(0, pos)}${sub}${str.slice(pos)}`; + + let emoji_typing_state = false; + + function update_ghost_div_textarea(text) + { + let ghostdiv = text.parentNode.querySelector(".ghostdiv"); + if (!ghostdiv) return; + + ghostdiv.innerText = text.value.substring(0, text.selectionStart); + ghostdiv.innerHTML += ""; + + // Now lets get coordinates + + ghostdiv.style.display = "initial"; + let end = ghostdiv.querySelector("span"); + const carot_coords = end.getBoundingClientRect(); + const ghostdiv_coords = ghostdiv.getBoundingClientRect(); + ghostdiv.style.display = "none"; + return { pos: text.selectionStart, x: carot_coords.x, y: carot_coords.y - ghostdiv_coords.y }; + } + + // Used for anything where a user is typing, specifically for the emoji modal + // Just leave it global, I don't care + let speed_carot_modal = document.createElement("div"); + speed_carot_modal.id = "speed-carot-modal"; + speed_carot_modal.style.position = "absolute"; + speed_carot_modal.style.left = "0px"; + speed_carot_modal.style.top = "0px"; + speed_carot_modal.style.display = "none"; + document.body.appendChild(speed_carot_modal); + + let current_word = ""; + let emojo_index = 0; + + function curr_word_is_emoji() + { + return current_word && current_word.charAt(0) == ":" && + current_word.charAt(current_word.length-1) != ":"; + } + + function populate_speed_emoji_modal(results, textbox) + { + if (!results || results.size === 0) + { + speed_carot_modal.style.display = "none"; + return -1; + } + + emojo_index = 0; + speed_carot_modal.innerHTML = ""; + const MAXXX = 25; + // Not sure why the results is a Set... but oh well + let i = 0; + for (let result of results) + { + if (i++ > MAXXX) return i; + let emoji_option = document.createElement("div"); + emoji_option.className = "speed-modal-option emoji-option " + (i === 1 ? "selected" : ""); + emoji_option.tabIndex = 0; + let emoji_option_img = document.createElement("img"); + emoji_option_img.className = "speed-modal-image emoji-option-image"; + // This is a bit + emoji_option_img.src = `/e/${result}.webp`; + let emoji_option_text = document.createElement("span"); + emoji_option_text.title = result; + emoji_option_text.innerText = result; + + emoji_option.onclick = (e) => { + speed_carot_modal.style.display = "none"; + textbox.value = textbox.value.replace(new RegExp(current_word+"(?=\\s|$)", "g"), `:${result}:`) + }; + // Pack + emoji_option.appendChild(emoji_option_img); + emoji_option.appendChild(emoji_option_text); + speed_carot_modal.appendChild(emoji_option); + } + if (i === 0) speed_carot_modal.style.display = "none"; + else speed_carot_modal.style.display = "initial"; + return i; + } + + function update_speed_emoji_modal(event) + { + if (event.target.tagName.toLowerCase() !== 'textarea') return; + + const box_coords = update_ghost_div_textarea(event.target); + + let text = event.target.value; + + // Unused, but left incase anyone wants to use this more efficient method for emojos + switch (event.data) + { + case ':': + emoji_typing_state = true; + break; + case ' ': + emoji_typing_state = false; + break; + default: + break; + } + + // Get current word at string, such as ":marse" or "word" + let coords = text.indexOf(' ',box_coords.pos); + current_word = /\S+$/.exec(text.slice(0, coords === -1 ? text.length : coords)); + if (current_word) current_word = current_word.toString(); + + /* We could also check emoji_typing_state here, which is less accurate but more efficient. I've + * kept it unless someone wants to provide an option to toggle it for performance */ + if (curr_word_is_emoji() && current_word != ":") + { + loadEmojis(null); + let modal_pos = event.target.getBoundingClientRect(); + modal_pos.x += window.scrollX; + modal_pos.y += window.scrollY; + + speed_carot_modal.style.display = "initial"; + speed_carot_modal.style.left = modal_pos.x + box_coords.x - 35 + "px"; + speed_carot_modal.style.top = modal_pos.y + box_coords.y + 14 + "px"; + + // Do the search (and do something with it) + populate_speed_emoji_modal(emojisSearchDictionary.searchFor(current_word.substr(1)), event.target); + + } + else { + speed_carot_modal.style.display = "none"; + } + } + + // Update emoji position + document.addEventListener('input', update_speed_emoji_modal, false); + + // Update emoji position + document.addEventListener('keydown', (e) => { + let select_items = speed_carot_modal.querySelectorAll(".speed-modal-option"); + if (!select_items || !curr_word_is_emoji()) return false; + // Up or down arrow or enter + if (e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13) + { + if (emojo_index > select_items.length) + emojo_index = select_items; + + select_items[emojo_index].classList.remove("selected"); + switch (e.keyCode) + { + case 38: // Up arrow + if (emojo_index) + emojo_index--; + break; + + case 40: // Down arrow + if (emojo_index < select_items.length-1) emojo_index++; + break; + + case 13: + select_items[emojo_index].click(); + + default: + break; + } + + select_items[emojo_index].classList.add("selected"); + e.preventDefault(); + } + }, false); +})(); + + function loadEmojis(inputTargetIDName) { if(!emojiEngineStarted) @@ -380,9 +551,10 @@ function loadEmojis(inputTargetIDName) emojiRequest.send(); } - emojiInputTargetDOM = document.getElementById(inputTargetIDName); + if (inputTargetIDName) + emojiInputTargetDOM = document.getElementById(inputTargetIDName); } document.getElementById('emojiModal').addEventListener('shown.bs.modal', function () { emojiSearchBarDOM.focus(); -}); \ No newline at end of file +}); diff --git a/files/templates/comments.html b/files/templates/comments.html index 76f769341..4a29c14c7 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -122,7 +122,7 @@ Comment {{'Replies' if (replies | length)>1 else 'Reply'}}: {{c.post.realtitle(v) | safe}} {% elif c.post.author_id==v.id and c.level == 1 and is_notification_page%} Post Reply: {{c.post.realtitle(v) | safe}} - {% elif is_notification_page and c.parent_submission in v.subscribed_idlist %} + {% elif is_notification_page and c.parent_submission in v.subscribed_idlist() %} Subscribed Thread: {{c.post.realtitle(v) | safe}} {% elif is_notification_page %} Username Mention: {{c.post.realtitle(v) | safe}} @@ -450,9 +450,9 @@ - + - + {% endif %} {% if c.parent_submission %} @@ -551,7 +551,8 @@ - + +
@@ -655,9 +656,9 @@ Give Award - Save + Save - Unsave + Unsave {% if c.author_id == v.id %} Edit diff --git a/files/templates/submission.html b/files/templates/submission.html index 6efe700a6..152eda2bd 100644 --- a/files/templates/submission.html +++ b/files/templates/submission.html @@ -983,6 +983,7 @@ +
@@ -1225,4 +1226,4 @@ {% endif %} {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/files/templates/submit.html b/files/templates/submit.html index c4ee47dbf..b03fc07f3 100644 --- a/files/templates/submit.html +++ b/files/templates/submit.html @@ -125,6 +125,7 @@ +