返回首頁 

Greasy Fork is available in English.

DTF User Notes

Add notes to users on dtf.ru

// ==UserScript==// @name         DTF User Notes// @namespace    http://tampermonkey.net/// @version      1.0// @description  Add notes to users on dtf.ru// @author       Avicenna// @match        https://dtf.ru/*// @license      MIT// @grant        none// ==/UserScript==(function() {'use strict';// Функция для создания заметкиfunction createNote(userId, username) {const currentNote = localStorage.getItem(`note_${userId}`);const currentColor = localStorage.getItem(`note_color_${userId}`) || 'gray';const noteText = prompt(`Введите заметку для пользователя ${username}:`, currentNote || '');if (noteText === null) return;const useRedText = confirm("Сделать текст заметки красным? (ОК - да, Отмена - нет)");const textColor = useRedText ? 'red' : 'gray';localStorage.setItem(`note_${userId}`, noteText);localStorage.setItem(`note_color_${userId}`, textColor);displayUserNotes();}// Функция для извлечения ID пользователя из ссылкиfunction extractUserId(href) {const match = href.match(/\/u\/(\d+)-/);return match ? match[1] : null;}// Функция для отображения заметок рядом с ником пользователяfunction displayUserNotes() {// Отображение заметок в постах и комментарияхconst authors = document.querySelectorAll('.author__name, .comment__author, .content-header__author a');authors.forEach(author => {if (author.href) {const userId = extractUserId(author.href);if (userId) {const note = localStorage.getItem(`note_${userId}`);const textColor = localStorage.getItem(`note_color_${userId}`) || 'gray';if (note) {// Удаляем старую заметку, если она естьconst existingNote = author.parentNode.querySelector('.user-note');if (existingNote) existingNote.remove();const noteSpan = document.createElement('span');noteSpan.innerText = ` ${note}`;noteSpan.classList.add('user-note');noteSpan.style.color = textColor;noteSpan.style.marginLeft = '5px';noteSpan.style.padding = '2px 5px';noteSpan.style.borderRadius = '3px';author.parentNode.insertBefore(noteSpan, author.nextSibling);}}}});// Отображение заметки в профилеconst profileName = document.querySelector('.subsite-card__name h1');if (profileName) {const userId = extractUserId(window.location.pathname);if (userId) {const note = localStorage.getItem(`note_${userId}`);const textColor = localStorage.getItem(`note_color_${userId}`) || 'gray';if (note) {// Удаляем старую заметку, если она естьconst existingNote = profileName.parentNode.querySelector('.user-note');if (existingNote) existingNote.remove();const noteSpan = document.createElement('span');noteSpan.innerText = ` ${note}`;noteSpan.classList.add('user-note');noteSpan.style.color = textColor;noteSpan.style.marginLeft = '5px';noteSpan.style.padding = '2px 5px';noteSpan.style.borderRadius = '3px';profileName.parentNode.insertBefore(noteSpan, profileName.nextSibling);}}}}// Функция для экспорта заметок в JSONfunction exportNotes() {const notes = {};for (let i = 0; i < localStorage.length; i++) {const key = localStorage.key(i);if (key.startsWith('note_') && !key.startsWith('note_color_')) {const userId = key.replace('note_', '');const note = localStorage.getItem(key);const color = localStorage.getItem(`note_color_${userId}`) || 'gray';notes[userId] = { note, color };}}const json = JSON.stringify(notes, null, 2);const blob = new Blob([json], { type: 'application/json' });const url = URL.createObjectURL(blob);// Форматируем дату и время для имени файлаconst now = new Date();const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;const formattedTime = `${String(now.getHours()).padStart(2, '0')}-${String(now.getMinutes()).padStart(2, '0')}-${String(now.getSeconds()).padStart(2, '0')}`;const fileName = `dtf_notes_${formattedDate}_${formattedTime}.json`;const a = document.createElement('a');a.href = url;a.download = fileName;a.click();URL.revokeObjectURL(url);}// Функция для импорта заметок из JSONfunction importNotes(event) {const file = event.target.files[0];if (!file) return;// Предупреждение перед импортомconst warningMessage = `Внимание! Импорт удалит все текущие заметки.Убедитесь, что у вас есть резервная копия.Продолжить?`;const isConfirmed = confirm(warningMessage);if (!isConfirmed) return;const reader = new FileReader();reader.onload = (e) => {const json = e.target.r###lt;const notes = JSON.parse(json);// Очищаем все существующие заметкиfor (let i = 0; i < localStorage.length; i++) {const key = localStorage.key(i);if (key.startsWith('note_') || key.startsWith('note_color_')) {localStorage.removeItem(key);}}// Добавляем только те заметки, которые есть в файлеfor (const userId in notes) {if (notes.hasOwnProperty(userId)) {localStorage.setItem(`note_${userId}`, notes[userId].note);localStorage.setItem(`note_color_${userId}`, notes[userId].color);}}displayUserNotes();};reader.readAsText(file);}// Функция для добавления кнопок в существующее выпадающее менюfunction addButtonsToExistingMenu() {const existingMenu = document.querySelector('.context-list');if (!existingMenu || existingMenu.querySelector('.custom-note-button')) return;// Создаем кнопку "Добавить заметку"const addNoteOption = document.createElement('div');addNoteOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');addNoteOption.style = '--press-duration: 140ms;';addNoteOption.innerHTML = `<div class="context-list-option__art context-list-option__art--icon"><svg class="icon icon--note" width="20" height="20"><use xlink:href="#note"></use></svg></div><div class="context-list-option__label">Добавить заметку</div>`;addNoteOption.onclick = () => {const userId = extractUserId(window.location.pathname);const username = document.querySelector('.subsite-card__name h1').innerText;if (userId) createNote(userId, username);};// Создаем кнопку "Экспорт заметок"const exportOption = document.createElement('div');exportOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');exportOption.style = '--press-duration: 140ms;';exportOption.innerHTML = `<div class="context-list-option__art context-list-option__art--icon"><svg class="icon icon--export" width="20" height="20"><use xlink:href="#export"></use></svg></div><div class="context-list-option__label">Экспорт заметок</div>`;exportOption.onclick = exportNotes;// Создаем кнопку "Импорт заметок"const importOption = document.createElement('div');importOption.classList.add('context-list-option', 'context-list-option--with-art', 'custom-note-button');importOption.style = '--press-duration: 140ms;';importOption.innerHTML = `<div class="context-list-option__art context-list-option__art--icon"><svg class="icon icon--import" width="20" height="20"><use xlink:href="#import"></use></svg></div><div class="context-list-option__label">Импорт заметок</div>`;importOption.onclick = () => {const importInput = document.createElement('input');importInput.type = 'file';importInput.accept = '.json';importInput.style.display = 'none';importInput.onchange = importNotes;importInput.click();};// Добавляем кнопки в менюexistingMenu.appendChild(addNoteOption);existingMenu.appendChild(exportOption);existingMenu.appendChild(importOption);}// Функция для запуска после загрузки DOMfunction init() {if (document.querySelector('.subsite-card__header')) {displayUserNotes();addButtonsToExistingMenu();}}// Оптимизация: debounce для вызова displayUserNoteslet debounceTimer;function debounceDisplayUserNotes() {clearTimeout(debounceTimer);debounceTimer = setTimeout(() => displayUserNotes(), 300); // Задержка 300 мс}// Отслеживание изменений на страницеconst observer = new MutationObserver((mutationsList) => {for (let mutation of mutationsList) {if (mutation.type === 'childList') {if (document.querySelector('.subsite-card__header')) {addButtonsToExistingMenu();}// Вызываем displayUserNotes с задержкойdebounceDisplayUserNotes();}}});// Начинаем наблюдение за изменениями в DOMobserver.observe(document.body, { childList: true, subtree: true });// Запуск функций при загрузке страницыinit();})();