Greasy Fork

来自缓存

Greasy Fork is available in English.

US Card Forum User Tag

Tag users on US Card Forum with personal labels

// ==UserScript==
// @name         US Card Forum User Tag
// @namespace    https://www.uscardforum.com/
// @version      1.2
// @description  Tag users on US Card Forum with personal labels
// @author       Your Name
// @match        https://www.uscardforum.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const STORAGE_KEY = 'uscardforum.user_tags.v1';
    const TAG_STYLE = 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 4px; padding: 2px 8px; margin-left: 8px; font-size: 11px; font-weight: 600; display: inline-block; box-shadow: 0 2px 4px rgba(0,0,0,0.2); vertical-align: middle;';
    const BUTTON_STYLE = 'background: #667eea; color: white; border: none; border-radius: 4px; padding: 3px 8px; margin-left: 8px; cursor: pointer; font-size: 11px; transition: all 0.2s; vertical-align: middle;';

    // Storage functions
    function getTagMap() {
        const tagJsonStr = GM_getValue(STORAGE_KEY, '{}');
        return JSON.parse(tagJsonStr);
    }

    function saveTagMap(tagMap) {
        GM_setValue(STORAGE_KEY, JSON.stringify(tagMap));
    }

    function deleteTag(username) {
        const tagMap = getTagMap();
        delete tagMap[username];
        saveTagMap(tagMap);
    }

    // Create and display tag element
    function createTagElement(tagText) {
        const tagSpan = document.createElement('span');
        tagSpan.textContent = tagText;
        tagSpan.setAttribute('style', TAG_STYLE);
        tagSpan.className = 'user-custom-tag';
        return tagSpan;
    }

    // Create tag button
    function createTagButton(username, containerElement) {
        const tagMap = getTagMap();

        const button = document.createElement('button');
        button.textContent = '🏷️';
        button.className = 'tag-user-btn';
        button.setAttribute('style', BUTTON_STYLE);
        button.title = 'Tag this user';

        button.addEventListener('mouseenter', () => {
            button.style.background = '#5568d3';
        });
        button.addEventListener('mouseleave', () => {
            button.style.background = '#667eea';
        });

        button.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            const currentTag = tagMap[username] || '';
            const newTag = prompt(
                `Edit tag for user: ${username}\n\nEnter tags (comma-separated):\nExamples: "excellent debater", "helpful", "expert"`,
                currentTag
            );

            if (newTag !== null) {
                if (newTag.trim() === '') {
                    if (currentTag && confirm(`Remove all tags for ${username}?`)) {
                        deleteTag(username);
                        location.reload();
                    }
                } else {
                    tagMap[username] = newTag;
                    saveTagMap(tagMap);
                    location.reload();
                }
            }
        });

        return button;
    }

    // Add tags and button to a user element
    function processUserElement(userLink) {
        // Avoid processing twice
        if (userLink.hasAttribute('data-tag-processed')) {
            return;
        }
        userLink.setAttribute('data-tag-processed', 'true');

        // Skip if inside avatar elements
        if (userLink.closest('.post-avatar') || userLink.closest('.topic-avatar') || userLink.closest('.topic-poster') || userLink.closest('.topic-list-data')) {
            return;
        }

        const username = userLink.getAttribute('data-user-card') || userLink.textContent.trim();
        if (!username) return;

        const tagMap = getTagMap();
        const parentSpan = userLink.closest('.first.username') || userLink.parentElement;

        if (!parentSpan) return;

        // Add existing tags
        if (tagMap[username]) {
            const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
            tags.forEach(tag => {
                const tagElement = createTagElement(tag);
                parentSpan.appendChild(tagElement);
            });
        }

        // Add tag button
        const button = createTagButton(username, parentSpan);
        parentSpan.appendChild(button);
    }

    // Process all user links in posts
    function processPosts() {
        // Find all user links in post headers
        const userLinks = document.querySelectorAll('a[data-user-card][href^="/u/"]');
        userLinks.forEach(link => {
            processUserElement(link);
        });
    }

    // Process user profile page
    function processUserProfile() {
        // Profile page has different structure
        const profileHeader = document.querySelector('.user-profile-names .username');
        if (profileHeader && !profileHeader.hasAttribute('data-tag-processed')) {
            profileHeader.setAttribute('data-tag-processed', 'true');

            const username = profileHeader.textContent.trim();
            const tagMap = getTagMap();

            // Add tags
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    profileHeader.appendChild(tagElement);
                });
            }

            // Add button
            const button = createTagButton(username, profileHeader);
            profileHeader.appendChild(button);
        }
    }

    // Process user cards (popup when clicking username)
    function processUserCards() {
        const userCards = document.querySelectorAll('.user-card .names-link, .user-card .username');
        userCards.forEach(nameElement => {
            if (nameElement.hasAttribute('data-tag-processed')) return;
            nameElement.setAttribute('data-tag-processed', 'true');

            const username = nameElement.textContent.trim();
            const tagMap = getTagMap();

            // Add tags
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    nameElement.appendChild(tagElement);
                });
            }

            // Add button
            const button = createTagButton(username, nameElement);
            nameElement.appendChild(button);
        });
    }

    // Process topic lists
    function processTopicLists() {
        // Topic list poster names
        const posterLinks = document.querySelectorAll('.topic-list .posters a[data-user-card], .latest-topic-list-item a[data-user-card]');
        posterLinks.forEach(link => {
            if (link.hasAttribute('data-tag-processed')) return;
            link.setAttribute('data-tag-processed', 'true');

            const username = link.getAttribute('data-user-card') || link.getAttribute('title');
            if (!username) return;

            const tagMap = getTagMap();
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    tagElement.style.marginLeft = '4px';
                    link.parentElement.appendChild(tagElement);
                });
            }
        });
    }

    // Main processing function
    function processAll() {
        processPosts();
        processUserProfile();
        processUserCards();
        processTopicLists();
    }

    // Observer for dynamic content
    const observer = new MutationObserver((mutations) => {
        // Debounce to avoid excessive processing
        clearTimeout(window.tagProcessTimeout);
        window.tagProcessTimeout = setTimeout(() => {
            processAll();
        }, 100);
    });

    // Initialize
    function init() {
        console.log('US Card Forum User Tag script initialized');
        processAll();

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

        console.log('Found', document.querySelectorAll('a[data-user-card][href^="/u/"]').length, 'user links');
    }

    // Wait for page to load
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();