dsh-xueqiu 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -121,6 +121,7 @@ The data layer is defended in depth — safe to leave open for hours:
121
121
 
122
122
  ## Changelog
123
123
 
124
+ - **1.7.2** (2025-08-19) — News tab pagination: scroll-to-bottom or "load earlier" fetches older items (max_id cursor, deduped); refresh resets to the latest page.
124
125
  - **1.7.1** (2025-08-19) — Fix static install (`dsh plugin add`) crashing `dsh web` startup: dual-mode RPC (harness.handle for dynamic runs, a loopback+same-origin fenced `/xq-rpc` webServer route for static installs) and a proper `__ModuleLoader__` CJS client bundle.
125
126
  - **1.7.0** (2025-08-19) — Hidden-tab polling pause + CN/HK/US market session hints.
126
127
  - **1.6.1** — Pipeline deadlock watchdog fix, tab-switch fix, badgePos restore fix, debug RPC.
package/README.md CHANGED
@@ -151,6 +151,8 @@ dsh-xueqiu/
151
151
 
152
152
  ## 📋 更新日志
153
153
 
154
+ - **1.7.2**(2025-08-19)
155
+ - 新增:快讯 tab 翻页——滚动到底或点「加载更早」自动加载历史快讯(max_id 游标,自动去重),「刷新」重置回最新一页。
154
156
  - **1.7.1**(2025-08-19)
155
157
  - 修复:`dsh plugin add` 静态安装后 `dsh web` 启动即崩(`harness is not defined`)——静态安装改走 `webServer` 前缀路由 `/xq-rpc`(带回环 + 同源双重栅栏),动态运行时仍走 `harness.handle`,双模式自动切换。
156
158
  - 修复:静态安装的浏览器端 bundle 格式(`__ModuleLoader__` CJS 工厂)与网络通道(同源 fetch 替代 `host.call`),面板/徽章/指数条在静态模式下完整可用。
package/dynamic/client.js CHANGED
@@ -62,6 +62,8 @@ return {
62
62
  '.xq-news-item{font-size:12.5px;padding:6px 8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:7px;}\n' +
63
63
  '.xq-news-item.xq-important{border-left:3px solid var(--dsw-alias-state-warn-primary);}\n' +
64
64
  '.xq-news-time{font-size:10.5px;color:var(--dsw-alias-label-secondary);margin-top:3px;}\n' +
65
+ '.xq-news-more{font-size:11.5px;color:var(--dsw-alias-label-secondary);text-align:center;padding:6px 0 2px;cursor:pointer;user-select:none;}\n' +
66
+ '.xq-news-more:hover{color:var(--dsw-alias-label-primary);}\n' +
65
67
  '.xq-detail-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;}\n' +
66
68
  '.xq-detail-name{font-size:15px;font-weight:700;}\n' +
67
69
  '.xq-detail-code{font-size:11px;color:var(--dsw-alias-label-secondary);}\n' +
@@ -461,6 +463,8 @@ return {
461
463
  const [hot, setHot] = React.useState([])
462
464
  const [hotMarket, setHotMarket] = React.useState('cn')
463
465
  const [news, setNews] = React.useState([])
466
+ const [newsMore, setNewsMore] = React.useState(false) // 翻页加载中
467
+ const [newsNoMore, setNewsNoMore] = React.useState(false) // 已到最旧
464
468
  const [searchQ, setSearchQ] = React.useState('')
465
469
  const [searchMode, setSearchMode] = React.useState('stock')
466
470
  const [searchRes, setSearchRes] = React.useState([])
@@ -505,12 +509,32 @@ return {
505
509
  ]).then(function (res) {
506
510
  setHot((res[0] && res[0].list) || [])
507
511
  setNews((res[1] && res[1].items) || [])
512
+ setNewsMore(false)
513
+ setNewsNoMore(false)
508
514
  setErr('')
509
515
  }).catch(function (e) {
510
516
  setErr(String((e && e.message) || e))
511
517
  })
512
518
  }
513
519
 
520
+ // 快讯翻页:max_id = 当前最旧一条 id,返回严格更早的记录
521
+ function loadMoreNews() {
522
+ if (newsMore || newsNoMore || !news.length) return
523
+ const maxId = news[news.length - 1].id
524
+ setNewsMore(true)
525
+ call('news', { count: 20, max_id: maxId }).then(function (res) {
526
+ const items = (res && res.items) || []
527
+ const seen = {}
528
+ news.forEach(function (it) { seen[it.id] = true })
529
+ const fresh = items.filter(function (it) { return !seen[it.id] })
530
+ if (!fresh.length) setNewsNoMore(true)
531
+ else setNews(news.concat(fresh))
532
+ setNewsMore(false)
533
+ }).catch(function () {
534
+ setNewsMore(false)
535
+ })
536
+ }
537
+
514
538
  React.useEffect(function () {
515
539
  setLoading(true)
516
540
  refreshMarket().then(function () { return refreshContent() })
@@ -868,8 +892,20 @@ return {
868
892
  el('div', { key: 'm', className: 'xq-news-time' }, fmtTime(it.created_at) + (it.mark === 1 ? ' · 重要' : ''))
869
893
  ])
870
894
  })
895
+ // 滚动到底自动加载;手动入口兜底(滚轮惯性/触控板场景)
896
+ kids.push(el('div', {
897
+ key: 'more',
898
+ className: 'xq-news-more',
899
+ onClick: function () { loadMoreNews() }
900
+ }, newsMore ? '加载中…' : (newsNoMore ? '没有更早的快讯了' : '加载更早 ↑')))
871
901
  return news.length
872
- ? el('div', { className: 'xq-news' }, kids)
902
+ ? el('div', {
903
+ className: 'xq-news',
904
+ onScroll: function (e) {
905
+ const n = e.target
906
+ if (n.scrollTop + n.clientHeight >= n.scrollHeight - 40) loadMoreNews()
907
+ }
908
+ }, kids)
873
909
  : el('div', { className: 'xq-muted' }, '暂无快讯')
874
910
  }
875
911
 
package/dynamic/host.js CHANGED
@@ -375,7 +375,10 @@ return {
375
375
 
376
376
  async function actNews(args) {
377
377
  const count = Math.min(Math.max(parseInt(args.count, 10) || 20, 1), 50)
378
- const data = await getJSON('/statuses/livenews/list.json', { since_id: -1, max_id: -1, count: count }, { base: SITE, ttl: 120000 })
378
+ // max_id 翻页游标:-1 取最新一页;传上一页最旧一条 id 继续往回翻
379
+ const maxId = parseInt(args.max_id, 10)
380
+ const cursor = isFinite(maxId) && maxId > 0 ? maxId : -1
381
+ const data = await getJSON('/statuses/livenews/list.json', { since_id: -1, max_id: cursor, count: count }, { base: SITE, ttl: 120000 })
379
382
  const items = (data && data.items) || []
380
383
  return {
381
384
  items: items.map(function (it) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-xueqiu",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "雪球 mini 行情面板 — DeepSeek Harness 免登录 A股/港美股实时行情、K线(蜡烛/成交量/均线)、分时图、热榜、搜索、7x24 快讯、热议用户。嵌入式面板 + 可拖拽行情徽标,内置请求节流、TTL 缓存与 Cookie 自动续期。",
5
5
  "keywords": [
6
6
  "dsh",
@@ -46,5 +46,10 @@
46
46
  "cordis.patch.yml",
47
47
  "README.md",
48
48
  "LICENSE"
49
- ]
49
+ ],
50
+ "scripts": {
51
+ "generate": "python3 scripts/gen-static.py",
52
+ "check": "python3 scripts/gen-static.py --check && node --check src/index.js && node --check src/client/index.js && node -e \"new Function(require('fs').readFileSync('dynamic/host.js','utf8'))\" && node -e \"new Function(require('fs').readFileSync('dynamic/client.js','utf8'))\" && bash scripts/static-smoke.sh",
53
+ "prepublishOnly": "npm run check"
54
+ }
50
55
  }
@@ -1,5 +1,5 @@
1
+ // 由 scripts/gen-static.py 从 dynamic/client.js 生成 —— 勿手改;改 dynamic/ 后重新生成。
1
2
  // 静态 bundle 格式:classic script 注册到 __ModuleLoader__,factory 为 CJS 形式。
2
- // 由 dynamic/client.js 机械转换生成 —— 修改请先改 dynamic/ 再重新转换。
3
3
  window.__ModuleLoader__.load({
4
4
  id: 'dsh-xueqiu',
5
5
  factory: function (require) {
@@ -7,7 +7,7 @@ window.__ModuleLoader__.load({
7
7
  var exports = module.exports
8
8
  const React = require('react')
9
9
 
10
- // 动态运行时的 styles.insert 替代:自管 <style> 标签,随插件停用移除
10
+ // 动态运行时 styles.insert 的替代:自管 <style> 标签(随插件停用移除交给卸载流程)
11
11
  let _styleTag = null
12
12
  function insertStyle (css) {
13
13
  if (typeof document === 'undefined' || !document.head) return
@@ -81,6 +81,8 @@ exports.default = {
81
81
  '.xq-news-item{font-size:12.5px;padding:6px 8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:7px;}\n' +
82
82
  '.xq-news-item.xq-important{border-left:3px solid var(--dsw-alias-state-warn-primary);}\n' +
83
83
  '.xq-news-time{font-size:10.5px;color:var(--dsw-alias-label-secondary);margin-top:3px;}\n' +
84
+ '.xq-news-more{font-size:11.5px;color:var(--dsw-alias-label-secondary);text-align:center;padding:6px 0 2px;cursor:pointer;user-select:none;}\n' +
85
+ '.xq-news-more:hover{color:var(--dsw-alias-label-primary);}\n' +
84
86
  '.xq-detail-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;}\n' +
85
87
  '.xq-detail-name{font-size:15px;font-weight:700;}\n' +
86
88
  '.xq-detail-code{font-size:11px;color:var(--dsw-alias-label-secondary);}\n' +
@@ -480,6 +482,8 @@ exports.default = {
480
482
  const [hot, setHot] = React.useState([])
481
483
  const [hotMarket, setHotMarket] = React.useState('cn')
482
484
  const [news, setNews] = React.useState([])
485
+ const [newsMore, setNewsMore] = React.useState(false) // 翻页加载中
486
+ const [newsNoMore, setNewsNoMore] = React.useState(false) // 已到最旧
483
487
  const [searchQ, setSearchQ] = React.useState('')
484
488
  const [searchMode, setSearchMode] = React.useState('stock')
485
489
  const [searchRes, setSearchRes] = React.useState([])
@@ -524,12 +528,32 @@ exports.default = {
524
528
  ]).then(function (res) {
525
529
  setHot((res[0] && res[0].list) || [])
526
530
  setNews((res[1] && res[1].items) || [])
531
+ setNewsMore(false)
532
+ setNewsNoMore(false)
527
533
  setErr('')
528
534
  }).catch(function (e) {
529
535
  setErr(String((e && e.message) || e))
530
536
  })
531
537
  }
532
538
 
539
+ // 快讯翻页:max_id = 当前最旧一条 id,返回严格更早的记录
540
+ function loadMoreNews() {
541
+ if (newsMore || newsNoMore || !news.length) return
542
+ const maxId = news[news.length - 1].id
543
+ setNewsMore(true)
544
+ call('news', { count: 20, max_id: maxId }).then(function (res) {
545
+ const items = (res && res.items) || []
546
+ const seen = {}
547
+ news.forEach(function (it) { seen[it.id] = true })
548
+ const fresh = items.filter(function (it) { return !seen[it.id] })
549
+ if (!fresh.length) setNewsNoMore(true)
550
+ else setNews(news.concat(fresh))
551
+ setNewsMore(false)
552
+ }).catch(function () {
553
+ setNewsMore(false)
554
+ })
555
+ }
556
+
533
557
  React.useEffect(function () {
534
558
  setLoading(true)
535
559
  refreshMarket().then(function () { return refreshContent() })
@@ -887,8 +911,20 @@ exports.default = {
887
911
  el('div', { key: 'm', className: 'xq-news-time' }, fmtTime(it.created_at) + (it.mark === 1 ? ' · 重要' : ''))
888
912
  ])
889
913
  })
914
+ // 滚动到底自动加载;手动入口兜底(滚轮惯性/触控板场景)
915
+ kids.push(el('div', {
916
+ key: 'more',
917
+ className: 'xq-news-more',
918
+ onClick: function () { loadMoreNews() }
919
+ }, newsMore ? '加载中…' : (newsNoMore ? '没有更早的快讯了' : '加载更早 ↑')))
890
920
  return news.length
891
- ? el('div', { className: 'xq-news' }, kids)
921
+ ? el('div', {
922
+ className: 'xq-news',
923
+ onScroll: function (e) {
924
+ const n = e.target
925
+ if (n.scrollTop + n.clientHeight >= n.scrollHeight - 40) loadMoreNews()
926
+ }
927
+ }, kids)
892
928
  : el('div', { className: 'xq-muted' }, '暂无快讯')
893
929
  }
894
930
 
package/src/index.js CHANGED
@@ -375,7 +375,10 @@ export default {
375
375
 
376
376
  async function actNews(args) {
377
377
  const count = Math.min(Math.max(parseInt(args.count, 10) || 20, 1), 50)
378
- const data = await getJSON('/statuses/livenews/list.json', { since_id: -1, max_id: -1, count: count }, { base: SITE, ttl: 120000 })
378
+ // max_id 翻页游标:-1 取最新一页;传上一页最旧一条 id 继续往回翻
379
+ const maxId = parseInt(args.max_id, 10)
380
+ const cursor = isFinite(maxId) && maxId > 0 ? maxId : -1
381
+ const data = await getJSON('/statuses/livenews/list.json', { since_id: -1, max_id: cursor, count: count }, { base: SITE, ttl: 120000 })
379
382
  const items = (data && data.items) || []
380
383
  return {
381
384
  items: items.map(function (it) {