팸코 뎃글콘 + 본문콘

팸코 뎃글콘을 쉽게 쓸수 있게 해줍니다 이제 뎃글콘을 본문에서도 쓸수 있습니다

// ==UserScript==
// @name        팸코 뎃글콘 + 본문콘
// @namespace   Violentmonkey Scripts
// @match        *://*.fmkorea.com/*
// @grant       none
// @version     1.4
// @author      -
// @description 팸코 뎃글콘을 쉽게 쓸수 있게 해줍니다 이제 뎃글콘을 본문에서도 쓸수 있습니다
// ==/UserScript==

(function () {
    'use strict';

// 상수 정의
const CONSTANTS = {
    SPREADSHEET_ID: 'YOUR_SPREADSHEET_ID',
    SHEET_CONFIG: [
        { name: 'Sheet1', displayName: '이름없음' },
        { name: 'Sheet2', displayName: '이름없음' },
        { name: 'Sheet3', displayName: '이름없음' },
        { name: 'Sheet4', displayName: '이름없음' },
        { name: 'Sheet5', displayName: '이름없음' },
    ],
    GRID_COLORS: ['#f0f0f0', '#e8f5e9', '#e3f2fd', '#fff3e0', '#fce4ec'],
    CELL_SIZE: 50,
    GRID_COLS: 10
};

// 로컬 스토리지 관리를 위한 유틸리티
const storageUtils = {
    saveRecentEmoticon(url) {
        try {
            const recent = JSON.parse(localStorage.getItem('recentEmoticons') || '[]');
            const index = recent.indexOf(url);
            if (index > -1) {
                recent.splice(index, 1);
            }
            recent.unshift(url);
            localStorage.setItem('recentEmoticons', JSON.stringify(recent.slice(0, 20)));
        } catch (error) {
            console.error('Failed to save recent emoticon:', error);
        }
    },

    getRecentEmoticons() {
        try {
            return JSON.parse(localStorage.getItem('recentEmoticons') || '[]');
        } catch (error) {
            console.error('Failed to get recent emoticons:', error);
            return [];
        }
    }
};

// 유틸리티 함수들
const utils = {
    createElementWithStyles: (tag, styles = {}) => {
        const element = document.createElement(tag);
        Object.assign(element.style, styles);
        return element;
    },

    addHideScrollbarStyles: (element) => {
        Object.assign(element.style, {
            msOverflowStyle: 'none',
            scrollbarWidth: 'none'
        });
        element.style.cssText += '::-webkit-scrollbar { display: none; }';
        return element;
    },

    debounce: (func, wait) => {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }
};

// 스프레드시트 데이터 fetcher
class SpreadsheetDataFetcher {
    static async fetchData(spreadsheetId, sheetName) {
        const url = `https://docs.google.com/spreadsheets/d/${spreadsheetId}/gviz/tq?tqx=out:json&sheet=${sheetName}`;

        try {
            const response = await fetch(url);
            const text = await response.text();
            const jsonData = JSON.parse(text.substring(47).slice(0, -2));

            return jsonData.table.rows
                .filter(row => row.c && row.c[0] && row.c[0].v)
                .map(row => row.c[0].v);
        } catch (error) {
            console.error(`Failed to fetch ${sheetName} data:`, error);
            return [];
        }
    }
}

// 미디어 셀 컴포넌트
class MediaCell {
    constructor(url, index, cols) {
        this.url = url;
        this.index = index;
        this.cols = cols;
        this.element = this.createElement();
    }

    createElement() {
        const cell = utils.createElementWithStyles('div', {
            width: `${CONSTANTS.CELL_SIZE}px`,
            height: `${CONSTANTS.CELL_SIZE}px`,
            backgroundColor: CONSTANTS.GRID_COLORS[Math.floor(this.index / this.cols) % CONSTANTS.GRID_COLORS.length],
            border: '1px solid #ccc',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            flexDirection: 'column',
            position: 'relative',
            transition: 'all 0.3s ease',
            zIndex: '1'
        });

        const mediaElement = this.createImageElement();
        cell.appendChild(mediaElement);

        this.addEventListeners(cell);
        return cell;
    }

    createImageElement() {
        const img = utils.createElementWithStyles('img', {
            width: '100%',
            height: '100%',
            objectFit: 'cover',
            position: 'absolute',
            top: '0',
            left: '0',
            transition: 'all 0.3s ease'
        });

        img.src = this.url;
        img.alt = '이미지';

        img.onerror = () => this.handleMediaError(img);
        return img;
    }

    handleMediaError(mediaElement) {
        mediaElement.style.display = 'none';
        const errorText = utils.createElementWithStyles('div', {
            fontSize: '10px',
            color: '#999'
        });
        errorText.textContent = '이미지 없음';
        this.element.appendChild(errorText);
    }

    addEventListeners(cell) {
        cell.addEventListener('mouseover', () => this.handleMouseOver(cell));
        cell.addEventListener('mouseout', () => this.handleMouseOut(cell));
        cell.addEventListener('click', () => this.handleClick());
    }

    handleMouseOver(cell) {
        Object.assign(cell.style, {
            backgroundColor: '#e0e0e0',
            cursor: 'pointer',
            opacity: '0.9',
            transform: 'scale(2)',
            zIndex: '2'
        });
    }

    handleMouseOut(cell) {
        Object.assign(cell.style, {
            backgroundColor: CONSTANTS.GRID_COLORS[Math.floor(this.index / this.cols) % CONSTANTS.GRID_COLORS.length],
            opacity: '1',
            transform: 'scale(1)',
            zIndex: '1'
        });
    }
  handleClick() {

        storageUtils.saveRecentEmoticon(this.url);

        // XpressEditor 처리
        const xpressEditor = document.querySelector('.xpress-editor');
        if (xpressEditor) {
            const editorFrame = xpressEditor.querySelector('iframe');
            if (editorFrame && editorFrame.contentWindow) {
                const doc = editorFrame.contentWindow.document;
                const bodyElement = doc.getElementById('___body');

                if (bodyElement) {
                    const mediaTag = `<img src="${this.url}" style="max-width: 100%;" />`;

                    // 현재 선택 영역 가져오기
                    const selection = editorFrame.contentWindow.getSelection();

                    if (selection.rangeCount > 0) {
                        // 현재 커서 위치의 range 가져오기
                        const range = selection.getRangeAt(0);

                        // 새로운 컨텐츠 생성
                        const newContent = doc.createElement('div');
                        newContent.innerHTML = `<p>${mediaTag}</p><p><br /></p>`;

                        // 새 컨텐츠의 노드들을 순서대로 삽입
                        Array.from(newContent.childNodes).forEach(node => {
                            range.insertNode(node);
                            range.setStartAfter(node);
                        });

                    } else {
                        // 선택 영역이 없는 경우 본문 끝에 추가
                        const newContent = doc.createElement('div');
                        newContent.innerHTML = `<p>${mediaTag}</p><p><br /></p>`;
                        bodyElement.append(...newContent.childNodes);
                    }
                }
            }
        }

        // 기존 댓글창 처리
        const textarea = document.querySelector('.fdb_lst_ul #re_cmt textarea') ||
                        document.querySelector('.cmt_editor textarea');

        if (textarea) {
            textarea.value = this.url;
            textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

            if (textarea.hasAttribute('data-autogrow')) {
                textarea.style.height = 'auto';
                textarea.style.height = `${textarea.scrollHeight}px`;
            }

            setTimeout(() => {
                const submitButton = document.querySelector('input.bd_btn.keyup_alt');
                if (submitButton) {
                    submitButton.click();
                }
            }, 100);
        }

        // 모달 닫기
        const modal = document.querySelector('.emotion-modal');
        if (modal) {
            modal.style.display = 'none';
        }
    }
}

// 모달 컴포넌트
class EmotionModal {
    constructor() {
        this.modal = this.createModal();
        this.modalContent = this.createModalContent();
        this.button = this.createButton();
        this.initialize();
        this.initializeXpressEditorButton();
    }

    async initialize() {
        try {
            const gridData = await this.fetchAllSheetData();
            if (gridData.length === 0) {
                console.log('No images to display');
                return;
            }

            this.setupModalContent(gridData);
            this.setupEventListeners();
            this.updateButtonPosition();

            document.body.appendChild(this.button);
            document.body.appendChild(this.modal);
        } catch (error) {
            console.error('Failed to initialize emotion modal:', error);
        }
    }

    async fetchAllSheetData() {
        const gridData = await Promise.all(
            CONSTANTS.SHEET_CONFIG.map(sheet =>
                SpreadsheetDataFetcher.fetchData(CONSTANTS.SPREADSHEET_ID, sheet.name)
            )
        );

        return CONSTANTS.SHEET_CONFIG
            .map((config, index) => ({
                data: gridData[index],
                config: config
            }))
            .filter(item => item.data.length > 0);
    }

    createModal() {
        const modal = utils.addHideScrollbarStyles(
            utils.createElementWithStyles('div', {
                display: 'none',
                position: 'fixed',
                zIndex: '1000',
                left: '0',
                top: '0',
                width: '100%',
                height: '100%',
                backgroundColor: 'rgba(0,0,0,0.4)',
                overflowY: 'scroll'
            })
        );
        modal.className = 'emotion-modal';
        return modal;
    }

    createModalContent() {
        return utils.addHideScrollbarStyles(
            utils.createElementWithStyles('div', {
                backgroundColor: '#fefefe',
                margin: '5% auto',
                marginBottom: '5%',
                padding: '15px',
                border: '1px solid #888',
                width: '80%',
                maxWidth: '600px',
                borderRadius: '5px',
                position: 'relative',
                maxHeight: '90vh',
                overflowY: 'scroll'
            })
        );
    }

    createButton() {
        const button = utils.createElementWithStyles('button', {
            position: 'fixed',
            zIndex: '1000',
            padding: '10px 20px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
            transition: 'all 0.3s ease'
        });
        button.textContent = '댓글콘';
        return button;
    }

    // Xpress 에디터 버튼 초기화
    initializeXpressEditorButton() {
        const editor = document.querySelector('.xpress-editor');

        if (editor) {
            const toolDiv = editor.querySelector('.tool');

            if (toolDiv) {
                const eighthChild = toolDiv.children[7];

                if (eighthChild) {
                    const newUl = document.createElement('ul');
                    const newLi = document.createElement('li');
                    const newSpan = document.createElement('span');

                    newSpan.textContent = '댓글콘';
                    newSpan.style.height = '21px';
                    newSpan.style.width = '60px';
                    newSpan.style.display = 'inline-block';
                    newSpan.style.cursor = 'pointer';

                    // 클릭 이벤트 추가
                    newSpan.addEventListener('click', () => {
                        this.modal.style.display = 'block';
                    });

                    newLi.appendChild(newSpan);
                    newUl.appendChild(newLi);
                    eighthChild.after(newUl);
                }
            }
        }
    }

    setupModalContent(gridData) {
        // 상단 헤더 컨테이너 생성
        const headerContainer = utils.createElementWithStyles('div', {
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            width: '100%',
            marginBottom: '15px'
        });

        // 제목 생성
        const title = utils.createElementWithStyles('h3', {
            margin: '0',
            padding: '0'
        });
        title.textContent = '댓글콘';

        // 스프레드시트 링크 생성
        const spreadsheetLink = utils.createElementWithStyles('a', {
            display: 'flex',
            alignItems: 'center',
            padding: '5px 10px',
            backgroundColor: '#34A853',
            color: 'white',
            borderRadius: '4px',
            textDecoration: 'none',
            fontSize: '12px',
            cursor: 'pointer',
            transition: 'background-color 0.2s'
        });

        // 구글 시트 아이콘 SVG
        const sheetIcon = `
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" style="margin-right: 5px;" fill="white">
                <path d="M19 3H5C3.89 3 3 3.89 3 5V19C3 20.11 3.89 21 5 21H19C20.11 21 21 20.11 21 19V5C21 3.89 20.11 3 19 3M19 19H5V5H19V19M17 17H7V7H17V17M15 15H9V13H15V15M15 11H9V9H15V11Z"/>
            </svg>
        `;

        spreadsheetLink.innerHTML = `${sheetIcon} 시트 바로가기`;
        spreadsheetLink.href = `https://docs.google.com/spreadsheets/d/${CONSTANTS.SPREADSHEET_ID}`;
        spreadsheetLink.target = '_blank';
        spreadsheetLink.rel = 'noopener noreferrer';

        // 마우스 오버 효과
        spreadsheetLink.addEventListener('mouseover', () => {
            spreadsheetLink.style.backgroundColor = '#2E9548';
        });
        spreadsheetLink.addEventListener('mouseout', () => {
            spreadsheetLink.style.backgroundColor = '#34A853';
        });

        // 헤더에 요소들 추가
        headerContainer.appendChild(title);
        headerContainer.appendChild(spreadsheetLink);

        const mainContainer = this.createMainContainer();

        // 최근 사용 섹션 추가
        const recentEmoticons = storageUtils.getRecentEmoticons();
        if (recentEmoticons.length > 0) {
            const recentSection = {
                data: recentEmoticons,
                config: { displayName: '최근 사용' }
            };
            mainContainer.appendChild(this.createGridSection(recentSection));
            mainContainer.appendChild(this.createDivider());
        }

        // 기존 그리드 데이터 추가
        gridData.forEach((item, index) => {
            if (index > 0) {
                mainContainer.appendChild(this.createDivider());
            }
            mainContainer.appendChild(this.createGridSection(item));
        });

        this.modalContent.innerHTML = '';
        this.modalContent.appendChild(headerContainer);
        this.modalContent.appendChild(mainContainer);
        this.modal.appendChild(this.modalContent);
    }

    createMainContainer() {
        return utils.addHideScrollbarStyles(
            utils.createElementWithStyles('div', {
                display: 'flex',
                flexDirection: 'column',
                gap: '15px',
                alignItems: 'center',
                width: '100%',
                overflowX: 'hidden',
                overflowY: 'scroll'
            })
        );
    }

    createDivider() {
        return utils.createElementWithStyles('hr', {
            width: '90%',
            margin: '8px auto'
        });
    }

    createGridSection(item) {
        const section = utils.createElementWithStyles('div', {
            width: '100%',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center'
        });

        const label = this.createSectionLabel(item);
        const grid = this.createGrid(item.data);

        section.appendChild(label);
        section.appendChild(grid);
        return section;
    }

    createSectionLabel(item) {
        const label = utils.createElementWithStyles('div', {
            fontSize: '12px',
            fontWeight: 'bold',
            padding: '4px',
            backgroundColor: item.config.displayName === '최근 사용' ? '#e3f2fd' : '#f5f5f5',
            borderRadius: '3px',
            marginBottom: '8px',
            width: '100%',
            textAlign: 'center'
        });
        label.textContent = item.config.displayName === '최근 사용' ?
            '최근 사용' :
            `${item.config.displayName} (${item.data.length}개)`;
        return label;
    }

    createGrid(contents) {
        const grid = utils.createElementWithStyles('div', {
            display: 'grid',
            gridTemplateColumns: `repeat(${CONSTANTS.GRID_COLS}, ${CONSTANTS.CELL_SIZE}px)`,
            gap: '3px',
            justifyContent: 'center',
            padding: '8px',
            transition: 'all 0.3s ease'
        });

        contents.forEach((url, index) => {
            const cell = new MediaCell(url, index, CONSTANTS.GRID_COLS);
            grid.appendChild(cell.element);
        });

        return grid;
    }

    setupEventListeners() {
        // 기존 이벤트 리스너들
        this.button.addEventListener('click', () => this.modal.style.display = 'block');
        this.modal.addEventListener('click', (event) => {
            if (event.target === this.modal) {
                this.modal.style.display = 'none';
            }
        });

        // 스프레드시트 단축키 추가
        document.addEventListener('keydown', (e) => {
            if (e.altKey && e.key.toLowerCase() === 's') {
                e.preventDefault(); // 기본 동작 방지
                window.open(`https://docs.google.com/spreadsheets/d/${CONSTANTS.SPREADSHEET_ID}`, '_blank');
            }
        });

        const debouncedUpdatePosition = utils.debounce(() => this.updateButtonPosition(), 100);
        window.addEventListener('scroll', debouncedUpdatePosition);
        window.addEventListener('resize', debouncedUpdatePosition);

        this.observeDOMChanges();
    }

    updateButtonPosition() {
        const content = document.querySelector('.rd_nav.img_tx.fr.m_btn_wrp');
        if (!content) {
            console.error('Navigation element not found');
            return;
        }

        const contentRect = content.getBoundingClientRect();
        const windowHeight = window.innerHeight;

        if (contentRect.bottom <= windowHeight) {
            this.button.style.top = `${contentRect.bottom - 140}px`;
            this.button.style.right = `${window.innerWidth - contentRect.right - 66}px`;
        }
    }

    observeDOMChanges() {
        const observer = new MutationObserver(() => {
            const commentSection = document.querySelector('.rd_ft_nav.clear');
            this.button.style.display = commentSection ? 'block' : 'none';
            if (commentSection) {
                this.updateButtonPosition();
            }

});

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

// 애플리케이션 초기화
function initializeApp() {
    const commentSection = document.querySelector('.rd_ft_nav.clear');
    if (commentSection) {
        new EmotionModal();
    } else {
        console.log('Comment section not found. Button will be created only if XpressEditor exists.');
        const xpressEditor = document.querySelector('.xpress-editor');
        if (xpressEditor) {
            new EmotionModal();
        }
    }
}

// 핫키 설정
document.addEventListener('keydown', (e) => {
    // Alt + E로 이모티콘 모달 열기
    if (e.altKey && e.key.toLowerCase() === 'e') {
        const modal = document.querySelector('.emotion-modal');
        if (modal) {
            modal.style.display = modal.style.display === 'none' ? 'block' : 'none';
        }
    }
});

// 스크립트 실행
(() => {
    // DOM이 완전히 로드된 후 실행
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeApp);
    } else {
        initializeApp();
    }
})();

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址