Auto-sync enabled

FlatlyPage

Version 1.0.0 • 54 files • 724.77 KB
admin/easyedit.js
(function () {
    const style = document.createElement('style');
    style.textContent = `
        .sas-floating-toolbar {
            position: absolute;
            background: linear-gradient(135deg, rgba(30, 30, 35, 0.98), rgba(20, 20, 25, 0.98));
            backdrop-filter: blur(20px) saturate(180%);
            -webkit-backdrop-filter: blur(20px) saturate(180%);
            border: 1px solid rgba(255, 255, 255, 0.18);
            border-radius: 14px;
            padding: 6px;
            display: flex;
            gap: 3px;
            z-index: 100000;
            box-shadow: 
                0 8px 32px rgba(0, 0, 0, 0.4),
                0 2px 8px rgba(0, 0, 0, 0.2),
                inset 0 1px 0 rgba(255, 255, 255, 0.1);
            opacity: 0;
            visibility: hidden;
            transform: translateY(12px) scale(0.92);
            transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
            pointer-events: none;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }
        
        .sas-floating-toolbar.active {
            opacity: 1;
            visibility: visible;
            transform: translateY(0) scale(1);
            pointer-events: all;
            animation: toolbarEntrance 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
        }
        
        @keyframes toolbarEntrance {
            0% {
                opacity: 0;
                transform: translateY(12px) scale(0.92);
            }
            60% {
                transform: translateY(-2px) scale(1.02);
            }
            100% {
                opacity: 1;
                transform: translateY(0) scale(1);
            }
        }
        
        .sas-floating-toolbar button {
            position: relative;
            background: rgba(255, 255, 255, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.08);
            color: rgba(255, 255, 255, 0.85);
            width: 38px;
            height: 38px;
            cursor: pointer;
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            overflow: hidden;
            padding: 0;
        }
        
        .sas-floating-toolbar button svg {
            width: 18px;
            height: 18px;
            transition: all 0.2s;
        }
        
        .sas-floating-toolbar button::before {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            width: 0;
            height: 0;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.15);
            transform: translate(-50%, -50%);
            transition: width 0.4s, height 0.4s;
        }
        
        .sas-floating-toolbar button:hover {
            background: rgba(255, 255, 255, 0.12);
            border-color: rgba(255, 255, 255, 0.2);
            color: #ffffff;
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        }
        
        .sas-floating-toolbar button:hover svg {
            transform: scale(1.1);
        }
        
        .sas-floating-toolbar button:active::before {
            width: 100px;
            height: 100px;
        }
        
        .sas-floating-toolbar button:active {
            transform: scale(0.95);
        }
        
        .sas-floating-toolbar .divider {
            width: 1px;
            background: linear-gradient(
                to bottom,
                rgba(255, 255, 255, 0),
                rgba(255, 255, 255, 0.15),
                rgba(255, 255, 255, 0)
            );
            margin: 8px 4px;
            align-self: stretch;
        }
        
        .sas-floating-toolbar::after {
            content: '';
            position: absolute;
            bottom: -7px;
            left: 50%;
            transform: translateX(-50%);
            border-left: 7px solid transparent;
            border-right: 7px solid transparent;
            border-top: 7px solid rgba(30, 30, 35, 0.98);
            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
        }
        
        .sas-floating-toolbar.arrow-left::after {
            left: 20px;
            transform: translateX(0);
        }
        
        .sas-floating-toolbar.arrow-right::after {
            left: auto;
            right: 20px;
            transform: translateX(0);
        }
        
        .sas-floating-toolbar button[title]:hover::after {
            content: attr(title);
            position: absolute;
            bottom: -32px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 4px 8px;
            border-radius: 6px;
            font-size: 11px;
            font-weight: 500;
            white-space: nowrap;
            pointer-events: none;
            animation: tooltipFade 0.2s ease;
            z-index: 1;
        }
        
        @keyframes tooltipFade {
            from { opacity: 0; transform: translateX(-50%) translateY(-4px); }
            to { opacity: 1; transform: translateX(-50%) translateY(0); }
        }
    `;
    document.head.appendChild(style);

    // SVG ICONS
    const icons = {
        bold: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M6 4h8a4 4 0 0 1 0 8H6z"/>
            <path d="M6 12h9a4 4 0 0 1 0 8H6z"/>
        </svg>`,

        italic: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <line x1="19" y1="4" x2="10" y2="4"/>
            <line x1="14" y1="20" x2="5" y2="20"/>
            <line x1="15" y1="4" x2="9" y2="20"/>
        </svg>`,

        underline: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M6 3v7a6 6 0 0 0 12 0V3"/>
            <line x1="4" y1="21" x2="20" y2="21"/>
        </svg>`,

        strikethrough: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M16 4H9a3 3 0 0 0-2.83 4"/>
            <path d="M14 12a4 4 0 0 1 0 8H6"/>
            <line x1="4" y1="12" x2="20" y2="12"/>
        </svg>`,

        quote: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/>
            <path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/>
        </svg>`
    };
    
    // TOOLBAR STRUCTURE
    const toolbar = document.createElement('div');
    toolbar.className = 'sas-floating-toolbar';
    toolbar.innerHTML = `
        <button data-tag="b" title="Bold (Ctrl+B)">${icons.bold}</button>
        <button data-tag="i" title="Italic (Ctrl+I)">${icons.italic}</button>
        <div class="divider"></div>
        <button data-tag="u" title="Underline (Ctrl+U)">${icons.underline}</button>
        <button data-tag="s" title="Strikethrough">${icons.strikethrough}</button>
        <div class="divider"></div>
        <button data-tag="quote" title="Quote">${icons.quote}</button>
    `;
    document.body.appendChild(toolbar);

    let activeInput = null;
    let hideTimeout = null;

    // SMART POSITIONING
    const positionToolbar = (x, y) => {
        const toolbarWidth = toolbar.offsetWidth;
        const toolbarHeight = toolbar.offsetHeight;
        const padding = 12;
        const arrowOffset = 18;

        let left = x - (toolbarWidth / 2);
        let top = y - toolbarHeight - arrowOffset;

        const viewportWidth = window.innerWidth;
        const scrollTop = window.pageYOffset;

        toolbar.classList.remove('arrow-left', 'arrow-right');

        if (left < padding) {
            left = padding;
            toolbar.classList.add('arrow-left');
        } else if (left + toolbarWidth > viewportWidth - padding) {
            left = viewportWidth - toolbarWidth - padding;
            toolbar.classList.add('arrow-right');
        }

        if (top < scrollTop + padding) {
            top = y + arrowOffset;
        }

        toolbar.style.left = `${left}px`;
        toolbar.style.top = `${top}px`;
    };

    // FORMATTING LOGIC
    const formatText = (tag) => {
        if (!activeInput) return;

        const start = activeInput.selectionStart;
        const end = activeInput.selectionEnd;
        const value = activeInput.value;
        const selectedText = value.substring(start, end);

        if (!selectedText) return;

        const openTag = `[${tag}]`;
        const closeTag = `[/${tag}]`;

        let replacement;
        let newSelectionStart, newSelectionEnd;

        const alreadyFormatted = selectedText.startsWith(openTag) && selectedText.endsWith(closeTag);

        if (alreadyFormatted) {
            replacement = selectedText.substring(openTag.length, selectedText.length - closeTag.length);
            newSelectionStart = start;
            newSelectionEnd = start + replacement.length;
        } else {
            replacement = openTag + selectedText + closeTag;
            newSelectionStart = start;
            newSelectionEnd = end + openTag.length + closeTag.length;
        }

        const scrollTop = activeInput.scrollTop;

        activeInput.focus();
        activeInput.setSelectionRange(start, end);
        document.execCommand('insertText', false, replacement);

        activeInput.setSelectionRange(newSelectionStart, newSelectionEnd);
        activeInput.scrollTop = scrollTop;

        activeInput.dispatchEvent(new Event('input', { bubbles: true }));
        activeInput.dispatchEvent(new Event('change', { bubbles: true }));
    };

    const showToolbar = () => {
        clearTimeout(hideTimeout);
        toolbar.classList.add('active');
    };

    const hideToolbar = (immediate = false) => {
        clearTimeout(hideTimeout);
        if (immediate) {
            toolbar.classList.remove('active');
        } else {
            hideTimeout = setTimeout(() => {
                toolbar.classList.remove('active');
            }, 100);
        }
    };

    // EVENT HANDLERS
    let selectionTimeout;

    const handleSelection = (e) => {
        clearTimeout(selectionTimeout);

        selectionTimeout = setTimeout(() => {
            const isTextInput = e.target.tagName === 'TEXTAREA' ||
                (e.target.tagName === 'INPUT' && e.target.type === 'text');

            if (!isTextInput) {
                if (!e.target.closest('.sas-floating-toolbar')) {
                    hideToolbar(true);
                }
                return;
            }

            const start = e.target.selectionStart;
            const end = e.target.selectionEnd;
            const hasSelection = start !== end;

            if (hasSelection) {
                activeInput = e.target;
                positionToolbar(e.pageX, e.pageY);
                showToolbar();
            } else {
                if (!e.target.closest('.sas-floating-toolbar')) {
                    hideToolbar(true);
                }
            }
        }, 10);
    };

    // Left click
    document.addEventListener('mouseup', handleSelection);

    // Right click
    document.addEventListener('contextmenu', (e) => {
        const isTextInput = e.target.tagName === 'TEXTAREA' ||
            (e.target.tagName === 'INPUT' && e.target.type === 'text');

        if (isTextInput) {
            setTimeout(() => {
                const start = e.target.selectionStart;
                const end = e.target.selectionEnd;
                if (start !== end) {
                    e.preventDefault();
                    handleSelection(e);
                }
            }, 10);
        }
    });

    // Button clicks
    toolbar.querySelectorAll('button[data-tag]').forEach(btn => {
        btn.addEventListener('mousedown', (e) => {
            e.preventDefault();
            e.stopPropagation();
            formatText(btn.dataset.tag);
        });
    });

    // Keyboard shortcuts
    document.addEventListener('keydown', (e) => {
        if (!activeInput) return;

        const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
        const modKey = isMac ? e.metaKey : e.ctrlKey;

        if (modKey) {
            const key = e.key.toLowerCase();
            let tag = null;

            switch (key) {
                case 'b': tag = 'b'; break;
                case 'i': tag = 'i'; break;
                case 'u': tag = 'u'; break;
            }

            if (tag) {
                e.preventDefault();
                formatText(tag);
            }
        } else if (!e.shiftKey && !e.altKey) {
            hideToolbar(true);
        }
    });

    // Hide on scroll/resize
    window.addEventListener('scroll', () => hideToolbar(true), { passive: true });
    window.addEventListener('resize', () => hideToolbar(true), { passive: true });

    // Prevent closing when clicking toolbar
    toolbar.addEventListener('mousedown', (e) => e.stopPropagation());

    // Hide when clicking outside
    document.addEventListener('mousedown', (e) => {
        if (!e.target.closest('.sas-floating-toolbar') && e.target !== activeInput) {
            hideToolbar(true);
        }
    });

})();