diff --git a/static/js/act.js b/static/js/act.js
index 73471d6..582bf9d 100644
--- a/static/js/act.js
+++ b/static/js/act.js
@@ -198,4 +198,3 @@ const deleteComment = (postId, modalid) => {
});
});
}
-
diff --git a/static/js/comments.js b/static/js/comments.js
index be86b1f..b04a9c7 100644
--- a/static/js/comments.js
+++ b/static/js/comments.js
@@ -1,161 +1,965 @@
-var navLock = false;
-var lastQuoteLinkBlock = true;
+(function () {
+ var navLock = false;
+ var lastQuoteLinkBlock = true;
-$(document).ready(function()
-{
- // Изменение рейтинга комментария (с учётом форматирования)
- function setComVote(cell, rating)
- {
- if (rating > 0) cell.removeClass('con').addClass('pro').html('+' + rating); else
- if (rating < 0) cell.removeClass('pro').addClass('con').html('–' + parseInt(-rating));
- else cell.removeClass('pro con').html(0);
- }
+ $(document).ready(function () {
+ // Изменение рейтинга комментария (с учётом форматирования)
+ function setComVote(cell, rating) {
+ if (rating > 0)
+ cell
+ .removeClass("con")
+ .addClass("pro")
+ .html("+" + rating);
+ else if (rating < 0)
+ cell
+ .removeClass("pro")
+ .addClass("con")
+ .html("–" + parseInt(-rating));
+ else cell.removeClass("pro con").html(0);
+ }
+
+ // Голосование за комментарии
+ $(document)
+ .on("click", ".w-btn", function () {
+ var vote = $(this).attr("vote");
+ if (vote != 0 && vote != 1) return false;
+
+ var voted = $(this).is(".voted");
+ $(this).toggleClass("voted");
+
+ var diff = (vote == 1 && !voted) || (vote == 0 && voted) ? 1 : -1;
+
+ var otherButton = $(this).siblings(".w-btn");
+ var votedOther = otherButton.is(".voted");
+
+ if (votedOther) {
+ otherButton.removeClass("voted");
+ diff *= 2;
+ }
+
+ var cell = $(this).siblings(".w-rating");
+ var rating = parseInt(
+ cell.is(".con") ? -cell.html().substring(1) : cell.html()
+ );
+
+ setComVote(cell, rating + diff);
+
+ var cell_ext = $(this).siblings(".w-rating-ext");
+ cell_ext.addClass("active-locked");
+
+ var pro = $(".pro", cell_ext);
+ var con = $(".con", cell_ext);
+
+ if (vote == 1 || (vote == 0 && votedOther))
+ pro.html(
+ "+" +
+ (parseInt(pro.text().substr(1)) + (vote == 1 && !voted ? 1 : -1))
+ );
+ if (vote == 0 || (vote == 1 && votedOther))
+ con.html(
+ "–" +
+ (parseInt(con.text().substr(1)) + (vote == 0 && !voted ? 1 : -1))
+ );
+
+ var wvote = $(this).closest(".wvote");
+ setTimeout(function () {
+ $(".w-btn", wvote).removeClass("active");
+ }, 200);
+ setTimeout(function () {
+ cell_ext.removeClass("active active-locked");
+ }, 1000);
+
+ $.getJSON(
+ "/api/photo/comment/rate",
+ { action: "vote-comment", wid: wvote.attr("wid"), vote: vote },
+ function (data) {
+ if (data && !data[3]) {
+ $('.w-btn[vote="1"]', wvote)[
+ data[0][1] ? "addClass" : "removeClass"
+ ]("voted");
+ $('.w-btn[vote="0"]', wvote)[
+ data[0][0] ? "addClass" : "removeClass"
+ ]("voted");
+
+ pro.html("+" + data[1][1]);
+ con.html("" + data[1][0]);
+
+ setComVote(cell, data[2]);
+ } else if (data[3]) alert(data[3]);
+ }
+ ).fail(function (jx) {
+ if (jx.responseText != "") alert(jx.responseText);
+ });
+
+ return false;
+ })
+ // Отображение кнопок
+ .on("mouseenter mouseleave", '.w-btn[vote="1"]', function () {
+ $(this).toggleClass("s2 s12");
+ })
+ .on("mouseenter mouseleave", '.w-btn[vote="0"]', function () {
+ $(this).toggleClass("s5 s15");
+ })
+ .on("mouseenter touchstart", ".wvote", function () {
+ $(".w-btn, .w-rating-ext", this).addClass("active");
+ })
+ .on("mouseleave", ".wvote", function () {
+ $(".w-btn, .w-rating-ext", this).removeClass("active");
+ })
+ .on("touchstart", function (e) {
+ if (
+ !$(e.target).is(".wvote") &&
+ $(e.target).closest(".wvote").length == 0
+ )
+ $(".w-btn, .w-rating-ext").removeClass("active");
+ });
+
+ // Подсветка комментария, если дана ссылка на комментарий
+ var anchorTestReg = /#(\d+)$/;
+ var arr = anchorTestReg.exec(window.location.href);
+ if (arr != null) $('.comment[wid="' + arr[1] + '"]').addClass("s2");
+
+ // Ссылка на комментарий
+ $(".cmLink").on("click", function () {
+ var comment = $(this).closest(".comment");
+ comment.siblings().removeClass("s2");
+ comment.addClass("s2");
+ });
+
+ // Удаление комментария
+ $(".delLink").on("click", function () {
+ return confirm(_text["P_DEL_CONF"]);
+ });
+
+ // Цитирование
+ $(".quoteLink").on("click", function () {
+ var comment = $(this).closest(".comment"),
+ mText,
+ mTextArray;
+ var selection = window.getSelection();
+
+ var selectedText = selection.toString();
+ var quotedText =
+ selectedText == "" ? $(".message-text", comment).text() : selectedText;
+ var msg = "";
+
+ if (selectedText == "" && comment.next(".comment").length == 0)
+ msg = _text["P_QUOTE_MSG"];
+ else if (quotedText.length > 600) msg = _text["P_QUOTE_LEN"];
+
+ if (msg != "") {
+ if ($(".no-quote-last", comment).length == 0)
+ comment.append('
' + msg + "
");
+
+ if (lastQuoteLinkBlock) {
+ lastQuoteLinkBlock = false;
+ return false;
+ }
+ } else $(".no-quote-last").remove();
+
+ if (selectedText == "") mText = $(".message-text", comment).html();
+ else
+ mText = $("")
+ .append(selection.getRangeAt(0).cloneContents())
+ .html();
+
+ mText = mText
+ .replace(/<\/?i[^>]*>/gi, "")
+ .replace(/<\/?u>/gi, "")
+ .replace(/<\/?span[^>]*>/gi, "")
+ .replace(/<\/?div[^>]*>/gi, "");
+ mText = mText
+ .replace(new RegExp('
[^>]*', "ig"), "");
+ mText = mText
+ .replace(/
]+)>)/gi, "")
+ .replace(/\[br\]/gi, "
")
+ .replace(/</gi, "<")
+ .replace(/"e;/gi, '"')
+ .replace(/&/gi, "&");
+ mTextArray = mText.split(/
\s*/i);
+
+ var mText2 = "";
+ for (var i = 0; i < mTextArray.length; ++i)
+ mText2 += "> " + mTextArray[i] + "\n";
+
+ var txtField = $("#wtext");
+ if (txtField.length) {
+ var messageText = txtField.val();
+ var insertText =
+ (messageText == "" ? "" : "\n") +
+ _text["P_QUOTE_TXT"] +
+ " (" +
+ $(".message_author", comment).text() +
+ ", " +
+ $(".message_date", comment).text() +
+ "):\n" +
+ mText2 +
+ "\n";
+
+ txtField.val(messageText + insertText);
+ txtField[0].focus();
+ }
+
+ return false;
+ });
+
+ // Отправка комментария
+
+ // Окно ввода комментария
+ $("#wtext")
+ .on("keypress", function (e) {
+ if ((e.which == 10 || e.which == 13) && e.ctrlKey) $("#f1").submit();
+ })
+ .on("focus", function () {
+ navLock = true;
+ saveSelection();
+ })
+ .on("blur", function () {
+ navLock = false;
+ })
+ .on("blur", function () {
+ navLock = false;
+ });
+ });
+
+ let lastSelection = null;
+ let autocompleteType = null; // 'emoji' или 'mention'
+ let currentMentions = [];
+
+ // Сохраняем позицию курсора
+ function saveSelection() {
+ const sel = window.getSelection();
+ if (sel.rangeCount > 0) {
+ lastSelection = sel.getRangeAt(0);
+ }
+ }
+
+ // Восстанавливаем позицию курсора
+ function restoreSelection() {
+ if (lastSelection) {
+ const sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(lastSelection);
+ }
+ }
+
+ // Вставка смайла
+ function insertEmoji(emojiElement) {
+ const editor = document.getElementById("wtext");
+ if (!editor) return;
+
+ // Принудительно фокусируем редактор
+ editor.focus();
+
+ // Создаем новый диапазон, если выделение потеряно
+ let sel = window.getSelection();
+ if (!sel.rangeCount || !editor.contains(sel.anchorNode)) {
+ const range = document.createRange();
+ range.selectNodeContents(editor);
+ range.collapse(false); // Курсор в конец
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+
+ // Сохраняем текущее выделение
+ saveSelection();
+
+ // Создаем элемент эмодзи
+ const img = document.createElement("img");
+ img.className = "editor-emoji";
+ img.src = emojiElement.src;
+ img.dataset.code = `[${emojiElement.dataset.code}]`;
+ img.contentEditable = "false";
+
+ // Вставляем в сохраненную позицию
+ if (lastSelection) {
+ lastSelection.insertNode(img);
+
+ // Обновляем позицию курсора
+ const newRange = document.createRange();
+ newRange.setStartAfter(img);
+ newRange.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(newRange);
+ } else {
+ editor.appendChild(img);
+ }
+
+ // Форсируем обновление состояния
+ editor.dispatchEvent(new Event('input', { bubbles: true }));
+ }
- // Голосование за комментарии
- $(document).on('click', '.w-btn', function()
- {
- var vote = $(this).attr('vote');
- if (vote != 0 && vote != 1) return false;
+ // Обработчики событий
+ const showPickerElement = document.getElementById("showPicker");
- var voted = $(this).is('.voted');
- $(this).toggleClass('voted');
+ if (showPickerElement) {
+ showPickerElement.addEventListener("click", function (e) {
+ e.stopPropagation();
+ const picker = document.getElementById("picker");
+ const rect = this.getBoundingClientRect();
- var diff = (vote == 1 && !voted || vote == 0 && voted) ? 1 : -1;
+ picker.style.top = `${rect.bottom + window.scrollY - 450}px`;
+ picker.style.left = `${rect.left + window.scrollX}px`;
+ picker.classList.toggle("active");
+ });
+ }
- var otherButton = $(this).siblings('.w-btn');
- var votedOther = otherButton.is('.voted');
+ document.querySelectorAll(".emoji-option").forEach((emoji) => {
+ emoji.addEventListener("mousedown", function(e) { // Используем mousedown вместо click
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Фокусируем редактор перед вставкой
+ const editor = document.getElementById("wtext");
+ if (editor) editor.focus();
+
+ insertEmoji(emoji);
+ document.getElementById("picker").classList.remove("active");
+ });
+ });
- if (votedOther)
- {
- otherButton.removeClass('voted');
- diff *= 2;
- }
+ document.addEventListener('selectionchange', () => {
+ const editor = document.getElementById("wtext");
+ const sel = window.getSelection();
+
+ if (editor && sel.rangeCount > 0) {
+ const range = sel.getRangeAt(0);
+ if (editor.contains(range.commonAncestorContainer)) {
+ saveSelection();
+ }
+ }
+ });
- var cell = $(this).siblings('.w-rating');
- var rating = parseInt(cell.is('.con') ? -cell.html().substring(1) : cell.html());
+const picker = document.getElementById("picker");
+const showPicker = document.getElementById("showPicker");
- setComVote(cell, rating + diff);
+if (picker) {
+ document.addEventListener("click", function(e) {
+ const isClickInsidePicker = picker.contains(e.target);
+ const isClickOnShowButton = showPicker?.contains(e.target);
+
+ if (!isClickInsidePicker && !isClickOnShowButton) {
+ picker.classList.remove("active");
+ }
+ });
+}
+ // Обновленный JavaScript для работы с новым форматом
+ document.addEventListener("click", function (e) {
+ if (e.target.classList.contains("toggle-message")) {
+ e.preventDefault();
+ const container = e.target.closest(".message-text");
+ const isExpanded = container.classList.contains("show-all");
- var cell_ext = $(this).siblings('.w-rating-ext');
- cell_ext.addClass('active-locked');
+ container.classList.toggle("show-all", !isExpanded);
+ e.target.textContent = isExpanded ? "показать больше" : "скрыть";
+ }
+ });
- var pro = $('.pro', cell_ext);
- var con = $('.con', cell_ext);
+ // Обработка отправки формы
+ const formElement = document.querySelector("form");
+ const editorElement = document.getElementById("wtext");
+ const hiddenContentElement = document.getElementById("hiddenContent");
- if (vote == 1 || vote == 0 && votedOther) pro.html('+' + (parseInt(pro.text().substr(1)) + (vote == 1 && !voted ? 1 : -1)));
- if (vote == 0 || vote == 1 && votedOther) con.html('–' + (parseInt(con.text().substr(1)) + (vote == 0 && !voted ? 1 : -1)));
+ if (formElement && editorElement && hiddenContentElement) {
+ formElement.addEventListener("submit", function (e) {
+ const tempDiv = document.createElement("div");
+ tempDiv.innerHTML = editorElement.innerHTML;
+ // Обработка смайлов
+ tempDiv.querySelectorAll("img.editor-emoji").forEach((img) => {
+ const textNode = document.createTextNode(img.dataset.code);
+ img.replaceWith(textNode);
+ });
- var wvote = $(this).closest('.wvote');
- setTimeout(function() { $('.w-btn', wvote).removeClass('active'); }, 200);
- setTimeout(function() { cell_ext.removeClass('active active-locked'); }, 1000);
+ // Обработка упоминаний
+ tempDiv.querySelectorAll(".user-mention").forEach((mention) => {
+ const textNode = document.createTextNode(
+ `@[${mention.dataset.userId}:${mention.innerText.replace("@", "")}]`
+ );
+ mention.replaceWith(textNode);
+ });
- $.getJSON('/api/photo/comment/rate', { action: 'vote-comment', wid: wvote.attr('wid'), vote: vote }, function (data)
- {
- if (data && !data[3])
- {
- $('.w-btn[vote="1"]', wvote)[data[0][1] ? 'addClass' : 'removeClass']('voted');
- $('.w-btn[vote="0"]', wvote)[data[0][0] ? 'addClass' : 'removeClass']('voted');
+ hiddenContentElement.value = tempDiv.innerHTML;
+ });
+ }
+ let allSmileys = [];
+ function removeFirstLast(str) {
+ return str.slice(1, -1);
+ }
- pro.html('+' + data[1][1]);
- con.html('' + data[1][0]);
+ async function loadSmileys() {
+ try {
+ const response = await fetch("/api/emoji/load");
+ if (!response.ok)
+ throw new Error(`HTTP error! status: ${response.status}`);
- setComVote(cell, data[2]);
- }
- else if (data[3]) alert(data[3]);
- })
- .fail(function(jx) { if (jx.responseText != '') alert(jx.responseText); });
+ const data = await response.json();
- return false;
- })
- // Отображение кнопок
- .on('mouseenter mouseleave', '.w-btn[vote="1"]', function() { $(this).toggleClass('s2 s12'); })
- .on('mouseenter mouseleave', '.w-btn[vote="0"]', function() { $(this).toggleClass('s5 s15'); })
- .on('mouseenter touchstart', '.wvote', function() { $('.w-btn, .w-rating-ext', this).addClass('active'); })
- .on('mouseleave', '.wvote', function() { $('.w-btn, .w-rating-ext', this).removeClass('active'); })
- .on('touchstart', function(e) { if (!$(e.target).is('.wvote') && $(e.target).closest('.wvote').length == 0) $('.w-btn, .w-rating-ext').removeClass('active'); });
+ allSmileys = (data.data || []).map((smiley) => {
+ let code = smiley.code
+ .replace(/\\(.)/g, "$1")
+ .replace(/^\[/, "") // Удалить первую [ если есть
+ .replace(/\]$/, ""); // Удалить последнюю ] если есть
+ code = `${code}`; // Обернуть снова в одну пару []
- // Подсветка комментария, если дана ссылка на комментарий
- var anchorTestReg = /#(\d+)$/;
- var arr = anchorTestReg.exec(window.location.href);
- if (arr != null) $('.comment[wid="' + arr[1] + '"]').addClass('s2');
+ return {
+ ...smiley,
+ code: code,
+ };
+ });
+ console.log(
+ "Processed codes:",
+ allSmileys.map((s) => s.code)
+ );
+ return allSmileys;
+ } catch (error) {
+ console.error("Error loading smileys:", error);
+ return [];
+ }
+ }
- // Ссылка на комментарий
- $('.cmLink').on('click', function()
- {
- var comment = $(this).closest('.comment');
- comment.siblings().removeClass('s2');
- comment.addClass('s2');
- });
+ async function initEditor() {
+ const editor = document.getElementById("wtext");
+ if (!editor) return;
+ await loadSmileys();
- // Удаление комментария
- $('.delLink').on('click', function() { return confirm(_text['P_DEL_CONF']); });
+ let html = editor.innerHTML;
+ // Заменяем шорткоды с точным совпадением
+ allSmileys.forEach(({ code, url }) => {
+ // Удаляем одну [ слева и одну ] справа, если они есть
+ const trimmed = code
+ .replace(/^\[/, "") // удаляет ПЕРВУЮ [
+ .replace(/\]$/, ""); // удаляет ПОСЛЕДНЮЮ ]
- // Цитирование
- $('.quoteLink').on('click', function()
- {
- var comment = $(this).closest('.comment'), mText, mTextArray;
- var selection = window.getSelection();
+ const cleanedCode = `${trimmed}`; // Добавляем одну пару скобок
- var selectedText = selection.toString();
- var quotedText = (selectedText == '') ? $('.message-text', comment).text() : selectedText;
- var msg = '';
+ const escapedCode = cleanedCode.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const regex = new RegExp(escapedCode, "g");
+ console.log(cleanedCode);
- if (selectedText == '' && comment.next('.comment').length == 0) msg = _text['P_QUOTE_MSG']; else
- if (quotedText.length > 600) msg = _text['P_QUOTE_LEN'];
+ html = html.replace(
+ regex,
+ `

`
+ );
+ });
- if (msg != '')
- {
- if ($('.no-quote-last', comment).length == 0) comment.append('
' + msg + '
');
+ editor.innerHTML = html;
+ }
- if (lastQuoteLinkBlock)
- {
- lastQuoteLinkBlock = false;
- return false;
- }
- }
- else $('.no-quote-last').remove();
+ // Обработчик клавиш
+ const wtextElement = document.getElementById("wtext");
- if (selectedText == '')
- mText = $('.message-text', comment).html();
- else mText = $('
').append(selection.getRangeAt(0).cloneContents()).html();
+ if (wtextElement) {
+ // Обработчик Backspace для удаления смайлов
+ wtextElement.addEventListener("keydown", function (e) {
+ if (e.key === "Backspace") {
+ const selection = window.getSelection();
+ if (selection && selection.isCollapsed) {
+ const range = selection.getRangeAt(0);
+ const node = range.startContainer;
- mText = mText.replace(/<\/?i[^>]*>/ig, '').replace(/<\/?u>/ig, '').replace(/<\/?span[^>]*>/ig, '').replace(/<\/?div[^>]*>/ig, '');
- mText = mText.replace(new RegExp('\
[^>]*\<\/a\>', 'ig'), '');
- mText = mText.replace(/
]+)>)/ig, '').replace(/\[br\]/ig, '
').replace(/</ig, '<').replace(/"e;/ig, '"').replace(/&/ig, '&');
- mTextArray = mText.split(/
\s*/i);
+ if (node.previousSibling?.classList?.contains("editor-emoji")) {
+ node.previousSibling.remove();
+ e.preventDefault();
+ }
+ }
+ }
+ });
- var mText2 = '';
- for (var i = 0; i < mTextArray.length; ++i)
- mText2 += '> ' + mTextArray[i] + '\n';
+ // Обработчик вставки текста
+ wtextElement.addEventListener("paste", function (e) {
+ e.preventDefault();
+ const text = (e.clipboardData || window.clipboardData).getData(
+ "text/plain"
+ );
+ document.execCommand("insertText", false, text);
+ });
+ }
- var txtField = $('#wtext');
- if (txtField.length)
- {
- var messageText = txtField.val();
- var insertText = (messageText == '' ? '' : '\n') + _text['P_QUOTE_TXT'] + ' (' + $('.message_author', comment).text() + ', ' + $('.message_date', comment).text() + '):\n' + mText2 + '\n';
+ // Автодополнение
+ // Автодополнение
+ function setupAutocomplete() {
+ const editor = document.getElementById("wtext");
+ let currentWord = "";
+ let startPos = 0;
+ let selectedIndex = -1;
+ let isAutocompleteOpen = false;
- txtField.val(messageText + insertText);
- txtField[0].focus();
- }
+ const acContainer = document.createElement("div");
+ acContainer.className = "autocomplete";
+ document.body.appendChild(acContainer);
- return false;
- });
+ // Обработчик клавиш
+ function handleKeyDown(e) {
+ if (!isAutocompleteOpen) return;
+ const items = acContainer.querySelectorAll(".autocomplete-item");
+ if (items.length === 0) return;
- // Отправка комментария
+ switch (e.key) {
+ case "ArrowDown":
+ e.preventDefault();
+ selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
+ updateSelection();
+ break;
+ case "ArrowUp":
+ e.preventDefault();
+ selectedIndex = Math.max(selectedIndex - 1, -1);
+ updateSelection();
+ break;
+ case "Enter":
+ e.preventDefault();
+ if (selectedIndex >= 0) {
+ insertSuggestion(items[selectedIndex]);
+ }
+ break;
+ case "Escape":
+ e.preventDefault();
+ closeAutocomplete();
+ break;
+ }
+ }
+ function updateSelection() {
+ const items = acContainer.querySelectorAll(".autocomplete-item");
+ items.forEach((item, index) => {
+ item.classList.toggle("selected", index === selectedIndex);
+ });
+ }
- // Окно ввода комментария
- $('#wtext').on('keypress', function(e) { if ((e.which == 10 || e.which == 13) && e.ctrlKey) $('#f1').submit(); })
- .on('focus', function() { navLock = true; })
- .on('blur', function() { navLock = false; });
+ function insertSuggestion(item) {
+ const code = item.dataset.code;
+ const sel = window.getSelection();
+ const range = sel.getRangeAt(0);
-});
\ No newline at end of file
+ range.setStart(range.startContainer, startPos);
+ range.deleteContents();
+
+ const textNode = document.createTextNode(`[${code}]`);
+ range.insertNode(textNode);
+
+ range.setStartAfter(textNode);
+ range.collapse(true);
+
+ closeAutocomplete();
+ editor.focus();
+ }
+
+ function closeAutocomplete() {
+ acContainer.style.display = "none";
+ acContainer.style.top = "-9999px";
+ acContainer.style.left = "-9999px";
+ acContainer.innerHTML = "";
+
+ // Сброс состояний
+ selectedIndex = -1;
+ isAutocompleteOpen = false;
+ autocompleteType = null;
+ }
+
+ // Предполагаем, что editor получен через getElementById
+
+ if (editor) {
+ let isAutocompleteOpen = false; // Добавляем инициализацию переменных
+ let autocompleteType = "";
+ let startPos = 0;
+ let currentWord = "";
+
+ // Объявляем функции для безопасного использования
+ const handleKeyDown = (e) => {
+ /* ... */
+ };
+ const showAutocomplete = (word, range) => {
+ /* ... */
+ };
+ const closeAutocomplete = () => {
+ /* ... */
+ };
+
+ // Обработчик нажатия клавиш
+ editor.addEventListener("keydown", function (e) {
+ const isAtSymbol =
+ e.key === "@" ||
+ (e.key === "2" && e.shiftKey) ||
+ (e.key === "Quote" && e.shiftKey);
+
+ if (e.key === ":" && !isAutocompleteOpen) {
+ autocompleteType = "emoji";
+ isAutocompleteOpen = true;
+ editor.addEventListener("keydown", handleKeyDown);
+ } else if (isAtSymbol && !isAutocompleteOpen) {
+ autocompleteType = "mention";
+ isAutocompleteOpen = true;
+ editor.addEventListener("keydown", handleKeyDown);
+ }
+ });
+
+ // Обработчик ввода
+ editor.addEventListener("input", function (e) {
+ if (!isAutocompleteOpen) return;
+
+ const sel = window.getSelection();
+ if (!sel || sel.rangeCount === 0) return;
+
+ const range = sel.getRangeAt(0);
+ const text = range.startContainer?.textContent || "";
+ const pos = range.startOffset;
+
+ let i = pos - 1;
+ const stopChar = autocompleteType === "emoji" ? ":" : "@";
+
+ while (i >= 0 && text[i] !== stopChar && text[i] !== " ") i--;
+
+ if (text[i] === stopChar) {
+ startPos = i;
+ currentWord = text.slice(i + 1, pos).toLowerCase();
+ showAutocomplete(currentWord, range);
+ } else {
+ closeAutocomplete();
+ }
+ });
+ }
+
+ // Показать подсказки
+ function showAutocomplete(query, range) {
+ if (autocompleteType === "emoji") {
+ const suggestions = allSmileys
+ .filter((smiley) =>
+ smiley.code.toLowerCase().includes(query.toLowerCase())
+ )
+ .slice(0, 10);
+
+ if (suggestions.length === 0) {
+ closeAutocomplete();
+ return;
+ }
+
+ const rect = range.getBoundingClientRect();
+ acContainer.style.top = `${rect.bottom + window.scrollY}px`;
+ acContainer.style.left = `${rect.left + window.scrollX}px`;
+
+ acContainer.innerHTML = suggestions
+ .map(
+ (smiley, index) => `
+
+

+
${smiley.code}
+
+ `
+ )
+ .join("");
+
+ acContainer.style.display = "block";
+ selectedIndex = 0;
+ updateSelection();
+ } else if (autocompleteType === "mention") {
+ fetch(`/api/users/search?q=${encodeURIComponent(query)}`)
+ .then((response) => response.json())
+ .then((users) => {
+ currentMentions = users;
+ const rect = range.getBoundingClientRect();
+ acContainer.style.top = `${rect.bottom + window.scrollY}px`;
+ acContainer.style.left = `${rect.left + window.scrollX}px`;
+
+ acContainer.innerHTML = users
+ .map(
+ (user, index) => `
+
+

+
${user.username}
+
+ `
+ )
+ .join("");
+
+ acContainer.style.display = "block";
+ selectedIndex = 0;
+ updateSelection();
+ });
+ }
+ }
+
+ // Обновленный обработчик клика
+ acContainer.addEventListener("mousedown", function (e) {
+ // Используем mousedown вместо click
+ const item = e.target.closest(".autocomplete-item");
+ if (item) {
+ insertSuggestion(item);
+ e.preventDefault();
+ }
+ });
+
+ // Скрыть при клике вне
+ document.addEventListener("click", function (e) {
+ if (!e.target.closest(".autocomplete")) {
+ acContainer.style.display = "none";
+ }
+ });
+
+ function insertSuggestion(item) {
+ if (autocompleteType === "emoji") {
+ const code = item.dataset.code;
+ const sel = window.getSelection();
+ const range = sel.getRangeAt(0);
+
+ range.setStart(range.startContainer, startPos);
+ range.deleteContents();
+
+ // Создаем элемент изображения вместо текстового узла
+ const img = document.createElement("img");
+ img.className = "editor-emoji";
+ img.src = item.querySelector("img").src;
+ img.dataset.code = `[${code}]`;
+ img.contentEditable = "false";
+
+ range.insertNode(img);
+
+ // Сдвигаем курсор после изображения
+ const newRange = document.createRange();
+ newRange.setStartAfter(img);
+ newRange.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(newRange);
+
+ closeAutocomplete();
+ editor.focus();
+ } else if (autocompleteType === "mention") {
+ const userId = item.dataset.userId;
+ const username = item.dataset.username;
+ const sel = window.getSelection();
+
+ if (sel.rangeCount === 0) return;
+
+ const range = sel.getRangeAt(0).cloneRange();
+ range.setStart(range.startContainer, startPos);
+ range.deleteContents();
+
+ // Создаем основной элемент упоминания
+ const mentionSpan = document.createElement("span");
+ mentionSpan.className = "user-mention";
+ mentionSpan.dataset.userId = userId;
+ mentionSpan.textContent = `@${username}`;
+ mentionSpan.contentEditable = "false";
+
+ // Создаем пробел после упоминания
+ const spaceSpan = document.createTextNode("\u00A0");
+
+ // Вставляем элементы
+ const fragment = document.createDocumentFragment();
+ fragment.appendChild(mentionSpan);
+ fragment.appendChild(spaceSpan);
+ range.insertNode(fragment);
+
+ // Обновляем позицию курсора
+ const newRange = document.createRange();
+ newRange.setStartAfter(spaceSpan);
+ newRange.collapse(true);
+
+ sel.removeAllRanges();
+ sel.addRange(newRange);
+
+ // Обновляем редактор
+ closeAutocomplete();
+ editor.focus();
+ }
+ closeAutocomplete();
+ editor.focus();
+ window.addEventListener("scroll", () => closeAutocomplete(), true);
+ }
+
+ function closeAutocomplete(immediate = false) {
+ if (immediate) {
+ // Немедленное удаление из DOM
+ acContainer.style.display = "none";
+ acContainer.innerHTML = "";
+ acContainer.style.top = "-9999px"; // Убираем за пределы видимости
+ selectedIndex = -1;
+ isAutocompleteOpen = false;
+ autocompleteType = null;
+ return;
+ }
+
+ // Анимация исчезновения (опционально)
+ acContainer.classList.add("closing");
+ setTimeout(() => {
+ acContainer.style.display = "none";
+ acContainer.innerHTML = "";
+ acContainer.classList.remove("closing");
+ selectedIndex = -1;
+ isAutocompleteOpen = false;
+ autocompleteType = null;
+ }, 200);
+ }
+
+ document.addEventListener("mouseover", async function (e) {
+ const mention = e.target.closest(".user-mention");
+ if (mention && !mention.dataset.loaded) {
+ const userId = mention.dataset.userId;
+
+ const response = await fetch(`/api/users/load/${userId}`);
+ const userInfo = await response.json();
+ }
+ });
+
+ function updateSelection() {
+ const items = acContainer.querySelectorAll(".autocomplete-item");
+ items.forEach((item, index) => {
+ item.classList.toggle("selected", index === selectedIndex);
+ });
+
+ // Прокрутка к выбранному элементу
+ if (selectedIndex >= 0 && items[selectedIndex]) {
+ items[selectedIndex].scrollIntoView({
+ behavior: "auto",
+ block: "nearest",
+ });
+ }
+ }
+ }
+
+ // Инициализация после загрузки
+ window.addEventListener("DOMContentLoaded", setupAutocomplete);
+
+ function setupImageValidation() {
+ const editor = document.getElementById("wtext");
+ if (!editor) return;
+
+ // Блокировка вставки через CTRL+V
+ editor.addEventListener("paste", function (e) {
+ const clipboardData = e.clipboardData || window.clipboardData;
+ if (!clipboardData?.items) return;
+
+ for (let i = 0; i < clipboardData.items.length; i++) {
+ if (clipboardData.items[i].type.indexOf("image") !== -1) {
+ e.preventDefault();
+ alert("Разрешена вставка только смайликов через пикер!");
+ return;
+ }
+ }
+ });
+
+ // Блокировка drag-and-drop изображений
+ editor.addEventListener("dragover", (e) => e.preventDefault());
+ editor.addEventListener("drop", function (e) {
+ e.preventDefault();
+ if (e.dataTransfer?.files?.length > 0) {
+ alert("Загрузка собственных изображений запрещена!");
+ }
+ });
+
+ // Запрет ручного добавления изображений
+ editor.addEventListener("DOMNodeInserted", function (e) {
+ if (
+ e.target?.tagName === "IMG" &&
+ !e.target?.classList?.contains("editor-emoji")
+ ) {
+ e.target.remove();
+ alert("Разрешены только смайлики из пикера!");
+ }
+ });
+ }
+
+ // Объединенный обработчик загрузки
+ document.addEventListener("DOMContentLoaded", async () => {
+ // Инициализация редактора
+ const editor = document.getElementById("wtext");
+
+ if (editor) {
+ setupImageValidation();
+ await loadSmileys().catch(console.error);
+ initEditor();
+ setupAutocomplete();
+ } else {
+ console.warn("Editor element (#wtext) not found");
+ }
+ });
+
+ const form = document.getElementById("f1");
+ if (!form) {
+ console.error("Форма #f1 не найдена!");
+ return;
+ }
+
+ const fileInput = document.createElement("input");
+ fileInput.type = "file";
+ fileInput.name = "filebody"; // Устанавливаем имя filebody
+ fileInput.style.display = "none";
+
+ form.appendChild(fileInput); // Добавляем input внутрь формы
+
+ const button = document.getElementById("attachFile");
+ const fileList = document.getElementById("fileList");
+
+ button.addEventListener("click", function () {
+ fileInput.click();
+ });
+
+ fileInput.addEventListener("change", function () {
+ const file = fileInput.files[0];
+ if (!file) return;
+
+ const maxSize = 100 * 1024 * 1024; // 100 MB
+ const forbiddenExtensions = [
+ ".html",
+ ".php",
+ ".htm",
+ ".exe",
+ ".com",
+ ".cmd",
+ ".bash",
+ ".sh",
+ ];
+
+ const fileName = file.name.toLowerCase();
+ const fileSize = file.size;
+
+ if (fileSize > maxSize) {
+ alert("Файл превышает 100 МБ.");
+ return;
+ }
+
+ if (forbiddenExtensions.some((ext) => fileName.endsWith(ext))) {
+ alert("Расширение не поддерживается.");
+ return;
+ }
+
+ const fileItem = document.createElement("div");
+ fileItem.setAttribute(
+ "style",
+ "border:solid 1px #bbb; width:max-content; font-size: 12px; padding:3px 10px 3px; margin-bottom:13px; background-color:#e2e2e2"
+ );
+ fileItem.textContent = file.name;
+
+ const removeBtn = document.createElement("a");
+ removeBtn.classList.add("compl");
+ removeBtn.setAttribute(
+ "style",
+ "display: inline-block; margin-left: 5px; color:#292929; cursor: pointer;"
+ );
+ removeBtn.textContent = "✖";
+ removeBtn.addEventListener("click", function () {
+ fileItem.remove();
+ fileInput.value = "";
+ });
+
+ fileItem.appendChild(removeBtn);
+ fileList.appendChild(fileItem);
+ });
+})();
diff --git a/static/js/index.js b/static/js/index.js
index dba393c..2ae4ec3 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -1,7 +1,6 @@
ar1 = new Image();
ar1.src = '/img/ar1.gif';
-
$(document).ready(function()
{
$('.ix-country > a[href="#"]').on('click', function(e)
@@ -119,11 +118,59 @@ function searchVehicles()
-function AddPhotoToBlock(block, arr, prepend)
-{
- block[prepend ? 'prepend' : 'append']('');
+function AddPhotoToBlock(block, arr, prepend) {
+ const html = `
+ `;
+
+ block[prepend ? 'prepend' : 'append'](html);
+
+ // Инициируем загрузку для нового элемента
+ lazyLoadSingleImage(block.find('.blur-load').last()[0]);
}
+// Отдельная функция для загрузки одного изображения
+function lazyLoadSingleImage(element) {
+ const realSrc = element.dataset.src;
+ const tempImg = new Image();
+
+ tempImg.src = realSrc;
+ tempImg.onload = () => {
+ element.style.backgroundImage = `url('${realSrc}')`;
+ element.classList.add('loaded');
+ };
+}
+
+// Обновленный обработчик для всей страницы
+function lazyLoadImages(selector = '.blur-load') {
+ document.querySelectorAll(selector).forEach(element => {
+ if(!element.dataset.loaded) { // Проверка чтобы не дублировать загрузку
+ element.dataset.loaded = true;
+ lazyLoadSingleImage(element);
+ }
+ });
+}
+
+// Инициализация при загрузке и после динамического добавления
+window.addEventListener('load', () => {
+ lazyLoadImages();
+ // Для динамически добавленных элементов можно вызывать lazyLoadImages() после добавления
+});
+
function LoadRandomPhotos()
diff --git a/static/js/photo.js b/static/js/photo.js
index fd93de2..5afa4a0 100644
--- a/static/js/photo.js
+++ b/static/js/photo.js
@@ -210,7 +210,7 @@ $(document).ready(function()
{
const url = window.location.pathname;
const segments = url.split('/');
- const id = segments[segments.length - 1];
+ const id = segments[2];
var faved = parseInt($(this).attr('faved'));
$(this).html(faved ? 'Добавить фото в Избранное' : 'Удалить фото из Избранного').attr('faved', faved ? 0 : 1);
if (!faved) $('.toggle').attr('class', 'toggle on');
diff --git a/static/js/routing.js b/static/js/routing.js
new file mode 100644
index 0000000..be18c60
--- /dev/null
+++ b/static/js/routing.js
@@ -0,0 +1,258 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const DEBUG = true;
+ const cache = new Map();
+ const CACHE_TTL = 300000; // 5 минут
+ let loadingTimeout;
+
+ const loadedScriptSrcs = new Set();
+
+ function reloadExternalScripts(doc) {
+ const scripts = Array.from(doc.querySelectorAll("script[src]"));
+ const loadedUrls = new Set(Array.from(document.scripts).map((s) => s.src));
+
+ // Загрузка скриптов последовательно
+ const loadScript = (index) => {
+ if (index >= scripts.length) return;
+
+ const script = scripts[index];
+ const src = script.src;
+
+ if (!loadedUrls.has(src)) {
+ const newScript = document.createElement("script");
+ newScript.src = src;
+ newScript.async = false; // Важно для порядка
+
+ // Копируем все атрибуты
+ Array.from(script.attributes).forEach((attr) => {
+ newScript.setAttribute(attr.name, attr.value);
+ });
+
+ newScript.onload = () => {
+ loadedUrls.add(src);
+ loadScript(index + 1); // Следующий скрипт
+ };
+
+ newScript.onerror = () => {
+ console.error(`Failed to load: ${src}`);
+ loadScript(index + 1); // Продолжаем цепочку
+ };
+
+ document.body.appendChild(newScript);
+ } else {
+ loadScript(index + 1); // Пропускаем уже загруженный
+ }
+ };
+
+ loadScript(0);
+ }
+
+ document.querySelectorAll("script[src]").forEach((script) => {
+ const src = script.getAttribute("src");
+ if (src) loadedScriptSrcs.add(src);
+ });
+
+ // Текущий путь страницы
+ let lastPath = location.pathname;
+
+ // Элементы интерфейса
+ const loader = createLoader();
+ const contentContainers = ["td.main", "#pmain"];
+
+ initNavigation();
+
+ function initNavigation() {
+ document.body.addEventListener("click", handleClick);
+ window.addEventListener("popstate", handlePopState);
+ }
+
+ function handleClick(e) {
+ const link = e.target.closest("a");
+ if (link && shouldIntercept(link)) {
+ e.preventDefault();
+ navigateTo(link.href);
+ }
+ }
+
+ function handlePopState() {
+ navigateTo(window.location.href, true);
+ }
+
+ function shouldIntercept(link) {
+ try {
+ const url = new URL(link.href);
+ return (
+ url.origin === location.origin && !link.dataset.noAjax && !link.hash
+ );
+ } catch {
+ return false;
+ }
+ }
+
+ async function navigateTo(url, isHistoryNavigation = false) {
+ try {
+ showLoader();
+
+ window.commentsCleanup?.();
+ delete window.commentsInitialized;
+ const cached = cache.get(url);
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
+ updatePage(cached.html, url, isHistoryNavigation);
+ return;
+ }
+
+ const html = await fetchContent(url);
+ cache.set(url, { html, timestamp: Date.now() });
+ updatePage(html, url, isHistoryNavigation);
+ } catch (error) {
+ console.error("Navigation error:", error);
+ window.location.href = url;
+ } finally {
+ hideLoader();
+ }
+ }
+
+ async function fetchContent(url) {
+ const response = await fetch(url, {
+ headers: { "X-Requested-With": "XMLHttpRequest" },
+ });
+ return await response.text();
+ }
+
+ function updatePage(html, url, isHistoryNavigation) {
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ const newTitle = doc.title;
+ const newPath = new URL(url).pathname;
+
+ // Обновление контента
+ contentContainers.forEach((selector) => {
+ const container = document.querySelector(selector);
+ const newContent = doc.querySelector(selector);
+ if (container && newContent) {
+ container.innerHTML = newContent.innerHTML;
+ }
+ });
+
+ // Обновление pmain: добавление/удаление
+ const currPmain = document.querySelector("#pmain");
+ const newPmain = doc.querySelector("#pmain");
+ if (!currPmain && newPmain) {
+ document.body.appendChild(newPmain.cloneNode(true));
+ } else if (currPmain && !newPmain) {
+ currPmain.remove();
+ }
+
+ // Управление navbar и title-small
+ const navbar = document.querySelector("#navbard");
+ const titleSmall = document.querySelector("#title-small");
+ const isPhoto = /\/photo\/\d+/.test(newPath);
+ if (isPhoto) {
+ if (navbar) navbar.style.display = "none";
+ if (titleSmall) titleSmall.style.display = "";
+ } else {
+ if (navbar) navbar.style.display = "";
+ if (titleSmall) titleSmall.style.display = "none";
+ }
+
+ // Обработка footer: удаляем дубликаты и ставим единственный в конец
+ const footers = Array.from(document.querySelectorAll("footer"));
+ if (footers.length > 1) footers.slice(1).forEach((f) => f.remove());
+ const footer = document.querySelector("footer");
+ if (footer) document.body.appendChild(footer);
+
+ // Обработка td.footer: оставляем только один и помещаем внутрь таблицы в #pmain
+ const tdFooters = Array.from(document.querySelectorAll("td.footer"));
+ if (tdFooters.length > 1) tdFooters.slice(1).forEach((td) => td.remove());
+ const singleTdFooter = document.querySelector("td.footer");
+ if (singleTdFooter) {
+ let tableWrapper = document.querySelector("#pmain table.footer-wrapper");
+ if (!tableWrapper) {
+ const tbl = document.createElement("table");
+ tbl.className = "footer-wrapper";
+ tbl.width = "100%";
+ tbl.style.marginTop = "30px";
+ const tbody = document.createElement("tbody");
+ const tr = document.createElement("tr");
+ tbody.appendChild(tr);
+ tbl.appendChild(tbody);
+ document.querySelector("#pmain").appendChild(tbl);
+ tableWrapper = tbl;
+ }
+ const tr = tableWrapper.querySelector("tr");
+ tr.innerHTML = "";
+ tr.appendChild(singleTdFooter);
+ }
+
+ // Обновление истории
+ if (!isHistoryNavigation) window.history.pushState({}, "", url);
+
+ // Обновление title
+ document.title = newTitle;
+
+ // Перезагрузка inline-скриптов
+ reloadExternalScripts(doc); // Только новые внешние скрипты
+ reloadInlineScripts(); // Inline-скрипты, кроме Tracy // Инициализация логики
+
+ // Прокрутка наверх
+ window.scrollTo({ top: 0, behavior: "smooth" });
+
+ lastPath = newPath;
+ }
+
+ const executedInlineScripts = new Set();
+
+ function reloadInlineScripts() {
+ document.querySelectorAll("script:not([src])").forEach((oldScript) => {
+ const code = oldScript.textContent.trim();
+ if (!code || /^Tracy\.Debug\.init/.test(code)) return;
+
+ const hash = simpleHash(code);
+ if (executedInlineScripts.has(hash)) return;
+
+ const newScript = document.createElement("script");
+ Array.from(oldScript.attributes).forEach((attr) =>
+ newScript.setAttribute(attr.name, attr.value)
+ );
+ newScript.textContent = code;
+ oldScript.parentNode.replaceChild(newScript, oldScript);
+
+ executedInlineScripts.add(hash);
+ });
+ }
+
+ function simpleHash(str) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash |= 0; // Преобразование в 32-битное целое
+ }
+ return hash;
+ }
+
+ function createLoader() {
+ const loader = document.createElement("div");
+ loader.style = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ padding: 15px;
+ background: #fff;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ border-radius: 5px;
+ display: none;
+ z-index: 9999;
+ `;
+ loader.innerHTML = "🔄 Загрузка...";
+ document.body.appendChild(loader);
+ return loader;
+ }
+
+ function showLoader() {
+ clearTimeout(loadingTimeout);
+ loadingTimeout = setTimeout(() => (loader.style.display = "block"), 300);
+ }
+
+ function hideLoader() {
+ clearTimeout(loadingTimeout);
+ loader.style.display = "none";
+ }
+});