diff --git a/TeXmacs/misc/themes/liii-night.css b/TeXmacs/misc/themes/liii-night.css index 9cb40e0a33..50f444c38a 100644 --- a/TeXmacs/misc/themes/liii-night.css +++ b/TeXmacs/misc/themes/liii-night.css @@ -925,6 +925,68 @@ QWidget#auxiliary_container { background-color: rgba(255, 255, 255, 0.2); } +/**************************************************************************** +* 版本更新提示条样式 +****************************************************************************/ +#updateNotificationBar { + background-color: #0d2b4a; + border: 1px solid #1a4a7a; + border-left: none; + border-right: none; + border-top: none; +} + +#updateNotificationMessage { + color: #64b5f6; + font-size: 13px; + font-weight: 500; +} + +#updateNotificationUpdateButton { + background-color: #1976d2; + color: white; + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 12px; + font-weight: 500; +} + +#updateNotificationUpdateButton:hover { + background-color: #1565c0; +} + +#updateNotificationSnoozeButton { + background-color: transparent; + border: 1px solid #4a7a9a; + color: #64b5f6; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +#updateNotificationSnoozeButton:hover { + background-color: #1a3a5a; +} + +#updateNotificationCloseButton { + background-color: transparent; + color: #64b5f6; + border: none; + font-size: 16px; + font-weight: bold; + border-radius: 4px; + padding: 0; + margin: 0; + min-width: 24px; + min-height: 24px; +} + +#updateNotificationCloseButton:hover { + background-color: rgba(100, 181, 246, 0.1); +} + /**************************************************************************** * 文档编辑区域样式 ****************************************************************************/ diff --git a/TeXmacs/misc/themes/liii.css b/TeXmacs/misc/themes/liii.css index 608d206c23..ef02a1cecc 100644 --- a/TeXmacs/misc/themes/liii.css +++ b/TeXmacs/misc/themes/liii.css @@ -893,6 +893,68 @@ QWidget#auxiliary_container { background-color: rgba(0, 0, 0, 0.2); } +/**************************************************************************** +* 版本更新提示条样式 +****************************************************************************/ +#updateNotificationBar { + background-color: #e3f2fd; + border: 1px solid #bbdefb; + border-left: none; + border-right: none; + border-top: none; +} + +#updateNotificationMessage { + color: #1565c0; + font-size: 13px; + font-weight: 500; +} + +#updateNotificationUpdateButton { + background-color: #1976d2; + color: white; + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 12px; + font-weight: 500; +} + +#updateNotificationUpdateButton:hover { + background-color: #1565c0; +} + +#updateNotificationSnoozeButton { + background-color: transparent; + border: 1px solid #90caf9; + color: #1976d2; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +#updateNotificationSnoozeButton:hover { + background-color: #e3f2fd; +} + +#updateNotificationCloseButton { + background-color: transparent; + color: #1565c0; + border: none; + font-size: 16px; + font-weight: bold; + border-radius: 4px; + padding: 0; + margin: 0; + min-width: 24px; + min-height: 24px; +} + +#updateNotificationCloseButton:hover { + background-color: rgba(21, 101, 192, 0.1); +} + /*文本工具栏窗口样式*/ QWidget#text_toolbar { background: #ffffff; diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 04d1ad03f2..71ed0b97b2 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -501,6 +501,7 @@ ("current graphical mode" "当前绘图模式") ("current user version" "当前用户版本") ("current version" "当前版本") +("current" "当前") ("cursor" "光标") ("cursors" "游标") ("curve intersections" "曲线交点") @@ -1493,6 +1494,7 @@ ("new row" "") ("new table" "") ("new version" "新版") +("New version available" "发现新版本") ("new window" "新窗口") ("new" "新建") ("newer version" "新版") @@ -1887,6 +1889,7 @@ ("Renew" "续费") ("Renew Early" "提前续费") ("Renew Now" "续费会员") +("Remind later" "稍后提醒") ("rendering" "渲染") ("renumber this page" "修改当前页码") ("repeat object" "") @@ -2397,6 +2400,7 @@ ("up" "") ("up" "上") ("Update buffer" "更新缓冲区") +("Update now" "立即更新") ("update from web" "") ("update image links" "") ("update this buffer" "更新此文档") @@ -2559,4 +2563,4 @@ ("zoom/unzoom" "放大/缩小") ("You are currently in guest mode, login to enable AI, MathOCR,and other features" "您当前处于访客状态,登录激活AI和公式识别等功能") ("Login Now" "立即登录") -("Use extensible brackets" "使用可伸缩括号") \ No newline at end of file +("Use extensible brackets" "使用可伸缩括号") diff --git a/TeXmacs/progs/utils/misc/version-update.scm b/TeXmacs/progs/utils/misc/version-update.scm new file mode 100644 index 0000000000..3f48dfe5bb --- /dev/null +++ b/TeXmacs/progs/utils/misc/version-update.scm @@ -0,0 +1,85 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : version-update.scm +;; DESCRIPTION : 版本更新检查(开发者配置) +;; COPYRIGHT : (C) 2026 Mogan STEM authors +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(texmacs-module (utils misc version-update)) + +;; ============================================ +;; 开发者配置区(修改此处调整行为) +;; ============================================ +(define SNOOZE-DAYS 3) ; 稍后提醒间隔,单位:天 + +;; Mock 远程版本号(用于测试,设为 #f 则使用真实网络请求) +;; 示例:(define MOCK-REMOTE-VERSION "2026.3.0") +(define MOCK-REMOTE-VERSION #f) + +;; ============================================ +;; 内部实现 +;; ============================================ + +;; 获取 Mock 远程版本号(优先从持久化存储读取) +(tm-define (get-mock-remote-version) + (:secure #t) + (let ((stored (persistent-get (get-texmacs-home-path) MOCK-VERSION-KEY))) + (if (and stored (!= stored "")) + stored + MOCK-REMOTE-VERSION))) + +;; 设置 Mock 远程版本号(同时保存到持久化存储) +(tm-define (set-mock-remote-version! version) + (:secure #t) + (set! MOCK-REMOTE-VERSION version) + (persistent-set (get-texmacs-home-path) MOCK-VERSION-KEY version)) + +;; 清除 Mock 远程版本号(恢复默认 #f) +(tm-define (clear-mock-remote-version) + (:secure #t) + (set! MOCK-REMOTE-VERSION #f) + (persistent-remove (get-texmacs-home-path) MOCK-VERSION-KEY)) + +(define LAST-CHECK-KEY "version_last_check") +(define SNOOZE-UNTIL-KEY "version_snooze_until") +(define MOCK-VERSION-KEY "version_mock_remote") + +(define (current-timestamp) + (current-time)) + +;; 检查是否应该检查更新(考虑稍后提醒时间) +(tm-define (should-check-version-update?) + (:secure #t) + (let* ((now (current-timestamp)) + (snooze-until (or (persistent-get (get-texmacs-home-path) SNOOZE-UNTIL-KEY) "0")) + (snooze-time (if (== snooze-until "") 0 (string->number snooze-until)))) + (>= now snooze-time))) + +;; 强制清除所有记录(用于测试) +(tm-define (clear-version-update-history) + (:secure #t) + (persistent-remove (get-texmacs-home-path) SNOOZE-UNTIL-KEY) + (clear-mock-remote-version)) + +;; 稍后提醒(使用默认间隔) +(tm-define (snooze-version-update) + (:secure #t) + (let* ((now (current-timestamp)) + (future (+ now (* SNOOZE-DAYS 24 3600)))) + (persistent-set (get-texmacs-home-path) SNOOZE-UNTIL-KEY + (number->string future)))) + +;; 获取下载页URL +;; 社区版跳转到 mogan.app,商业版跳转到 liiistem.cn/com +(tm-define (get-update-download-url) + (:secure #t) + (if (community-stem?) + ;; 社区版官网 + (if (== (get-output-language) "chinese") + "https://mogan.app/zh/" + "https://mogan.app/en/") + ;; 商业版官网 + (if (== (get-output-language) "chinese") + "https://liiistem.cn/install.html" + "https://liiistem.com/install.html"))) diff --git a/devel/202_112.md b/devel/202_112.md new file mode 100644 index 0000000000..27cb4d6842 --- /dev/null +++ b/devel/202_112.md @@ -0,0 +1,159 @@ +# 202_112 版本更新提示功能 + +## 2026年3月26日 + +### 如何测试 + +#### 步骤1:设置 Mock 版本 +1. 因为测试版本一般都是最新的,测试版本号比远程获取的大,所以需要模拟一个更大的远程版本,在 Scheme 会话中执行: +```scheme +(set-mock-remote-version! "2026.3.0") +``` + +#### 步骤2:重启应用&检查功能 +1. 重启应用后,等待10秒后查看提示条是否显示 +2. 点击立即更新,看是否跳转到mogan 官网(社区版跳转到 mogan 官网,商业版跳转到 liii stem 官网) +3. 点击稍后提示,看提示条是否消失(稍后提示 3 天后再提醒) + +#### 步骤3:再次重启应用&清除记录 +1. 重启应用,等待 10 秒后查看是否无提示条显示(上面点击了稍后提醒,不应该显示) +```scheme +;; 查看存储的稍后提醒时间戳(Unix时间) +(persistent-get (get-texmacs-home-path) "version_snooze_until") +``` +2. 清除mock remote version 的设定和稍后提示的缓存标记 +```scheme +(clear-version-update-history) +``` + +### 测试要点 +- 新版本可用时正确显示提示条 +- 点击"立即更新"打开浏览器,提示条不消失 +- 点击"稍后提醒"3天后再次提示 +- 版本号比较正确(如 2026.10.0 > 2026.2.0) +- 社区版和商业版都显示版本更新提示 +- 社区版跳转到 mogan.app,商业版跳转到 liiistem.cn/com + +--- + +### What +实现版本更新提示功能,当有新版本可用时,在窗口顶部显示提示条,引导用户更新到最新版本。 + +### 功能特性 +1. **自动检查版本** - 启动后延迟10秒自动检查远程版本 +2. **语义化版本比较** - 正确比较版本号(如 2026.10.0 > 2026.2.0) +3. **多语言支持** - 根据语言环境显示不同官网链接 +4. **双版本支持** - 社区版和商业版都显示版本更新提示,但跳转到不同官网 + +### 用户操作按钮 +- **立即更新** - 打开浏览器跳转到官网下载页,提示条保持显示 +- **稍后提醒** - 3天后(开发者可配置)再次提示 +- **关闭** - 仅隐藏当前会话 + +### 新增文件 +``` +src/Plugins/QWindowKit/updatenotificationbar.hpp +src/Plugins/QWindowKit/updatenotificationbar.cpp +TeXmacs/progs/utils/misc/version-update.scm +``` + +### 修改文件 +``` +src/Plugins/Qt/qt_tm_widget.hpp +src/Plugins/Qt/qt_tm_widget.cpp +TeXmacs/misc/themes/liii.css +TeXmacs/misc/themes/liii-night.css +TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +``` + +### 开发者配置 +在 `TeXmacs/progs/utils/misc/version-update.scm` 中: + +```scheme +;; 配置稍后提醒间隔(单位:天) +(define SNOOZE-DAYS 3) +``` + +### Scheme 控制接口 +```scheme +(use-modules (utils misc version-update)) + +;; 检查是否应该检查更新(设置了稍后提醒不检查更新,否则检查更新) +(should-check-version-update?) + +;; Mock 版本控制(用于测试,持久化存储,重启后生效) +(get-mock-remote-version) ; 查看当前 mock 版本 +(set-mock-remote-version! "2026.3.0") ; 设置 mock 版本,重启后生效 +(clear-mock-remote-version) ; 清除 mock 版本,恢复真实网络请求 + +;; 稍后提醒 +(snooze-version-update) + +;; 清除所有记录(包括 mock 版本) +(clear-version-update-history) +``` + +### UI 布局 +版本更新提示条和登录提示条垂直堆叠显示: +``` +┌─────────────────────────────────────────┐ +│ 🎉 发现新版本 v2026.3.0(当前 v2026.2.1)│ +│ [立即更新] [稍后提醒] [×] │ +├─────────────────────────────────────────┤ +│ 访客登录提示条(如有) │ +│ [立即登录] [×] │ +└─────────────────────────────────────────┘ +``` + +### 官网链接 + +| 版本 | 中文 | 英文 | +|------|------|------| +| 社区版 | `https://mogan.app/zh/` | `https://mogan.app/en/` | +| 商业版 | `https://liiistem.cn/install.html` | `https://liiistem.com/install.html` | + +### 记录存储位置 +| 平台 | 路径 | +|------|------| +| macOS | `~/Library/Application Support/moganlab/system/_/` | +| Linux | `~/.local/share/moganlab/system/_/` | +| Windows | `%APPDATA%/moganlab/system/_/` | + +### 提示显示判定逻辑 + +版本更新提示条的显示需要满足以下条件(按顺序检查): + +``` +应用启动 + ↓ +延迟10秒 + ↓ +检查稍后提醒时间 ──否──→ 结束(不显示) + ↓ 是(当前时间 > 设置的稍后提醒时间) +获取远程版本号(Mock优先) + ↓ +语义化版本比较(只比较前三位数字) + ↓ +远程版本 > 本地版本 ──否──→ 结束(不显示) + ↓ 是 +显示提示条 +``` + +**关键判定条件:** + +| 检查项 | 说明 | Scheme变量/函数 | +|--------|------|-----------------| +| 稍后提醒时间 | 用户点击"稍后提醒"后记录的时间戳 | `version_snooze_until` | +| 版本号比较 | 取前三位数字比较,如 `2026.10.0` vs `2026.2.1` | `isVersionNewer()` | + +**注意:** 目前没有"跳过此版本"功能,用户只能选择"稍后提醒"或保持提示条显示。 + +### Why +帮助用户及时获取最新版本,提升用户体验和软件安全性。 + +### How +1. 启动后延迟10秒检查版本 +2. Mock 版本优先于网络请求(用于测试) +3. 语义化版本号比较(取前三位数字) +4. 社区版和商业版都显示版本更新提示,根据版本类型跳转到不同官网 +5. 用户操作后记录状态到本地持久化存储 diff --git a/src/Plugins/QWindowKit/updatenotificationbar.cpp b/src/Plugins/QWindowKit/updatenotificationbar.cpp new file mode 100644 index 0000000000..c5cf615f10 --- /dev/null +++ b/src/Plugins/QWindowKit/updatenotificationbar.cpp @@ -0,0 +1,99 @@ +/*****************************************************************************/ +/* MODULE : updatenotificationbar.cpp */ +/* DESCRIPTION : Version update notification bar (horizontal layout) */ +/* COPYRIGHT : (C) 2026 Mogan STEM authors */ +/*****************************************************************************/ + +#include "updatenotificationbar.hpp" +#include "qt_utilities.hpp" + +#include + +namespace QWK { + +UpdateNotificationBar::UpdateNotificationBar (QWidget* parent) + : QFrame (parent) { + setupUI (); +} + +UpdateNotificationBar::~UpdateNotificationBar ()= default; + +void +UpdateNotificationBar::setupUI () { + // 主布局(横条) + m_layout= new QHBoxLayout (this); + m_layout->setContentsMargins (12, 8, 12, 8); + m_layout->setSpacing (0); + + // 左侧 stretch(将内容推向中间) + m_layout->addStretch (1); + + // 图标 🎉 + m_iconLabel= new QLabel (this); + m_iconLabel->setText ("🎉"); + m_iconLabel->setStyleSheet ("font-size: 16px; margin-right: 8px;"); + m_layout->addWidget (m_iconLabel); + + // 提示文字(居中对齐) + m_messageLabel= new QLabel (this); + m_messageLabel->setObjectName ("updateNotificationMessage"); + m_messageLabel->setAlignment (Qt::AlignCenter | Qt::AlignVCenter); + m_messageLabel->setWordWrap (false); + m_layout->addWidget (m_messageLabel); + + // 消息和按钮之间的间距 + m_layout->addSpacing (16); + + // 按钮容器 + QWidget* btnWidget= new QWidget (this); + QHBoxLayout* btnLayout= new QHBoxLayout (btnWidget); + btnLayout->setContentsMargins (0, 0, 0, 0); + btnLayout->setSpacing (8); + + // 立即更新按钮 + m_updateNowBtn= new QPushButton (qt_translate ("Update now"), btnWidget); + m_updateNowBtn->setObjectName ("updateNotificationUpdateButton"); + m_updateNowBtn->setCursor (Qt::PointingHandCursor); + connect (m_updateNowBtn, &QPushButton::clicked, this, + &UpdateNotificationBar::updateNowRequested); + btnLayout->addWidget (m_updateNowBtn); + + // 稍后提醒按钮 + m_snoozeBtn= new QPushButton (qt_translate ("Remind later"), btnWidget); + m_snoozeBtn->setObjectName ("updateNotificationSnoozeButton"); + m_snoozeBtn->setCursor (Qt::PointingHandCursor); + connect (m_snoozeBtn, &QPushButton::clicked, this, + &UpdateNotificationBar::snoozeRequested); + btnLayout->addWidget (m_snoozeBtn); + + m_layout->addWidget (btnWidget); + + // 右侧 stretch(与左侧对称,将内容推向中间) + m_layout->addStretch (1); + + // 关闭按钮 + m_closeBtn= new QPushButton ("×", this); + m_closeBtn->setObjectName ("updateNotificationCloseButton"); + m_closeBtn->setCursor (Qt::PointingHandCursor); + m_closeBtn->setFixedSize (24, 24); + m_closeBtn->setStyleSheet ("text-align: center; font-size: 18px;"); + connect (m_closeBtn, &QPushButton::clicked, this, + &UpdateNotificationBar::closeRequested); + m_layout->addWidget (m_closeBtn); + + // 对象名(用于样式表) + setObjectName ("updateNotificationBar"); +} + +void +UpdateNotificationBar::setVersionInfo (const QString& currentVersion, + const QString& remoteVersion) { + QString msg= QString ("%1 v%2 (%3: v%4)") + .arg (qt_translate ("New version available")) + .arg (remoteVersion) + .arg (qt_translate ("current")) + .arg (currentVersion); + m_messageLabel->setText (msg); +} + +} // namespace QWK diff --git a/src/Plugins/QWindowKit/updatenotificationbar.hpp b/src/Plugins/QWindowKit/updatenotificationbar.hpp new file mode 100644 index 0000000000..79271005fb --- /dev/null +++ b/src/Plugins/QWindowKit/updatenotificationbar.hpp @@ -0,0 +1,45 @@ +/*****************************************************************************/ +/* MODULE : updatenotificationbar.hpp */ +/* DESCRIPTION : Version update notification bar (horizontal layout) */ +/* COPYRIGHT : (C) 2026 Mogan STEM authors */ +/*****************************************************************************/ + +#ifndef UPDATENOTIFICATIONBAR_H +#define UPDATENOTIFICATIONBAR_H + +#include +#include +#include +#include + +namespace QWK { + +class UpdateNotificationBar : public QFrame { + Q_OBJECT + +public: + explicit UpdateNotificationBar (QWidget* parent= nullptr); + ~UpdateNotificationBar (); + + void setVersionInfo (const QString& currentVersion, + const QString& remoteVersion); + +signals: + void updateNowRequested (); + void snoozeRequested (); + void closeRequested (); + +private: + void setupUI (); + + QHBoxLayout* m_layout; + QLabel* m_iconLabel; + QLabel* m_messageLabel; + QPushButton* m_updateNowBtn; + QPushButton* m_snoozeBtn; + QPushButton* m_closeBtn; +}; + +} // namespace QWK + +#endif // UPDATENOTIFICATIONBAR_H diff --git a/src/Plugins/Qt/qt_tm_widget.cpp b/src/Plugins/Qt/qt_tm_widget.cpp index 1c16cf6c35..a1d1fa33b2 100644 --- a/src/Plugins/Qt/qt_tm_widget.cpp +++ b/src/Plugins/Qt/qt_tm_widget.cpp @@ -335,8 +335,20 @@ qt_tm_widget_rep::qt_tm_widget_rep (int mask, command _quit) QObject::connect (loginButton, &QWK::LoginButton::clicked, [this] () { checkLocalTokenAndLogin (); }); - // 初始化访客提示条 - guestNotificationBar= new QWK::GuestNotificationBar (mw); + // 创建通知条容器(垂直布局,放在标题栏下方) + QWidget* notificationContainer= new QWidget (mw); + QVBoxLayout* notificationLayout = new QVBoxLayout (notificationContainer); + notificationLayout->setContentsMargins (0, 0, 0, 0); + notificationLayout->setSpacing (0); + + // 初始化版本更新提示条(在上) + updateNotificationBar= new QWK::UpdateNotificationBar (); + notificationLayout->addWidget (updateNotificationBar); + updateNotificationBar->hide (); + + // 初始化访客提示条(在下) + guestNotificationBar= new QWK::GuestNotificationBar (); + notificationLayout->addWidget (guestNotificationBar); // 连接提示条信号 QObject::connect (guestNotificationBar, @@ -362,6 +374,27 @@ qt_tm_widget_rep::qt_tm_widget_rep (int mask, command _quit) checkNetworkAvailable (); } + // 连接版本更新提示条信号 + QObject::connect (updateNotificationBar, + &QWK::UpdateNotificationBar::updateNowRequested, [this] () { + eval ("(use-modules (utils misc version-update))"); + string url= as_string (call ("get-update-download-url")); + open_url (url); + // 不隐藏提示条,保持显示直到用户实际更新版本 + }); + QObject::connect (updateNotificationBar, + &QWK::UpdateNotificationBar::snoozeRequested, [this] () { + eval ("(use-modules (utils misc version-update))"); + call ("snooze-version-update"); + updateNotificationBar->hide (); + }); + QObject::connect (updateNotificationBar, + &QWK::UpdateNotificationBar::closeRequested, + [this] () { updateNotificationBar->hide (); }); + + // 延迟检查版本更新(启动后10秒) + QTimer::singleShot (10000, [this] () { checkVersionUpdate (); }); + // there is a bug in the early implementation of toolbars in Qt 4.6 // which has been fixed in 4.6.2 (at least) // this is why we change dimension of icons @@ -519,7 +552,8 @@ qt_tm_widget_rep::qt_tm_widget_rep (int mask, command _quit) QWidget* q= main_widget->as_qwidget (); // force creation of QWidget q->setParent ( qwid); // q->layout()->removeWidget(q) will reset the parent to this - bl->addWidget (guestNotificationBar); // 添加访客提示条 + bl->addWidget ( + notificationContainer); // 添加通知容器(包含版本更新和访客提示条) bl->addWidget (q); mw->setCentralWidget (cw); @@ -2236,3 +2270,106 @@ qt_tm_widget_rep::checkNetworkAvailable () { } }); } + +// 检查版本更新,根据条件显示提示条 +// 流程:1.检查稍后提醒时间 -> 2.获取远程版本 -> 3.比较并显示 +// 社区版和商业版都显示版本更新提示,但跳转到不同的官网 +void +qt_tm_widget_rep::checkVersionUpdate () { + eval ("(use-modules (utils misc version-update))"); + + // 检查是否处于稍后提醒期间 + bool shouldCheck= as_bool (call ("should-check-version-update?")); + if (!shouldCheck) return; + + // 检查是否有 mock 版本(用于测试) + object mockVersion= call ("get-mock-remote-version"); + bool hasMock = !is_bool (mockVersion) || as_bool (mockVersion); + + if (hasMock) { + // 使用 mock 版本进行测试 + QString remoteVersion= to_qstring (as_string (mockVersion)); + QString localVersion = XMACS_VERSION; + + if (isVersionNewer (remoteVersion, localVersion)) { + m_remoteVersion= remoteVersion; + updateNotificationBar->setVersionInfo (localVersion, remoteVersion); + updateNotificationBar->show (); + } + return; + } + + // 发送HTTP请求获取远程版本 + // 商业版和社区版使用不同的版本号接口 + QString versionUrl; + if (is_community_stem ()) { + versionUrl= "https://liiistem.cn/mogan_latest_version.tm"; + } + else { + versionUrl= "https://liiistem.cn/latest_version.tm"; + } + + QNetworkAccessManager* manager= new QNetworkAccessManager (mainwindow ()); + QNetworkRequest request (versionUrl); + request.setRawHeader ("User-Agent", + to_qstring (stem_user_agent ()).toUtf8 ()); + + QNetworkReply* reply= manager->get (request); + QObject::connect (reply, &QNetworkReply::finished, [this, reply, manager] () { + if (reply->error () == QNetworkReply::NoError) { + QByteArray data = reply->readAll (); + QString remoteVersion= parseVersionFromTM (data); + QString localVersion = XMACS_VERSION; + + if (!remoteVersion.isEmpty ()) { + qDebug () << "[VersionUpdate] Parsed remote version:" << remoteVersion; + } + + if (remoteVersion.isEmpty ()) { + qDebug () << "[VersionUpdate] Failed to parse version from response"; + } + else if (isVersionNewer (remoteVersion, localVersion)) { + m_remoteVersion= remoteVersion; + updateNotificationBar->setVersionInfo (localVersion, remoteVersion); + updateNotificationBar->show (); + } + } + else { + qDebug () << "[VersionUpdate] Failed to fetch remote version:" + << reply->errorString (); + } + reply->deleteLater (); + manager->deleteLater (); + }); +} + +QString +qt_tm_widget_rep::parseVersionFromTM (const QByteArray& data) { + QString content= QString::fromUtf8 (data); + // 解析 TeXmacs 格式的 <\body> 标签内容 + QRegularExpression re ("<\\\\?body>\\s*([\\d\\.\\-rc]+)"); + QRegularExpressionMatch match= re.match (content); + return match.captured (1).trimmed (); +} + +bool +qt_tm_widget_rep::isVersionNewer (const QString& remote, const QString& local) { + // 提取纯数字版本号(去掉 -rcX 后缀) + QString remoteClean= remote.split ("-")[0]; + QString localClean = local.split ("-")[0]; + + // 语义化版本号比较(只比较前三位) + QStringList remoteParts= remoteClean.split ("."); + QStringList localParts = localClean.split ("."); + + // 只比较前三位版本号 + for (int i= 0; i < 3; i++) { + int remoteNum= (i < remoteParts.size ()) ? remoteParts[i].toInt () : 0; + int localNum = (i < localParts.size ()) ? localParts[i].toInt () : 0; + + if (remoteNum != localNum) { + return remoteNum > localNum; + } + } + return false; // 版本相同 +} diff --git a/src/Plugins/Qt/qt_tm_widget.hpp b/src/Plugins/Qt/qt_tm_widget.hpp index 7a489ce41a..c98e523b24 100644 --- a/src/Plugins/Qt/qt_tm_widget.hpp +++ b/src/Plugins/Qt/qt_tm_widget.hpp @@ -33,6 +33,7 @@ #include "../QWindowKit/guestnotificationbar.hpp" #include "../QWindowKit/loginbutton.hpp" #include "../QWindowKit/logindialog.hpp" +#include "../QWindowKit/updatenotificationbar.hpp" #include "../QWindowKit/windowbar.hpp" #include "../QWindowKit/windowbutton.hpp" #include @@ -65,31 +66,33 @@ class qt_tm_widget_rep : public qt_window_widget_rep { tab_tools_visibility = 1024 } visibility_t; */ - QLabel* rightLabel; - QLabel* leftLabel; - QLabel* middleLabel; - QToolBar* menuToolBar; - QToolBar* mainToolBar; - QToolBar* modeToolBar; - QToolBar* focusToolBar; - QToolBar* userToolBar; - QDockWidget* sideTools; - QDockWidget* leftTools; - QDockWidget* bottomTools; - QDockWidget* extraTools; - QTMTabPageContainer* tabPageContainer; - QTMAuxiliaryWidget* auxiliaryWidget; - QWK::WidgetWindowAgent* windowAgent; - QWK::GuestNotificationBar* guestNotificationBar; // 新增:访客提示条 - QWK::LoginButton* loginButton; - QWK::LoginDialog* m_loginDialog; - QLabel* avatarLabel; - QLabel* nameLabel; - QLabel* accountIdLabel; - QLabel* membershipPeriodLabel; - QLabel* membershipTitleLabel; - QPushButton* loginActionButton; - QPushButton* logoutButton; + QLabel* rightLabel; + QLabel* leftLabel; + QLabel* middleLabel; + QToolBar* menuToolBar; + QToolBar* mainToolBar; + QToolBar* modeToolBar; + QToolBar* focusToolBar; + QToolBar* userToolBar; + QDockWidget* sideTools; + QDockWidget* leftTools; + QDockWidget* bottomTools; + QDockWidget* extraTools; + QTMTabPageContainer* tabPageContainer; + QTMAuxiliaryWidget* auxiliaryWidget; + QWK::WidgetWindowAgent* windowAgent; + QWK::GuestNotificationBar* guestNotificationBar; // 访客提示条 + QWK::UpdateNotificationBar* updateNotificationBar; // 版本更新提示条 + QWK::LoginButton* loginButton; + QWK::LoginDialog* m_loginDialog; + QLabel* avatarLabel; + QLabel* nameLabel; + QLabel* accountIdLabel; + QLabel* membershipPeriodLabel; + QLabel* membershipTitleLabel; + QPushButton* loginActionButton; + QPushButton* logoutButton; + QString m_remoteVersion; // 远程版本号 #ifdef Q_OS_MAC QToolBar* dumbToolBar; @@ -124,6 +127,11 @@ class qt_tm_widget_rep : public qt_window_widget_rep { void showNotLoggedInDialog (const QString& errorMessage); void logout (); + // Version update notification + void checkVersionUpdate (); + QString parseVersionFromTM (const QByteArray& data); + bool isVersionNewer (const QString& remote, const QString& local); + qt_widget main_widget; qt_widget main_menu_widget; qt_widget waiting_main_menu_widget;