Identify Unicode in Selection

Highlight text → right-click → User Script Commands → Identify Unicode in Selection. Shows character, U+XXXX, decimal, and Unicode name in a page modal.

// ==UserScript==
// @name         Identify Unicode in Selection
// @namespace    https://github.com/AnnaRoblox
// @version      1.1.1
// @description  Highlight text → right-click → User Script Commands → Identify Unicode in Selection. Shows character, U+XXXX, decimal, and Unicode name in a page modal.
// @author       annablox
// @match        *://*/*
// @license MIT
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      unicode.org
// ==/UserScript==

(function() {
    'use strict';

    // Load or fetch the full UnicodeData.txt mapping codepoint → name
    async function loadUnicodeNames() {
        let stored = GM_getValue('unicodeMap');
        if (stored) {
            return JSON.parse(stored);
        }
        // Fetch the UnicodeData.txt file
        const data = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://www.unicode.org/Public/UNIDATA/UnicodeData.txt',
                onload: res => resolve(res.responseText),
                onerror: err => reject(err)
            });
        });
        const map = {};
        for (const line of data.split(/\r?\n/)) {
            if (!line) continue;
            const parts = line.split(';');
            const cp = parseInt(parts[0], 16);
            const name = parts[1];
            map[cp] = name;
        }
        // Cache for next time
        GM_setValue('unicodeMap', JSON.stringify(map));
        return map;
    }

    function createModal(htmlContent) {
        // Remove any existing modal
        const existing = document.getElementById('unicode-identify-modal');
        if (existing) existing.remove();

        // backdrop
        const backdrop = document.createElement('div');
        backdrop.id = 'unicode-identify-modal';
        Object.assign(backdrop.style, {
            position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
            background: 'rgba(0,0,0,0.5)', zIndex: '9999', display: 'flex',
            alignItems: 'center', justifyContent: 'center',
        });

        // modal container
        const box = document.createElement('div');
        Object.assign(box.style, {
            background: '#fff', color: '#000', padding: '1em', borderRadius: '8px',
            maxWidth: '80%', maxHeight: '80%', overflowY: 'auto',
            fontFamily: 'monospace', whiteSpace: 'pre-wrap', position: 'relative'
        });

        // close button
        const close = document.createElement('button');
        close.textContent = '× Close';
        Object.assign(close.style, {
            position: 'absolute', top: '0.5em', right: '0.5em',
            background: 'transparent', border: 'none', fontSize: '1.5em',
            cursor: 'pointer', color: '#000'
        });
        close.onclick = () => backdrop.remove();

        box.innerHTML = htmlContent;
        box.appendChild(close);
        backdrop.appendChild(box);
        document.body.appendChild(backdrop);
    }

    GM_registerMenuCommand('Identify Unicode in Selection', async () => {
        const sel = window.getSelection().toString();
        if (!sel) {
            alert('No text selected. Please highlight some text first.');
            return;
        }

        const nameMap = await loadUnicodeNames();

        const lines = [];
        for (let i = 0; i < sel.length; ) {
            const codePoint = sel.codePointAt(i);
            const char = String.fromCodePoint(codePoint);
            const hex = codePoint.toString(16).toUpperCase().padStart(4, '0');
            const name = nameMap[codePoint] || '<no name>';
            lines.push(`${char}    U+${hex}    (${codePoint})    ${name}`);
            i += codePoint > 0xFFFF ? 2 : 1;
        }

        const header = 'Char    Code Point    Decimal    Name\n' +
                       '----    ----------    -------    ----\n';
        createModal(header + lines.join('\n'));
    });
})();