Skip to content

feat: messages.html WebUI for short messages#944

Merged
PythonSmall-Q merged 23 commits intodevfrom
feature/messages-webui
Mar 15, 2026
Merged

feat: messages.html WebUI for short messages#944
PythonSmall-Q merged 23 commits intodevfrom
feature/messages-webui

Conversation

@boomzero
Copy link
Member

@boomzero boomzero commented Mar 15, 2026

关联 Issue

Closes #752

改动内容

  • 新增 messages.html:独立的短消息 WebUI,无需安装用户脚本即可收发站内短消息
  • 修改 index.html:导航栏新增"短消息 WebUI (Alpha)"链接,功能介绍中补充 WebUI 说明

动机与背景

iOS/iPadOS 用户无法安装油猴脚本,无法使用 XMOJ 短消息功能。此页面托管于同一 Cloudflare Pages 站点(xmoj-bbs.me),让所有浏览器均可直接使用。

此前已有两次尝试(Copilot PR #941、Codex PR #942/#943),但设计或正确性不满意,本 PR 从 dev 重新实现。

messages.html 功能

技术栈

  • Bootstrap 5.3.3(bootcdn,与脚本同源),支持 data-bs-theme 自动深/浅色模式
  • marked 9.1.6 + DOMPurify 3.0.6(bootcdn)渲染消息中的 Markdown 内容
  • 无构建步骤,单 HTML 文件

登录

  • 书签登录(推荐):拖拽书签到浏览器书签栏,在 xmoj.tech 登录后点击书签,自动跳转并完成认证(从 Cookie 读取 PHPSESSID,从 #profile 读取用户名,通过 URL hash 传回)
  • 手动输入:直接填写用户名和 PHPSESSID

消息列表页

  • Bootstrap table-hover table-borderless 表格,列:用户 / 最新消息预览 / 时间
  • 未读对话高亮(table-primary),未读数显示红色 badge
  • Bootstrap collapse 新消息面板

对话页

  • 表格展示单条消息,列:发送者 / 内容(Markdown 渲染)/ 时间 / 状态
  • 未读消息高亮(table-primary
  • 底部粘性编辑区:Ctrl+Enter 发送,图片上传(文件选择 + 粘贴)
  • 每 10 秒自动刷新,页面隐藏时暂停

其他

  • 图片点击放大(Bootstrap modal-xl)
  • Toast 通知
  • 主题切换:自动 / 深色 / 浅色,持久化到 localStorage

测试计划

  • python3 -m http.server 8080 启动本地服务,访问 http://localhost:8080/messages.html
  • 登录页两个 tab 正常渲染,深色模式随系统切换
  • 书签代码可生成、可复制,无 JS 报错
  • 手动输入真实账号后进入消息列表页,消息可正常加载
  • 点击对话进入消息页,可发送消息,10 秒自动刷新
  • 移动端宽度下表格横向滚动,编辑区保持粘性底部
  • index.html 导航栏新链接正常跳转

Summary by Sourcery

Add a standalone web-based UI for XMOJ short messages and surface it from the main page for environments that cannot use user scripts.

New Features:

  • Introduce messages.html as a single-page WebUI for viewing and sending XMOJ short messages without requiring user scripts, including login, inbox, conversation view, Markdown rendering, image upload, auto-refresh, and theme switching.

Documentation:

  • Update index.html to link to the new short messages WebUI from the navbar and feature list, documenting it as an alternative for devices that cannot install user scripts.

Summary by cubic

Adds a standalone short‑messages WebUI at messages.html so browsers without userscripts (e.g., iOS/iPadOS Safari) can read and send XMOJ messages. Updates the main page link and wording to surface it.

  • New Features

    • Single‑file WebUI (messages.html, no build) using Bootstrap 5.3.3, marked@9.1.6, and DOMPurify@3.0.6 from cdnjs with auto light/dark theme.
    • Login: supports session (PHPSESSID) with per‑browser steps by default, plus a bookmarklet option for desktop.
    • Inbox/thread: unread badges, Markdown, image upload (choose/paste), sticky composer with Ctrl+Enter and auto‑resize, user badges and a user info modal, local contact search, image zoom with zoom cursor, auto‑scroll to latest, and 10s auto‑refresh paused when the tab is hidden. Update index.html to add the messages link and adjust its label.
  • Bug Fixes

    • Include DebugMode in API requests to avoid backend errors.
    • Show read status based on IsRead; keep row highlight only for incoming unread. Sort thread messages oldest‑first so the newest is at the bottom.
    • Fix dark‑mode table colors; include year for older timestamps; set Referer + credentials when fetching user info.
    • Restore features lost in a bad rebase. Make mail list rows clearly clickable with a caption and chevron; correct the loading row colspan to 4. Blur image before closing the viewer modal to avoid the aria‑hidden focus warning.
    • Switch index.html Bootstrap CDN to cdnjs; update page/link text for clarity.

Written for commit 7dd281c. Summary will update on new commits.

Adds a standalone messages.html page hosted on the Cloudflare Pages site,
allowing iOS/iPadOS users (and anyone without userscript support) to read
and send XMOJ short messages via the existing API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 15, 2026

Reviewer's Guide

Adds a new standalone messages.html WebUI for XMOJ short messages (including login, inbox, conversations, markdown rendering, image upload, theming, and auto-refresh) and wires it into the existing index.html navigation and feature description links.

Sequence diagram for bookmarklet-based login and authentication

sequenceDiagram
  actor User
  participant Browser
  participant XMOJSite as XMOJ_site
  participant MessagesPage as Messages_html
  participant Api as XMOJBBS_API

  User->>Browser: Open XMOJ_site and log in
  Browser->>XMOJSite: Request user pages
  XMOJSite-->>Browser: HTML with profile and PHPSESSID cookie

  User->>Browser: Click bookmarklet
  Browser->>Browser: Bookmarklet reads PHPSESSID from cookie
  Browser->>Browser: Bookmarklet reads username from element profile
  Browser->>Browser: Build redirect URL to messages_html with session hash
  Browser->>MessagesPage: Navigate to messages_html#session=username:phpsessid

  MessagesPage->>MessagesPage: Init script runs
  MessagesPage->>MessagesPage: checkSessionHash parses hash
  MessagesPage->>Browser: Save username and phpsessid to localStorage
  MessagesPage->>Browser: Replace URL hash with clean path
  MessagesPage->>MessagesPage: onLoggedIn show screen_list and navbar user

  MessagesPage->>Api: POST GetMailList(Authentication with username, phpsessid)
  Api-->>MessagesPage: MailList data
  MessagesPage->>Browser: Render inbox with threads and unread badges
Loading

Flow diagram for messages.html screen routing and auto-refresh

flowchart TD
  A_init["Init messages_html script"] --> B_checkHash["checkSessionHash"]
  B_checkHash -->|Hash_valid| C_saveSession["Save session to localStorage and set currentUser"]
  B_checkHash -->|No_or_invalid_hash| D_loadSession["loadSession from localStorage"]

  C_saveSession --> E_onLoggedIn["onLoggedIn: update navbar and show screen_list"]
  D_loadSession -->|Found_session| E_onLoggedIn
  D_loadSession -->|No_session| F_showLogin["showScreen(screen_login)"]

  E_onLoggedIn --> G_loadMailList["loadMailList via GetMailList"]
  G_loadMailList --> H_inboxRendered["Render inbox table and click handlers"]

  H_inboxRendered --> I_openThread["openThread(otherUser)"]
  I_openThread --> J_setThreadState["Set currentThread and isFirstLoad"]
  J_setThreadState --> K_showThread["showScreen(screen_thread)"]
  K_showThread --> L_startRefresh["startRefresh setInterval(loadThread)"]
  K_showThread --> M_loadThreadOnce["loadThread via GetMail"]

  M_loadThreadOnce --> N_renderMessages["Render messages, bind image and link handlers"]
  N_renderMessages --> O_scrollLogic["Scroll to bottom on first load or near bottom"]

  subgraph Auto_refresh_and_visibility
    L_startRefresh --> P_intervalTick["Interval: loadThread every 10s"]
    P_intervalTick --> M_loadThreadOnce
    Q_visibilityChange["document.visibilitychange"] -->|hidden| R_stopRefresh["stopRefresh clearInterval"]
    Q_visibilityChange -->|visible_and_currentThread| S_resume["loadThread and startRefresh"]
    S_resume --> M_loadThreadOnce
    S_resume --> L_startRefresh
  end

  subgraph Navigation_and_logout
    T_backButton["Back button from thread"] --> R_stopRefresh
    T_backButton --> G_loadMailList
    T_backButton --> H_inboxRendered

    U_logoutButton["Logout button"] --> V_clearSession["Remove session from localStorage"]
    V_clearSession --> R_stopRefresh
    V_clearSession --> F_showLogin
  end
Loading

File-Level Changes

Change Details Files
Expose the new short-message WebUI from the main landing page.
  • Add a navbar link labeled 短消息 WebUI (Alpha) in the main navigation pointing to messages.html.
  • Extend the 短消息 feature description to mention the standalone WebUI and link to messages.html for devices without user-script support.
index.html
Implement messages.html as a single-page Bootstrap 5 WebUI for viewing and sending short messages via the existing XMOJ-BBS backend APIs.
  • Set up page layout with a fixed-top navbar, login screen, inbox (mail list), thread view with sticky compose area, image modal viewer, and toast container.
  • Implement theme initialization and switching (auto/dark/light) with persistence in localStorage and integration with Bootstrap’s data-bs-theme and prefers-color-scheme.
  • Implement session handling using localStorage-stored username and PHPSESSID, including login state bootstrap, logout, and navigation between login/list/thread screens.
  • Add two login modes: bookmarklet-based login that reads PHPSESSID and username from xmoj.tech and passes them back via URL hash, and manual login where the user inputs username and PHPSESSID.
  • Implement API abstraction around the XMOJ-BBS backend (GetMailList, GetMail, SendMail, UploadImage, ReadUserMailMention) using fetch with JSON bodies including Authentication and Version metadata.
  • Render the inbox as a responsive, hoverable, borderless table with unread highlighting, unread-count badges, Markdown-preview stripping, and per-conversation click navigation to thread view.
  • Render conversation threads with Markdown -> HTML via marked + DOMPurify, including link rewriting (new tab), read/unread status indication, and scroll-to-bottom behavior while preserving user scroll when appropriate.
  • Provide message composition flows from both the inbox (new conversation) and thread view, with disabled/“sending” states, error handling via toast notifications, and Ctrl+Enter shortcut sending in the thread view.
  • Implement image upload in threads via file input and paste events, including MIME and size validation, upload to UploadImage as a data URL, and auto-insertion of Markdown image links referencing ASSET_BASE URLs.
  • Add periodic auto-refresh of the thread every 10 seconds, pausing when the tab is hidden and resuming when visible, plus manual refresh buttons for both list and thread.
  • Implement bookmarklet code generation and clipboard copy button; the bookmarklet extracts PHPSESSID and username from xmoj.tech and redirects back to messages.html with a #session= hash, which is parsed and stored as a session on load.
messages.html

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@hendragon-bot hendragon-bot bot added the website This issue or pull request is related to website related files label Mar 15, 2026
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The API client currently only checks for HTTP-level failures; consider standardizing and handling logical errors returned in the JSON payload (e.g., a success flag or error field) so the UI can surface backend error messages consistently instead of assuming a successful shape for result.Data.
  • PHPSESSID is stored in localStorage and reused indefinitely; consider adding an explicit session expiry/refresh mechanism or an easy one-click way to clear credentials (besides full logout), and re-evaluate whether any additional XSS hardening around Markdown rendering is needed given that stealing this token compromises the user’s XMOJ session.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The API client currently only checks for HTTP-level failures; consider standardizing and handling logical errors returned in the JSON payload (e.g., a success flag or error field) so the UI can surface backend error messages consistently instead of assuming a successful shape for `result.Data`.
- PHPSESSID is stored in `localStorage` and reused indefinitely; consider adding an explicit session expiry/refresh mechanism or an easy one-click way to clear credentials (besides full logout), and re-evaluate whether any additional XSS hardening around Markdown rendering is needed given that stealing this token compromises the user’s XMOJ session.

## Individual Comments

### Comment 1
<location path="messages.html" line_range="532-541" />
<code_context>
+    document.getElementById('upload-indicator').textContent = msg;
+}
+
+async function uploadImageData(dataUrl) {
+    var textarea = document.getElementById('thread-compose');
+    var placeholder = '![上传中…]()';
+    textarea.value += '\n' + placeholder;
+    setUploadIndicator('图片上传中…');
+    try {
+        var result = await apiCall('UploadImage', { Image: dataUrl });
+        var imageId = result && result.Data && result.Data.ImageID;
+        if (!imageId) throw new Error('未获取到 ImageID');
+        var mdImg = '![图片](' + ASSET_BASE + imageId + ')';
+        textarea.value = textarea.value.replace(placeholder, mdImg);
+        setUploadIndicator('');
+        showToast('图片上传成功', 'success');
+    } catch (err) {
+        textarea.value = textarea.value.replace('\n' + placeholder, '');
+        setUploadIndicator('');
+        showToast('图片上传失败:' + err.message, 'danger');
</code_context>
<issue_to_address>
**issue (bug_risk):** Handling the markdown placeholder via global string replacement can misbehave with multiple or edited uploads.

Because this relies on a fixed placeholder and a global `replace`, concurrent uploads or user edits before completion can cause the wrong occurrence to be replaced or extra text to be removed. Consider storing the insertion range for each upload (e.g., `selectionStart`/`selectionEnd`) and updating that slice, or generating a unique placeholder per upload and replacing only that token.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +532 to +541
async function uploadImageData(dataUrl) {
var textarea = document.getElementById('thread-compose');
var placeholder = '![上传中…]()';
textarea.value += '\n' + placeholder;
setUploadIndicator('图片上传中…');
try {
var result = await apiCall('UploadImage', { Image: dataUrl });
var imageId = result && result.Data && result.Data.ImageID;
if (!imageId) throw new Error('未获取到 ImageID');
var mdImg = '![图片](' + ASSET_BASE + imageId + ')';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Handling the markdown placeholder via global string replacement can misbehave with multiple or edited uploads.

Because this relies on a fixed placeholder and a global replace, concurrent uploads or user edits before completion can cause the wrong occurrence to be replaced or extra text to be removed. Consider storing the insertion range for each upload (e.g., selectionStart/selectionEnd) and updating that slice, or generating a unique placeholder per upload and replacing only that token.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21b5e4e643

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +383 to +384
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate API-level failure responses before returning

The new apiCall only checks HTTP status and returns JSON directly, but this backend also reports errors as Success: false in a 200 response (the existing userscript checks ResponseData.Success for all mail actions). In this UI, that means expired PHPSESSID, invalid recipients, or other API rejections are treated as successful operations (for example SendMail clears the compose box and GetMailList can look like an empty inbox), which can silently drop user actions. Please convert Success: false into a thrown error using the API message so callers can handle failures.

Useful? React with 👍 / 👎.

boomzero and others added 2 commits March 15, 2026 16:31
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 15, 2026

Deploying xmoj-script-dev-channel with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7dd281c
Status: ✅  Deploy successful!
Preview URL: https://e4eab461.xmoj-script-dev-channel.pages.dev
Branch Preview URL: https://feature-messages-webui.xmoj-script-dev-channel.pages.dev

View logs

</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
@PythonSmall-Q
Copy link
Member

等等,我要看一下

… instructions

Bookmarklets are not usable on mobile and confusing on desktop.
Make the session (PHPSESSID) tab the default with an accordion of
step-by-step instructions for Chrome, Firefox, Safari (macOS), and
iOS/iPadOS. Bookmarklet tab kept as secondary option for desktop users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="messages.html">

<violation number="1" location="messages.html:384">
P1: Throw when the backend returns `Success: false`; callers currently treat API failures as successful responses.</violation>

<violation number="2" location="messages.html:444">
P2: Capture the selected thread before awaiting here; a stale response can overwrite the conversation the user is currently viewing.</violation>

<violation number="3" location="messages.html:534">
P2: Use a unique placeholder per upload; identical markers let concurrent uploads replace each other out of order.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

})
});
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Throw when the backend returns Success: false; callers currently treat API failures as successful responses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 384:

<comment>Throw when the backend returns `Success: false`; callers currently treat API failures as successful responses.</comment>

<file context>
@@ -0,0 +1,710 @@
+        })
+    });
+    if (!res.ok) throw new Error('HTTP ' + res.status);
+    return res.json();
+}
+
</file context>
Fix with Cubic


async function uploadImageData(dataUrl) {
var textarea = document.getElementById('thread-compose');
var placeholder = '![上传中…]()';
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Use a unique placeholder per upload; identical markers let concurrent uploads replace each other out of order.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 534:

<comment>Use a unique placeholder per upload; identical markers let concurrent uploads replace each other out of order.</comment>

<file context>
@@ -0,0 +1,710 @@
+
+async function uploadImageData(dataUrl) {
+    var textarea = document.getElementById('thread-compose');
+    var placeholder = '![上传中…]()';
+    textarea.value += '\n' + placeholder;
+    setUploadIndicator('图片上传中…');
</file context>
Suggested change
var placeholder = '![上传中…]()';
var placeholder = '![上传中… ' + Date.now().toString(36) + Math.random().toString(36).slice(2) + ']()';
Fix with Cubic

var atBottom = isFirstLoad ||
(scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < SCROLL_THRESHOLD);
try {
var result = await apiCall('GetMail', { OtherUser: currentThread });
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Capture the selected thread before awaiting here; a stale response can overwrite the conversation the user is currently viewing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 444:

<comment>Capture the selected thread before awaiting here; a stale response can overwrite the conversation the user is currently viewing.</comment>

<file context>
@@ -0,0 +1,710 @@
+    var atBottom = isFirstLoad ||
+        (scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < SCROLL_THRESHOLD);
+    try {
+        var result = await apiCall('GetMail', { OtherUser: currentThread });
+        var mails = result && result.Data && result.Data.Mail;
+        var tbody = document.getElementById('thread-tbody');
</file context>
Fix with Cubic

boomzero and others added 2 commits March 15, 2026 16:44
API returns "参数DebugMode未找到" without it, causing all calls to fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status badge (未读/已读) now reflects IsRead directly.
Row highlight (table-primary) still only applies to incoming unread messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@PythonSmall-Q
Copy link
Member

@copilot 如果检测到用户是桌面端就优先显示书签登陆方式,移动端优先显示会话登录;对话内容和现在的顺序反一下,确保最新的内容在最下面

Copy link
Contributor

Copilot AI commented Mar 15, 2026

@PythonSmall-Q I've opened a new pull request, #945, to work on those changes. Once the pull request is ready, I'll request review from you.

boomzero and others added 2 commits March 15, 2026 16:57
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@PythonSmall-Q
Copy link
Member

@copilot 如果检测到用户是桌面端就优先显示书签登陆方式,移动端优先显示会话登录;发送者的名字不要是链接,而是点击名字之后从地址获取信息作为一个popup然后展示出来;图片支持放大(参照script已有功能);检测ctrl+v内容,如果为图片则上传(逻辑和现在页面上有的相同);优化用户体验

Copy link
Contributor

Copilot AI commented Mar 15, 2026

@PythonSmall-Q I've opened a new pull request, #946, to work on those changes. Once the pull request is ready, I'll request review from you.

<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
Copilot AI and others added 4 commits March 15, 2026 09:07
- Desktop/mobile login tab priority: bookmarklet on desktop (pointer: fine), session on mobile
- Replace sender name links with Bootstrap popovers showing user info popup
- Image zoom: cursor: zoom-in/zoom-out, title tooltip on images
- Global Ctrl+V paste handler for images anywhere in thread view
- Auto-resize compose textarea as user types

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
…nfo modal

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Copilot AI and others added 4 commits March 15, 2026 09:41
…in userinfo fetch

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
…ck + auto-scroll fix

Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com>
feat(messages.html): user badges, contact search, auto-scroll to latest
Add caption "点击任意行打开对话 →" above table and a › chevron
in a trailing column on each row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="messages.html">

<violation number="1" location="messages.html:408">
P3: Include the year for older timestamps; otherwise prior-year messages render ambiguously.</violation>

<violation number="2" location="messages.html:578">
P2: Re-scroll after inline images load; one immediate `scrollTop` update does not keep image threads pinned to the latest message.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Restored: contact search, renderMailList, mailListCache, user badges,
user info modal, userSpan, auto-resize textarea, global paste handler,
requestAnimationFrame scroll, setLoginTab, dark-mode table-primary fix.

Added on top: caption "点击任意行打开对话 →" and › chevron column
to make list rows obviously clickable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="messages.html">

<violation number="1" location="messages.html:386">
P2: Clear `mailListCache` when sessions change; otherwise the search box can expose the previous account's cached contacts.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

let currentThread = null; // other user's username
let refreshTimer = null;
let isFirstLoad = true;
let mailListCache = []; // last fetched mail list (for local search)
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Clear mailListCache when sessions change; otherwise the search box can expose the previous account's cached contacts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 386:

<comment>Clear `mailListCache` when sessions change; otherwise the search box can expose the previous account's cached contacts.</comment>

<file context>
@@ -348,12 +383,14 @@ <h5 class="mb-0 flex-grow-1" id="thread-title">与 … 的对话</h5>
 let currentThread  = null;   // other user's username
 let refreshTimer   = null;
 let isFirstLoad    = true;
+let mailListCache  = [];     // last fetched mail list (for local search)
 
 // ── Bootstrap instances ────────────────────────────────────────────────────
</file context>
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="messages.html">

<violation number="1" location="messages.html:515">
P2: Guard the async modal update so a slower earlier click cannot overwrite the currently selected user's info.</violation>

<violation number="2" location="messages.html:516">
P2: This cross-origin profile fetch is blocked by CORS, so the new user-info modal never receives the HTML it needs.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

body.innerHTML = '<div class="text-center py-3"><span class="spinner-border spinner-border-sm me-2"></span>加载中…</div>';
userInfoModalBS.show();

var results = await Promise.allSettled([
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard the async modal update so a slower earlier click cannot overwrite the currently selected user's info.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 515:

<comment>Guard the async modal update so a slower earlier click cannot overwrite the currently selected user's info.</comment>

<file context>
@@ -399,18 +436,153 @@ <h5 class="mb-0 flex-grow-1" id="thread-title">与 … 的对话</h5>
+    body.innerHTML = '<div class="text-center py-3"><span class="spinner-border spinner-border-sm me-2"></span>加载中…</div>';
+    userInfoModalBS.show();
+
+    var results = await Promise.allSettled([
+        fetch(profileUrl, { referrer: XMOJ_BASE + '/', credentials: 'include' })
+            .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); }),
</file context>
Fix with Cubic

userInfoModalBS.show();

var results = await Promise.allSettled([
fetch(profileUrl, { referrer: XMOJ_BASE + '/', credentials: 'include' })
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This cross-origin profile fetch is blocked by CORS, so the new user-info modal never receives the HTML it needs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages.html, line 516:

<comment>This cross-origin profile fetch is blocked by CORS, so the new user-info modal never receives the HTML it needs.</comment>

<file context>
@@ -399,18 +436,153 @@ <h5 class="mb-0 flex-grow-1" id="thread-title">与 … 的对话</h5>
+    userInfoModalBS.show();
+
+    var results = await Promise.allSettled([
+        fetch(profileUrl, { referrer: XMOJ_BASE + '/', credentials: 'include' })
+            .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); }),
+        getUserBadge(username)
</file context>
Fix with Cubic

Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
@PythonSmall-Q PythonSmall-Q merged commit 3ea071d into dev Mar 15, 2026
11 checks passed
@PythonSmall-Q PythonSmall-Q deleted the feature/messages-webui branch March 15, 2026 10:43
@PythonSmall-Q PythonSmall-Q linked an issue Mar 22, 2026 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL website This issue or pull request is related to website related files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] WebUI for short message viewing

3 participants