Bitcointalk User Notes Pro

Visualizza e gestisci le note utente con tag personalizzati solo nei thread e nei profili di Bitcointalk.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Bitcointalk User Notes Pro
// @version      1.0.28
// @description  Visualizza e gestisci le note utente con tag personalizzati solo nei thread e nei profili di Bitcointalk.
// @author       Ace
// @match        https://bitcointalk.org/*
// @grant        GM.setValue
// @grant        GM.getValue
// @namespace    https://github.com/ace-d-portugal
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js
// @license      MIT
// ==/UserScript==

(function() {
'use strict';

const getValue = typeof GM_getValue === 'undefined' ? GM.getValue : GM.getValue;
const setValue = typeof GM_setValue === 'undefined' ? GM.setValue : GM.setValue;

const getNotes = async () => {
    try {
        const notes = await getValue('notes');
        return notes ? JSON.parse(notes) : {};
    } catch (error) {
        return {};
    }
};

const setNotes = async (notes) => {
    await setValue('notes', JSON.stringify(notes));
};

const getUserNote = async (userId) => {
    const notes = await getNotes();
    return notes[userId];
};

const setUserNote = async (userId, note, tags = []) => {
    const notes = await getNotes();
    notes[userId] = { text: note, tags };
    await setNotes(notes);
};

const deleteUserNote = async (userId) => {
    const notes = await getNotes();
    delete notes[userId];
    await setNotes(notes);
};

const getSavedTags = async () => {
    try {
        const savedTags = await getValue('savedTags');
        return savedTags ? JSON.parse(savedTags) : {};
    } catch (error) {
        return {};
    }
};

const setSavedTags = async (tags) => {
    await setValue('savedTags', JSON.stringify(tags));
};

const findUserContainer = (link) => {
    // Controlla se il link è all'interno di un post (thread)
    let container = link.closest('.poster_info');
    if (container) return container;

    // Controlla se il link è nella pagina del profilo utente
    if (window.location.href.includes('action=profile')) {
        const profileContainer = document.querySelector('.profile_info');
        if (profileContainer) return profileContainer;
    }

    // Se non è in un post o in un profilo, ignora
    return null;
};

const openNoteModal = async (userId, existingNote = '', existingTags = []) => {
    const savedTags = await getSavedTags();

    const modal = document.createElement('div');
    modal.id = 'note-modal';
    modal.style.position = 'fixed';
    modal.style.top = '0';
    modal.style.left = '0';
    modal.style.width = '100%';
    modal.style.height = '100%';
    modal.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
    modal.style.display = 'flex';
    modal.style.justifyContent = 'center';
    modal.style.alignItems = 'center';
    modal.style.zIndex = '1000';

    const modalContent = document.createElement('div');
    modalContent.style.backgroundColor = 'white';
    modalContent.style.padding = '20px';
    modalContent.style.borderRadius = '10px';
    modalContent.style.width = '500px';
    modalContent.style.maxHeight = '80vh';
    modalContent.style.overflowY = 'auto';

    const tagsHTML = Object.entries(savedTags).map(([tag, style]) => {
        return `<span class="saved-tag" style="background-color: ${style.backgroundColor}; color: ${style.color}; padding: 2px 6px; border-radius: 10px; margin: 2px; display: inline-block; cursor: pointer;">${tag}</span>`;
    }).join('');

    modalContent.innerHTML = `
        <h2 style="margin-top: 0;">Edit Note</h2>
        <textarea id="note-text" style="width: 100%; min-height: 100px; margin: 10px 0;">${existingNote}</textarea>
        <div style="margin: 10px 0;">
            <label for="note-tags">Tags:</label>
            <div id="tags-container" style="margin-top: 5px; border: 1px solid #ddd; padding: 10px; border-radius: 5px; min-height: 50px; display: flex; flex-wrap: wrap; gap: 3px;">
                ${existingTags.map(tag => {
                    const style = savedTags[tag] || { backgroundColor: '#757575', color: 'white' };
                    return `<span class="tag-badge" style="background-color: ${style.backgroundColor}; color: ${style.color}; padding: 2px 6px; border-radius: 10px; margin: 2px; display: inline-block;">${tag}</span>`;
                }).join('')}
            </div>
            <div style="margin-top: 10px;">
                <label>Saved Tags:</label>
                <div id="saved-tags-container" style="margin-top: 5px; display: flex; flex-wrap: wrap; gap: 3px;">
                    ${tagsHTML}
                </div>
                <div style="margin-top: 10px;">
                    <input type="text" id="new-tag-name" placeholder="New Tag Name" style="padding: 5px; margin-right: 5px;">
                    <button id="add-new-tag" style="padding: 5px 10px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;">Add New Tag</button>
                    <div id="color-picker-container" style="margin-top: 10px; display: none;">
                        <div style="margin-bottom: 10px;">
                            <label>Background Color:</label>
                            <input type="color" id="background-color-picker" value="#757575" style="margin-left: 10px;">
                        </div>
                        <div style="margin-bottom: 10px;">
                            <label>Text Color:</label>
                            <input type="color" id="text-color-picker" value="#ffffff" style="margin-left: 10px;">
                        </div>
                        <button id="save-new-tag" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Save Tag</button>
                    </div>
                </div>
            </div>
        </div>
        <div style="display: flex; justify-content: space-between; margin-top: 10px;">
            <button id="delete-note" style="padding: 5px 10px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">Delete Note</button>
            <div>
                <button id="cancel-note" style="padding: 5px 10px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;">Cancel</button>
                <button id="save-note" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Save</button>
            </div>
        </div>
    `;

    modal.appendChild(modalContent);
    document.body.appendChild(modal);

    let newTagName = '';
    let backgroundColor = '#757575';
    let textColor = '#ffffff';

    document.getElementById('add-new-tag').addEventListener('click', () => {
        newTagName = document.getElementById('new-tag-name').value.trim();
        if (newTagName) {
            document.getElementById('color-picker-container').style.display = 'block';
        }
    });

    document.getElementById('background-color-picker').addEventListener('input', (e) => {
        backgroundColor = e.target.value;
    });

    document.getElementById('text-color-picker').addEventListener('input', (e) => {
        textColor = e.target.value;
    });

    document.getElementById('save-new-tag').addEventListener('click', async () => {
        if (newTagName) {
            const savedTags = await getSavedTags();
            savedTags[newTagName] = { backgroundColor, color: textColor };
            await setSavedTags(savedTags);
            document.getElementById('color-picker-container').style.display = 'none';
            document.getElementById('new-tag-name').value = '';
            const savedTagsContainer = document.getElementById('saved-tags-container');
            savedTagsContainer.innerHTML += `<span class="saved-tag" style="background-color: ${backgroundColor}; color: ${textColor}; padding: 2px 6px; border-radius: 10px; margin: 2px; display: inline-block; cursor: pointer;">${newTagName}</span>`;
            newTagName = '';
            backgroundColor = '#757575';
            textColor = '#ffffff';
        }
    });

    document.querySelectorAll('.saved-tag').forEach(tagElement => {
        tagElement.addEventListener('click', async () => {
            const tagName = tagElement.textContent;
            const tagsContainer = document.getElementById('tags-container');
            const existingTag = Array.from(tagsContainer.querySelectorAll('.tag-badge')).find(el => el.textContent === tagName);
            if (!existingTag) {
                const savedTags = await getSavedTags();
                const style = savedTags[tagName] || { backgroundColor: '#757575', color: 'white' };
                tagsContainer.innerHTML += `<span class="tag-badge" style="background-color: ${style.backgroundColor}; color: ${style.color}; padding: 2px 6px; border-radius: 10px; margin: 2px; display: inline-block;">${tagName}</span>`;
            }
        });
    });

    document.getElementById('save-note').addEventListener('click', async () => {
        const noteText = document.getElementById('note-text').value;
        const tagBadges = document.querySelectorAll('#tags-container .tag-badge');
        const tags = Array.from(tagBadges).map(tag => tag.textContent);
        await setUserNote(userId, noteText, tags);
        document.body.removeChild(modal);
        updateAllNotes();
    });

    document.getElementById('delete-note').addEventListener('click', async () => {
        if (confirm('Are you sure you want to delete this note?')) {
            await deleteUserNote(userId);
            document.body.removeChild(modal);
            updateAllNotes();
        }
    });

    document.getElementById('cancel-note').addEventListener('click', () => {
        document.body.removeChild(modal);
    });
};

const updateNoteElement = async (link) => {
    const userIdMatch = link.href.match(/u=(\d+)/);
    if (!userIdMatch) return;
    const userId = userIdMatch[1];

    const noteContainer = findUserContainer(link);
    if (!noteContainer) return; // Ignora se non è in un contesto valido

    const existingNoteDiv = noteContainer.querySelector('.user-note-div');
    if (existingNoteDiv) existingNoteDiv.remove();

    const noteData = await getUserNote(userId);
    const savedTags = await getSavedTags();

    const noteDiv = document.createElement('div');
    noteDiv.className = 'user-note-div';
    noteDiv.style.marginTop = '5px';
    noteDiv.style.fontSize = '0.9em';

    if (noteData) {
        const tagsHTML = noteData.tags.map(tag => {
            const style = savedTags[tag] || { backgroundColor: '#757575', color: 'white' };
            return `<span class="tag-badge" style="background-color: ${style.backgroundColor}; color: ${style.color}; padding: 2px 6px; border-radius: 10px; margin: 2px; display: inline-block;">${tag}</span>`;
        }).join('');

        noteDiv.innerHTML = `
            <div>📃 ${DOMPurify.sanitize(noteData.text)}</div>
            ${noteData.tags.length > 0 ? `<div style="margin-top: 3px; display: flex; flex-wrap: wrap; gap: 3px;">${tagsHTML}</div>` : ''}
            <span class="edit-note" style="cursor: pointer; color: #2e518b; margin-left: 5px; font-weight: bold;">✏️</span>
            <span class="delete-note" style="cursor: pointer; color: #f44336; margin-left: 5px; font-weight: bold;">🗑️</span>
        `;
    } else {
        noteDiv.innerHTML = '<span class="add-note" style="cursor: pointer; font-weight: bold; color: #2e518b;">➕ Add Note</span>';
    }

    noteContainer.appendChild(noteDiv);

    noteDiv.querySelector('.edit-note')?.addEventListener('click', async () => {
        const noteData = await getUserNote(userId);
        openNoteModal(userId, noteData?.text || '', noteData?.tags || []);
    });

    noteDiv.querySelector('.add-note')?.addEventListener('click', () => {
        openNoteModal(userId);
    });

    noteDiv.querySelector('.delete-note')?.addEventListener('click', async () => {
        if (confirm('Are you sure you want to delete this note?')) {
            await deleteUserNote(userId);
            updateAllNotes();
        }
    });
};

const updateAllNotes = async () => {
    const userLinks = document.querySelectorAll('a[href*="action=profile;u="]');
    for (const link of userLinks) {
        await updateNoteElement(link);
    }
};

const injectStyles = () => {
    const style = document.createElement('style');
    style.textContent = `
        .user-note-div {
            margin-top: 5px;
            font-size: 0.9em;
        }
        .edit-note, .add-note, .delete-note {
            cursor: pointer;
            font-weight: bold;
        }
        .edit-note {
            color: #2e518b;
        }
        .delete-note {
            color: #f44336;
        }
        .tag-badge {
            display: inline-block;
        }
    `;
    document.head.appendChild(style);
};

window.addEventListener('load', () => {
    injectStyles();
    updateAllNotes();

    const observer = new MutationObserver(async (mutations) => {
        let needsUpdate = false;
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (node.querySelectorAll('a[href*="action=profile;u="]').length > 0 || node.matches('a[href*="action=profile;u="]')) {
                        needsUpdate = true;
                        break;
                    }
                }
            }
            if (needsUpdate) break;
        }
        if (needsUpdate) {
            await updateAllNotes();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    console.log('Bitcointalk User Notes Pro is running!');
});

})();