话题聊天室4.0来咯,已上架!

最新版说明

  • 更加兼容但需要自己根据屏幕调整位置,实属抱歉,后续一定会进行优化。
      const buttonWrapper = createAndAppendElement('div', {
            title: "聊天室",
            style: `position: fixed;
            top: 1.4%;//调节距离上方高度
            right: 15.5%;//调节距离右边距离
            transform: translate(50%, 0);
            z-index: 9999;
            cursor: move;
            width: 42.33px;
            height: 42.33px;
            display: flex;
            justify-content: center;
            align-items: center;`
            }, null, document.body);

版本 更新日志

  • 4.1.1 更新页面,解除点赞不显示bug
  • 4.1.0 更新页面,支持多张图片上传,优化代码
  • 4.0.9 修改介绍语
  • 4.0.8 调整样式
  • 4.0.7 调整高度,宽度,更加像聊天室咯
  • 4.0.6 小小的更新一下
  • 4.0.5 感谢Reno佬提供的新方式,将聊天室放在了很好的位置
  • 4.0.4 新奇的体验,快来试试吧
  • 4.0.3修复了之前在聊天室查看帖子后不算作已阅读的 bug

版本 4.0 更新内容

  • 图片上传: 添加了图片上传功能,目前支持单张图片上传。若上传错误,点击图片即可清除当前图片。后续将进行优化。
  • 新消息提醒: 当有新消息到来时,按钮颜色会变化。打开后,帖子将标记为已读,解决之前已读帖子仍存在小蓝点的问题。
  • 楼主显示: 对楼主进行了标识,在帖子用户名前展示小红旗。
  • 样式修改: 聊天框打开后,最近回复按钮消失;聊天框关闭后展示。

点击这里,安装话题聊天室脚本

话题聊天室

图片展示:

从左到右 初始效果 回复效果 发送图片效果

往期内容

  • 可以点赞: 添加了点赞功能
  • 可以回复: 点击回复按钮,会让下面的头像进行改变,若不想回复即再次点击回复头像即可清空回复。
  • 在线状态: 鼠标悬浮获取用户在线状态,估计现在失效了。
3.0样式以及代码

// ==UserScript==
// @name         最近回复
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Display and quickly reply to the most recent reply to your recent post on Linux.do forum
// @author       unique
// @match        https://linux.do/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    //若需要固定话题聊天,只需要将下面的值进行替换对应的话题id,如//let fixedTopic_id = 1,不填入该值则为你最近回复的话题
    let fixedTopic_id = '';
    // 不要轻易动他,否则回不了家
    let homeTopic_id = '';
    let homeNumber = '';
    //用户名设置
    let username ='';
    // 添加拖动功能
    function addDraggableFeature(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const dragMouseDown = function (e) {
            // 检查事件的目标是否是输入框
            if (e.target.tagName.toUpperCase() === 'INPUT' || e.target.tagName.toUpperCase() === 'TEXTAREA') {
                return; // 如果是,则不执行拖动逻辑
            }
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        };
        const elementDrag = function (e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
            // 为了避免与拖动冲突,在此移除bottom和right样式
            element.style.bottom = '';
            element.style.right = '';
        };
        const closeDragElement = function () {
            document.onmouseup = null;
            document.onmousemove = null;
        };
        element.onmousedown = dragMouseDown;
    }
    const getUsername = async () => {
        // 构建带有 CSRF token 的请求头
        const headers = new Headers();
        headers.append('X-Csrf-Token', getCsrfToken());

        // 发起带有 CSRF token 的请求
        const response = await fetch('https://linux.do/my/summany.json', {
            method: 'GET',
            headers: headers
        });
        const newURL = response.url;
        const urlObj = new URL(newURL);
        const pathParts = urlObj.pathname.split('/');
        username = pathParts[2];
    };

    const getCsrfToken = () => {
        const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]');
        return csrfTokenMeta ? csrfTokenMeta.getAttribute('content') : null;
    };
    const fetchMostRecentReply = async () => {
        try {
            const response = await fetch(`https://linux.do/user_actions.json?offset=0&username=${username}&filter=5`);
            if (!response.ok) throw new Error('Failed to fetch recent reply.');

            const jsonData = await response.json();
            if (jsonData.length === 0) {
                console.error('No recent actions found');
                return null;
            }
            const recentPost = jsonData.user_actions[0];
            if (fixedTopic_id === '') {
                homeTopic_id = recentPost.topic_id;
            } else {
                homeTopic_id = fixedTopic_id;
            }
            const postTopicResponse = await fetch(`https://linux.do/t/topic/${homeTopic_id}.json`);
            const postTopicJsonData = await postTopicResponse.json();
            let postNumber = postTopicJsonData.last_read_post_number;
            const postHeightNumber = postTopicJsonData.highest_post_number;
            if (postHeightNumber >= postNumber){
                postNumber = postHeightNumber;
            }
            homeNumber = postNumber;
            const postResponse = await fetch(`https://linux.do/t/topic/${homeTopic_id}/${postNumber}.json`);
            const postJsonData = await postResponse.json();
            const posts = postJsonData.post_stream.posts;
            const lastTenPosts = posts.map(post => ({
                id:post.id,
                name: post.name,
                username: post.username,
                avatar_template: post.avatar_template,
                cooked: post.cooked,
                reaction_users_count:post.reaction_users_count,
                current_user_used_main_reaction:post.current_user_used_main_reaction,
                reply_to_user:post.reply_to_user,
                date: post.created_at,
                title: postJsonData.title,
                floor:post.post_number
            }));
            return {topicId: homeTopic_id, mostRecentReply: lastTenPosts };
        } catch (error) {
            console.error('Error fetching recent reply:', error);
            return null;
        }
    };
    const createAndAppendElement = (tag, attributes, textContent, parent) => {
        const element = document.createElement(tag);
        if (attributes) {
            Object.keys(attributes).forEach(key => {
                element.setAttribute(key, attributes[key]);
            });
        }
        if (textContent) {
            element.textContent = textContent;
        } else {
            element.innerHTML = attributes && attributes.innerHTML ? attributes.innerHTML : '';
        }
        if (parent) {
            parent.appendChild(element);
        }
        return element;
    };

    const fetchPutLikePost = (postID, actionI) => {
        const csrfToken = getCsrfToken();
        return fetch(
            `https://linux.do/discourse-reactions/posts/${postID}/custom-reactions/heart/toggle.json`,
            {
                headers: {
                    "x-csrf-token": csrfToken,
                },
                method: "PUT",
                credentials: "include",
            }
        );
    };

    let reply_to_post_number= '';
    let reply_avater = 'https://cdn.linux.do/uploads/default/original/1X/3a18b4b0da3e8cf96f7eea15241c3d251f28a39b.png';
    const sendNewPost = async (content, topicId) => {
        const url = 'https://linux.do/posts';
        const csrfToken = getCsrfToken();
        if (!csrfToken) return;
        const headers = {
            'Accept': 'application/json, text/plain, */*',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Origin': 'https://linux.do',
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-Token': csrfToken
        };

        const formData = new URLSearchParams({
            'raw': content,
            'unlist_topic': 'false',
            'topic_id': topicId,
            'is_warning': 'false',
            'archetype': 'regular',
            'featured_link': '',
            'whisper': false,
            'shared_draft': 'false',
            'draft_key': `topic_${topicId}`,
            'nested_post': 'true',
            'reply_to_post_number':reply_to_post_number
        });

        try {
            const response = await fetch(url, {
                method: 'POST',
                headers: headers,
                body: formData,
                credentials: 'include'
            });
            reply_to_post_number = '';
            const avatarImg = document.getElementById('quick-reply-avatar');
            // 更新头像的 src 属性
            avatarImg.src = 'https://cdn.linux.do/uploads/default/original/1X/3a18b4b0da3e8cf96f7eea15241c3d251f28a39b.png';
            if (!response.ok) throw new Error('Failed to send new post.');
        } catch (error) {
            console.error('Error sending new post:', error);
        }
    };

    // 初始化弹框样式
    const initPopupStyle = () => {
        return 'display: none;position: fixed; bottom: 2px; right: 5px; z-index: 9999; width: 400px; height: 440px; overflow-y: hidden; padding: 10px; background-color: #fff; border: 1px solid #ccc; box-shadow: 0 2px 4px rgba(0,0,0,0.1);';
    };


    // 创建标题和内容区域
    const createTitleAndContent = async (popup) => {
        try {
            const mostRecentReply = await fetchMostRecentReply(); // 等待获取最近回复数据
            if (!mostRecentReply) return; // 如果获取数据失败,则退出函数

            let recentReply = mostRecentReply.mostRecentReply[0].title;
            const title = createAndAppendElement('div', { style: 'font-weight: bold; margin-bottom: 10px;' }, null, popup);

            // 创建并添加 SVG 图标
            const svgIcon = `
           <svg class="fa d-icon d-icon-d-chat svg-icon svg-string" xmlns="http://www.w3.org/2000/svg">
             <use href="#comment" style="fill: #0088cc;"></use>
           </svg>
        `;
            const svgElement = createAndAppendElement('span', null, null, title);
            svgElement.innerHTML = svgIcon;

            // 添加文字
            createAndAppendElement('span', null,'最近回复' , title);


            const content = createAndAppendElement('div', { id: 'recent-reply-content', style: 'overflow-y: auto; height: 80%; margin-bottom: 10px;' }, null, popup);

            const recentReplyContent = document.getElementById('recent-reply-content');
            const isScrolledToBottom = recentReplyContent.scrollHeight - recentReplyContent.clientHeight <= recentReplyContent.scrollTop + 1;

            recentReplyContent.innerHTML = '';
            if (!mostRecentReply) return;

            const { mostRecentReply: replies } = mostRecentReply;
            replies.forEach(reply => {
                if(reply.name === reply.username){
                    reply.name = reply.username;
                }else{
                    reply.name = reply.name +'  '+ reply.username;
                }
                createCard(reply);
            });
            if (isScrolledToBottom) {
                recentReplyContent.scrollTop = recentReplyContent.scrollHeight - recentReplyContent.clientHeight;
            }
            function createCard(reply) {
                const truncatedName = reply.name.length > 10 ? reply.name.substring(0, 10) + '...' : reply.name;
                const cardHtml = `
  <div style="display: flex; align-items: start; padding: 12px; background-color: #f0f0f0; border-radius: 16px; margin-bottom: 12px; max-width: 345px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
   <img src="${reply.avatar_template.replace("{size}", "144")}"="" alt="" width="45" height="45" class="avatar" loading="lazy" style="border-radius: 50%; margin-right: 10px; object-fit: cover;" />
   <div style="flex-grow: 1; overflow: hidden;">
    <div style="display: flex; justify-content: space-between; align-items: center;">
     <div style="color: #646464; font-weight: bold; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${reply.name}">
      ${truncatedName}
     </div>
     <div style="color: #777; font-size: 12px;">
      <!-- 判断是否存在 reply.reply_to_user --> ${reply.reply_to_user ? `
      <span style="vertical-align: middle;">
       <svg class="fa d-icon d-icon-share svg-icon svg-node" style="vertical-align: middle; width: 12px; height: 10px;">
        <use xlink:href="#share"></use>
       </svg> <img src="https://cdn.linux.do${reply.reply_to_user.avatar_template.replace('{size}', '48')}" alt="Avatar" style="width: 18px; height: 18px; border-radius: 50%; margin-right: 4px;padding-bottom: 0px;" /> </span>` : ''} ${formatDate(reply.date)}
     </div>
    </div>
    <div style="color: #555; word-wrap: break-word; font-size: 16px; font-family: 'Microsoft YaHei';">
     ${reply.cooked}
    </div>
    <div style="display: flex; align-items: center; margin-bottom: 12px;">
     <span style="color: #b6b6b6; font-size: 12px; margin-right: 10px;">楼层: ${reply.floor}</span>
     <div style="flex-grow: 1;"></div>
     <!-- 空白的flex-grow用于将后续内容推到右侧 -->
     <button class="heart-button" style="border: none;background-color: transparent; padding-top: 2px;">
     <span style="color: #b6b6b6; font-size: 16px; margin-left: 4px;padding-bottom: 1px">${reply.reaction_users_count}</span>
       <img src="${reply.current_user_used_main_reaction ? 'https://cdn.linux.do/uploads/default/original/3X/9/6/96d0dab6761d121c2e0b3a7d31fdc6ef41dc37c7.png':'https://cdn.linux.do/uploads/default/original/3X/c/7/c79f3b7c76904bdc488d805b8f6b9e11a99ff5b0.png'}" style="width: 18px; height: 16px; margin-left: 1px; padding-bottom: 2px;pointer-events: none;">
     </button>
     <!-- 回复按钮 -->
     <button class="widget-button btn-flat reply create fade-out btn-icon-text" style="margin-left: 1px;">
      <svg class="fa d-icon d-icon-reply svg-icon svg-node" aria-hidden="true">
       <use xlink:href="#reply"></use>
      </svg> </button>
    </div>
   </div>
  </div>
`;



                const card = createAndAppendElement('div', {innerHTML: cardHtml}, null, recentReplyContent);

                const avatarImg = card.querySelector('.avatar');
                avatarImg.addEventListener('mouseover', async () => {
                    const isOnline = await checkLastSeenWithinFiveMinutes(reply.username);
                    if (isOnline) {
                        // 如果用户在线,给头像添加绿色边框
                        avatarImg.style.border = '2px solid green';
                    } else {
                        // 如果用户不在线,保持头像原有样式
                        // 这里你可以添加相应的处理逻辑,例如添加红色边框或其他样式
                    }
                });

                avatarImg.addEventListener('mouseleave', () => {
                    // 鼠标离开头像时将边框恢复为原来的状态
                    avatarImg.style.border = ''; // 清除边框样式
                });


                // 为按钮添加点击事件监听器
                const button = card.querySelector('.widget-button');
                button.addEventListener('click', function() {
                    const floor = reply.floor;
                    reply_avater = reply.avatar_template.replace("{size}", "144");
                    reply_to_post_number = floor;
                    console.log(reply_to_post_number);
                    console.log(reply_avater);
                    // 获取头像元素
                    const avatarImg = document.getElementById('quick-reply-avatar');
                    // 更新头像的 src 属性
                    avatarImg.src = reply_avater;
                });

                const heartbutton = card.querySelector('.heart-button');
                heartbutton.addEventListener('click',function() {
                    console.log(reply.id);
                    fetchPutLikePost(reply.id);
                    createTitleAndContent(popup);
                });

                // Check for images in the reply and add click event to open them in a new tab
                const images = card.querySelectorAll('img');
                images.forEach(image => {
                    image.style.cursor = 'pointer';
                    image.addEventListener('click', (event) => {
                        event.preventDefault();
                        window.open(image.src, '_blank');
                        image.style.maxWidth === '100%' ? image.style.maxWidth = '' : image.style.maxWidth = '100%';
                    });
                });
            }

            function formatDate(dateString) {
                const date = new Date(dateString);
                const hours = ('0' + date.getHours()).slice(-2);
                const minutes = ('0' + date.getMinutes()).slice(-2);
                return `${hours}:${minutes}`;
            }

            return content; // 返回内容区域的引用,以便后续添加消息
        } catch (error) {
            console.error('Error creating title and content:', error);
        }
    };

    async function checkLastSeenWithinFiveMinutes(username) {
    try {
         const csrfToken = getCsrfToken();
        // 构建URL
        const url = `https://linux.do/u/${username}/card.json`;

        // 构建请求头
        const headers = new Headers();
        // 添加需要的请求头,例如认证信息等
        headers.append('Accept', 'application/json, text/javascript, */*; q=0.01');
        headers.append('Discourse-Logged-In', 'true');
        headers.append('Discourse-Present', 'true');
        headers.append('Referer', 'https://linux.do');
        headers.append('Sec-Ch-Ua', '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"');
        headers.append('Sec-Ch-Ua-Mobile', '?0');
        headers.append('Sec-Ch-Ua-Platform', '"Windows"');
        headers.append('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36');
        headers.append('X-Csrf-Token', csrfToken);
        headers.append('X-Requested-With', 'XMLHttpRequest');

        // 发送请求
        const response = await fetch(url, {
            method: 'GET',
            headers: headers,
        });
        const userData = await response.json();

        // 解析最后看到的时间
        const lastSeenTime = new Date(userData.user.last_seen_at);
        // 获取当前时间
        const currentTime = new Date();

        // 计算时间差(单位:毫秒)
        const timeDifference = currentTime - lastSeenTime;

        // 将时间差转换成分钟
        const minutesDifference = timeDifference / (1000 * 60);

        // 检查时间差是否小于等于五分钟
        return minutesDifference <= 5;
    } catch (error) {
    }
}




    // 创建输入框和发送按钮
    const createInputAndSendButton = (popup, sendAction) => {
        const inputContainer = createAndAppendElement('div', {
            style: 'display: flex; align-items: center; margin-top: 10px; background-color: #f0f0f0; padding: 8px; border-radius: 10px; justify-content: space-between;', // 已添加 justify-content 用于分隔元素
        }, null, popup);

        // 创建包含图片的按钮,并添加点击事件监听器
        const avatarButton = createAndAppendElement('button', {
            id: 'quick-reply-avatar-button',
            style: 'border: none; background: none; padding: 0;'
        }, null, inputContainer);

        // 创建 img 元素,用于显示头像,并设置为圆形
        const avatarImg = createAndAppendElement('img', {
            id: 'quick-reply-avatar',
            src: reply_avater,
            width: '25',
            height: '25', // 将高度与宽度保持一致以确保正方形
            style: 'border-radius: 50%; object-fit: cover;margin-bottom: 5px;margin-right: 6px;'
        }, null, avatarButton);

        // 添加点击事件监听器
        avatarButton.addEventListener('click', function() {
            // 在点击时更改 reply_avater 的值为新的头像地址
            reply_avater = 'https://cdn.linux.do/uploads/default/original/1X/3a18b4b0da3e8cf96f7eea15241c3d251f28a39b.png';
            // 更新图片的 src 属性为新的头像地址
            avatarImg.src = reply_avater;
            reply_to_post_number= '';
        });


        // 创建 SVG 字符串
        const svgContent = `
         <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="none">
           <path d="M11.286 21.7001C11.286 21.7001 11.2862 21.7004 12 21C12.7138 21.7004 12.7145 21.6997 12.7145 21.6997C12.5264 21.8913 12.2685 22 12 22C11.7315 22 11.474 21.8918 11.286 21.7001Z" fill="#0088cc"/>
           <path fill-rule="evenodd" clip-rule="evenodd" d="M12 7C10.3431 7 9 8.34315 9 10C9 11.6569 10.3431 13 12 13C13.6569 13 15 11.6569 15 10C15 8.34315 13.6569 7 12 7ZM11 10C11 9.44772 11.4477 9 12 9C12.5523 9 13 9.44772 13 10C13 10.5523 12.5523 11 12 11C11.4477 11 11 10.5523 11 10Z" fill="#0088cc"/>
           <path fill-rule="evenodd" clip-rule="evenodd" d="M11.286 21.7001L12 21L12.7145 21.6997L12.7204 21.6937L12.7369 21.6767L12.7986 21.6129C12.8521 21.5574 12.9296 21.4765 13.0277 21.3726C13.2239 21.1649 13.5029 20.8652 13.8371 20.4938C14.5045 19.7523 15.3968 18.7198 16.2916 17.5608C17.1835 16.4056 18.0938 15.104 18.7857 13.8257C19.4617 12.5767 20 11.2239 20 10C20 5.58172 16.4183 2 12 2C7.58172 2 4 5.58172 4 10C4 11.2239 4.53828 12.5767 5.21431 13.8257C5.90617 15.104 6.81655 16.4056 7.70845 17.5608C8.60322 18.7198 9.49555 19.7523 10.1629 20.4938C10.4971 20.8652 10.7761 21.1649 10.9723 21.3726C11.0704 21.4765 11.1479 21.5574 11.2014 21.6129L11.2631 21.6767L11.2796 21.6937L11.286 21.7001ZM6 10C6 6.68629 8.68629 4 12 4C15.3137 4 18 6.68629 18 10C18 10.7091 17.6633 11.6978 17.0268 12.8737C16.4062 14.0204 15.5665 15.2272 14.7084 16.3386C13.8532 17.4464 12.9955 18.4391 12.3504 19.156C12.2249 19.2955 12.1075 19.4244 12 19.5414C11.8925 19.4244 11.7751 19.2955 11.6496 19.156C11.0045 18.4391 10.1468 17.4464 9.29155 16.3386C8.43345 15.2272 7.59383 14.0204 6.9732 12.8737C6.33672 11.6978 6 10.7091 6 10Z" fill="#0088cc"/>
         </svg>
        `;
        // 创建并添加点击按钮
        const iconButton = createAndAppendElement('button', {
            id: 'quick-reply-icon',
            style: 'flex-shrink: 0; background-color: transparent; border: none; cursor: pointer; outline: none; padding: 0; margin-right: 6px'
        }, null, inputContainer);
        iconButton.innerHTML = svgContent;

        // 添加点击事件监听器
        iconButton.addEventListener('click', function() {
            console.log(homeTopic_id);
            // 当前标签页打开网址
            window.location.href = `https://linux.do/t/topic/${homeTopic_id}/${homeNumber}`;
        });

        const refreshButton = createAndAppendElement('button', {
            id: 'quick-reply-refresh',
            style: 'flex-shrink: 0; background-color: transparent; border: none; cursor: pointer; outline: none; padding: 0;margin-right: 8px'
        }, null, inputContainer);
        refreshButton.innerHTML = `
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                   <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#0088cc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
                   <path d="M8.01001 14.5101C8.19001 14.8101 8.41 15.0901 8.66 15.3401C10.5 17.1801 13.49 17.1801 15.34 15.3401C16.09 14.5901 16.52 13.64 16.66 12.67" stroke="#0088cc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
                   <path d="M7.33997 11.3301C7.47997 10.3501 7.90997 9.41003 8.65997 8.66003C10.5 6.82003 13.49 6.82003 15.34 8.66003C15.6 8.92003 15.81 9.20005 15.99 9.49005" stroke="#0088cc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
                   <path d="M7.81995 17.18V14.51H10.4899" stroke="#0088cc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
                   <path d="M16.18 6.82007V9.49005H13.51" stroke="#0088cc" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
               </svg>
        `;


        // 创建文本输入框,调整了一些样式以适应内嵌的布局
        const replyBox = createAndAppendElement('textarea', {
            id: 'quick-reply-box',
            style: 'flex-grow: 1; padding: 10px; background-color: #fff; border: 1px solid #ddd; border-radius: 18px 0 0 18px; resize: none; font-size: 14px; line-height: 1.5; outline: none; height: 40px; margin-right: 0; box-sizing: border-box;' // 调整了边框半径和右边距
        }, null, inputContainer);
        replyBox.style.overflow = 'hidden';

        // 创建发送按钮,调整了一些样式以适应内嵌的布局
        const sendButton = createAndAppendElement('button', {
            id: 'quick-reply-send',
            style: 'background-color: #0088cc; margin-bottom:9px; color: white; border: none; border-radius: 0 18px 18px 0; cursor: pointer; font-size: 14px; line-height: 1; outline: none; padding: 10px 16px 8px 5px; height: 40px; width: 40px;' // 调整了边框半径以匹配输入框
        }, '', inputContainer);

        const sendContent = `
         <svg class="fa d-icon d-icon-paper-plane svg-icon svg-string" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><use href="#paper-plane"></use></svg>
       `;

        sendButton.innerHTML = sendContent;

        sendButton.addEventListener('click', async () => {
            const content = replyBox.value;
            if (content !== null && content.trim() !== '') {
                sendAction(content);
                replyBox.value = '';
            }
        });

        refreshButton.addEventListener('click', async () => {
            event.preventDefault();
            await createTitleAndContent(popup);
        });

        replyBox.addEventListener('keydown', (event) => {
            if (event.ctrlKey && event.key === 'Enter') {
                event.preventDefault();
                sendButton.click();
            }
        });

    };

    // 初始化
    const init = async () => {
        try {
            await getUsername();
            // 创建并设置弹框样式
            const popup = createAndAppendElement('div', { id: 'quick-reply-popup', style: initPopupStyle() }, null, document.body);
            const contentArea = await createTitleAndContent(popup); // 使用 await 等待内容区域创建完成


            // 调用 createInputAndSendButton 方法,并将更新头像的函数传递给它
            createInputAndSendButton(popup, async (message) => {
                // 发送消息
                await sendNewPost(message, homeTopic_id);
                // 重新填充最近回复内容
                await createTitleAndContent(popup);
            });

            const rightMargin = 52; // 按钮距离屏幕右边的距离
            const buttonWidth = 100; // 按钮的宽度
            const buttonHeight = 40; // 按钮的高度
            const bottomOffset = 423; // 顶部偏移量,可根据需要调整

            const buttonWrapper = createAndAppendElement('div', {
                style: `position: fixed;
            bottom: ${bottomOffset}px;
            right: ${rightMargin}px;
            transform: translate(50%, 0);
            z-index: 9999;
            cursor: move;
            width: ${buttonWidth}px;
            height: ${buttonHeight}px;
            display: flex;
            justify-content: center;
            align-items: center;`
            }, null, document.body);

            const openButton = createAndAppendElement('button', {
                id: 'quick-reply-open',
                style: 'padding: 5px 10px; background-color: #0088cc; color: white; border: none; border-radius: 3px; cursor: pointer;'
            }, '最近回复', buttonWrapper);

            addDraggableFeature(buttonWrapper);

            addDraggableFeature(popup);

            openButton.addEventListener('click', async () => {
                const popup = document.getElementById('quick-reply-popup');
                if (popup.style.display === 'none') {
                    popup.style.display = 'block';
                    openButton.textContent = '关闭回复';
                    // 在打开最近回复弹窗时获取数据
                    await createTitleAndContent(popup);
                } else {
                    popup.style.display = 'none';
                    openButton.textContent = '最近回复';
                    const avatarImg = document.getElementById('quick-reply-avatar');
                    // 更新头像的 src 属性
                    avatarImg.src = 'https://cdn.linux.do/uploads/default/original/1X/3a18b4b0da3e8cf96f7eea15241c3d251f28a39b.png';
                }
            });

            setInterval(async () => {
                const replyBox = document.getElementById('quick-reply-box');
                if (!replyBox.value.trim()) {
                    console.log('Refreshing content'); // 添加日志
                    await createTitleAndContent(popup);
                }
            }, 6000);

        } catch (error) {
            console.error('Error initializing script:', error);
        }
    };
    // 调用初始化函数
    init();
})();
感谢@delph1s 佬提供的点赞接口
41 个赞

前排马克

5 个赞

好东西,mark

5 个赞

嘿嘿

7 个赞

好东西,mark

5 个赞

mark

5 个赞

mark

5 个赞

好吧。玩的越来越溜了

5 个赞

快体验一下

7 个赞

mark,三级了再用

5 个赞

可以

5 个赞

体验一下

4 个赞

好好好~建楼神器

4 个赞

mark

4 个赞

挖槽,上电视了

4 个赞

image
这些都不是我回复的或者回复我的啊

4 个赞

围观 :paw_prints:

4 个赞

666,好东西

4 个赞

这是以单个话题为主的,你可以看到你最近回复话题下的回复内容你是想只看回复你的吗

1 个赞

不对呀,我看到的那个话题,最近回复也是16天之前的了,并且那个话题我也没回复