dsh-design-studio 0.13.0 → 0.13.1

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
@@ -42,7 +42,7 @@ If DSH can't reach the npm registry, it falls back to a mirror in China. To upda
42
42
  - **Open what you have:** click "Browse…" on the empty page to pick an HTML file, or drop an HTML file or folder onto the sidebar (desktop app); you can also type a path or a dev-server port.
43
43
  - **Design check:** one click finds overflowing text, low contrast, small tap targets and tiny text, marked right on the page. In Design mode the model also checks its own work and fixes what it finds.
44
44
  - **Versions and compare:** every turn is saved; drag a slider to compare, and go back to any version.
45
- - **Export and phone preview:** showcase images, PDF and PowerPoint (links between screens still work); scan a QR code to see it on your phone at real size.
45
+ - **Export and phone preview:** showcase images, PDF and PowerPoint (links between screens still work); scan a QR code to see it on your phone at real size. Slides and posters larger than the default canvas now use the page's real size. You can also set it by adding `WIDTHxHEIGHT` after the type, for example `<meta name="dsh-design" content="slides 1440x810">`.
46
46
  - **Design library and gallery:** everything you've designed, plus saved references and styles, lives in the Library. Browse templates, design systems and style cards others shared, and "Start from this" copies one for you to keep editing. Share your own with "Share to the gallery": it's checked, zipped and submitted on a web page, and listed once reviewed. The gallery catalog comes from npm mirrors and is used only when its signature and checksums match; to keep it offline, turn on "Design library offline" on the Plugins page.
47
47
  - **Your brand, and real code:** give a site URL to extract a brand design system and design with it from then on; turn a finished design into React / Vue / HTML (mini programs straight into uni-app or native WeChat code, HBuilderX projects included), compared screen by screen against the design.
48
48
 
package/README.md CHANGED
@@ -42,7 +42,7 @@
42
42
  - **打开已有的页面**:首页输入框左边的「+」→「打开已有的 HTML…」选一个,或把 HTML 文件 / 文件夹拖进右侧栏就能预览(桌面版);也能输路径或开发服务器端口。
43
43
  - **设计体检**:一键查文字溢出、对比度不够、按钮太小、字太小,问题直接标在页面上;模型做完也会自己查一遍再改。
44
44
  - **版本和对比**:每轮自动存一版,左右拖动对比,随时回到任意一版。
45
- - **导出和手机上看**:导出展示图、PDF、PPT(屏和屏之间的跳转照样能点);扫码在手机上按真机尺寸看。
45
+ - **导出和手机上看**:导出展示图、PDF、PPT(屏和屏之间的跳转照样能点);扫码在手机上按真机尺寸看。页面比默认画幅大的幻灯片、海报,画幅自动跟页面走;也可以在类型后面跟 `宽x高` 指定,比如 `<meta name="dsh-design" content="slides 1440x810">`。
46
46
  - **设计库和设计市场**:以前做过的设计、收的参考图和风格都在「设计库」里;市场里逛大家分享的模板、设计系统和风格卡,点「用这个开始」复制一份接着改。自己做的点「分享到设计市场」,查过打成 zip,在网页里交上去,审过就上架。市场目录从 npm 镜像取,签了名、核对过才用;不想让它联网,在插件页打开「设计库不联网」。
47
47
  - **照品牌风格做、转成代码**:给官网地址,提一套品牌设计系统,以后都照它做;做好的设计转成 React / Vue / HTML,小程序直接转成 uni-app 或原生小程序代码(HBuilderX 建的项目也认得出),逐屏和设计稿比对。
48
48
 
package/lib/client.js CHANGED
@@ -2057,6 +2057,7 @@ window.__ModuleLoader__.load({
2057
2057
  .ddp-home.over{position:absolute;inset:0;z-index:10;background-color:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));animation:ddp-homein .28s ${EASE} both}
2058
2058
  .ddp-home.over.out{animation:ddp-homeout .19s ease both;pointer-events:none}
2059
2059
  .ddp-home .ddp-start>*{animation:ddp-rise .5s ${EASE} both}
2060
+ .ddp-home .ddp-start>.ddp-askwrap{position:relative;z-index:5}
2060
2061
  .ddp-home .ddp-start>:nth-child(2){animation-delay:.05s}
2061
2062
  .ddp-home .ddp-start>:nth-child(3){animation-delay:.1s}
2062
2063
  .ddp-home .ddp-start>:nth-child(4){animation-delay:.15s}
@@ -4300,6 +4301,28 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
4300
4301
  return KIND_SIZE[kind] || KIND_SIZE.web
4301
4302
  }
4302
4303
 
4304
+ // 定死画面的类型:页面比默认画幅大(写死了 1440×810 又没写进 meta)就按页面实际大小。比例不像一页的(几页竖着堆的)不算
4305
+ const AUTO_RATIO = { slides: [1.2, 2.4], poster: [0.45, 2.5] }
4306
+
4307
+ /** 各屏量到的实际大小 → 'WxH'(都没比默认大:'')。和 look.js 的 autoSize 一样 */
4308
+ function autoSize(list, kind) {
4309
+ const range = AUTO_RATIO[kind]
4310
+ if (!range) return ''
4311
+ const [dw, dh] = KIND_SIZE[kind]
4312
+ let W = 0, H = 0
4313
+ for (const n of list) {
4314
+ if (!Array.isArray(n)) continue
4315
+ const w = Math.ceil(+n[0]), h = Math.ceil(+n[1])
4316
+ if (!(w >= 200 && h >= 200 && w <= 4000 && h <= 6000)) continue
4317
+ if ((w <= dw && h <= dh) || w / h < range[0] || w / h > range[1]) continue
4318
+ W = Math.max(W, w)
4319
+ H = Math.max(H, h)
4320
+ }
4321
+ if (!W) return ''
4322
+ // 一组屏共用一个画幅:别的屏量不出比默认大(正好默认大小的也报不出来),画幅不能比默认小,不然它们被裁
4323
+ return list.length > 1 ? `${Math.max(W, dw)}x${Math.max(H, dh)}` : `${W}x${H}`
4324
+ }
4325
+
4303
4326
  /** 外框加上去之后的整块大小 */
4304
4327
  function deviceBox(kind, size) {
4305
4328
  const f = FRAME[kind] || FRAME.web
@@ -5085,7 +5108,12 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
5085
5108
  if (!f || !f.contentWindow) return
5086
5109
  // 远程看:沙盒里的页面是不透明的源,只能发给 *(发的都是界面指令,没有要保密的)
5087
5110
  if (f.hasAttribute('srcdoc')) f.contentWindow.postMessage({ dshDesign: 1, ...msg }, '*')
5088
- else if (origin) f.contentWindow.postMessage({ dshDesign: 1, ...msg }, origin)
5111
+ else {
5112
+ // 按这一帧自己的地址发:单看时换到别的设计目录,还是同一个组件,api 里留着的旧闭包记的是旧地址,消息会被浏览器丢掉(导出一直等到超时)
5113
+ let to = ''
5114
+ try { to = new URL(f.getAttribute('src')).origin } catch { /* 地址不对就不发 */ }
5115
+ if (to) f.contentWindow.postMessage({ dshDesign: 1, ...msg }, to)
5116
+ }
5089
5117
  }
5090
5118
 
5091
5119
  // 在后台载一帧新的;正在载就记一笔,等它换上来再载
@@ -5205,7 +5233,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
5205
5233
  L.title = d.title || ''
5206
5234
  if (p.dark) postTo(id, { type: 'theme', dark: true })
5207
5235
  if (p.tweak) postTo(id, { type: 'tweak', vars: p.tweak.vars, attrs: p.tweak.attrs })
5208
- if (p.onMeta) p.onMeta(d.meta || '', d.title || '', Array.isArray(d.tokens) ? d.tokens : [], !!d.dark, Array.isArray(d.links) ? d.links : [])
5236
+ if (p.onMeta) p.onMeta(d.meta || '', d.title || '', Array.isArray(d.tokens) ? d.tokens : [], !!d.dark, Array.isArray(d.links) ? d.links : [], d.natural)
5209
5237
  if (p.onHref && d.href) p.onHref(d.href)
5210
5238
  if (isCur) {
5211
5239
  postTo(id, { type: 'reveal' })
@@ -7578,7 +7606,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
7578
7606
  const meta = parseMeta(main.meta || (st && st.draft && st.draft.meta) || (playR.current.end ? '' : playR.current.meta) || '') // 第一屏还在放草稿(真的没摆出来):按草稿开头写的类型
7579
7607
  const tokens = main.tokens || []
7580
7608
  const kind = (st && st.kind) || meta.kind || 'web'
7581
- const size = sizeOf((st && st.size) || meta.size, kind)
7609
+ const size = sizeOf((st && st.size) || meta.size || autoSize(screens.map((sc) => (infoOf(ik(sc)) || {}).natural), kind), kind)
7582
7610
  const view = (st && st.view) || (screens.length >= 2 && screens.length <= 12 ? 'canvas' : 'single')
7583
7611
  const canvas = view === 'canvas'
7584
7612
  const writingNew = !!(st && st.writing) && /\.html?$/i.test(st.writing) && !screens.some((s) => s.key === st.writing)
@@ -7764,11 +7792,14 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
7764
7792
  showFlash(t('born', { name: title || labelOf(sc) }))
7765
7793
  }
7766
7794
 
7767
- const remember = (key, m, title, toks, dk, links) => {
7795
+ const remember = (key, m, title, toks, dk, links, nat) => {
7768
7796
  const k = site + '|' + key
7769
7797
  const sig = JSON.stringify([toks || [], links || []])
7770
- setInfo((cur) => (cur[k] && cur[k].meta === m && cur[k].title === title && cur[k].sig === sig && cur[k].dark === dk ? cur
7771
- : { ...cur, [k]: { meta: m, title, tokens: toks || [], links: links || [], sig, dark: dk } }))
7798
+ setInfo((cur) => {
7799
+ const natural = nat || (cur[k] && cur[k].natural) || null // 画幅放大后页面不再比框大、报空:还按上回量的
7800
+ return cur[k] && cur[k].meta === m && cur[k].title === title && cur[k].sig === sig && cur[k].dark === dk && String(cur[k].natural) === String(natural) ? cur
7801
+ : { ...cur, [k]: { meta: m, title, tokens: toks || [], links: links || [], sig, dark: dk, natural } }
7802
+ })
7772
7803
  }
7773
7804
 
7774
7805
  const rememberLinks = (key, links) => {
@@ -8068,7 +8099,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
8068
8099
  onEdited: (d) => onEdited(sc, d),
8069
8100
  onEditDone: () => setTextEdit(false),
8070
8101
  onNote: (n) => note(n, sc),
8071
- onMeta: (m, title, toks, dk, links) => remember(ik(sc), m, title, toks, dk, links),
8102
+ onMeta: (m, title, toks, dk, links, nat) => remember(ik(sc), m, title, toks, dk, links, nat),
8072
8103
  onLinks: (links) => rememberLinks(ik(sc), links),
8073
8104
  onTop: (dk) => setTop(ik(sc), dk),
8074
8105
  onBuilt: (title) => onBuilt(sc, title),
@@ -9031,7 +9062,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
9031
9062
  onNav: isTop ? demoGo : undefined,
9032
9063
  onHref: isTop ? (href) => demoHref(l.id, href) : undefined,
9033
9064
  onShown: () => demoShown(l.id),
9034
- onMeta: (m, title, toks, dk, links) => remember(l.key, m, title, toks, dk, links),
9065
+ onMeta: (m, title, toks, dk, links, nat) => remember(l.key, m, title, toks, dk, links, nat),
9035
9066
  onLinks: (links) => rememberLinks(l.key, links),
9036
9067
  onTop: (dk) => setTop(l.key, dk),
9037
9068
  })))))
@@ -9366,7 +9397,7 @@ ${Object.entries(CHIP_ICON).map(([k, d]) => `[data-composer-chip="${chipSrc(k)}"
9366
9397
  // 注意:这里是客户端**服务名**(短的),不是包名
9367
9398
  exports.inject = INJECT
9368
9399
  // 测试用
9369
- exports._test = { pulseStep, MarketDetail, dragKind, droppedOf, pickInfo, pickLabel, pickRanges, pickRule, chipOf, chipRanges, makeT, ZH, EN, formatCheck, formatTweaks, markHit, markPath, STYLES, formatStyle, accentUse, autoStyle, styleLook, padInsert, pageOf, pxOf, styleChanges, formatStyleAsk, takesOf, formatCheckTool, poller, frameOf, paletteOf, formatRef, layoutRow, keyOfPath, flowsOf, flowLines, flowMarks, formatPick, whereOf, parseMeta, sizeOf, layoutGrid, fitCam, tokenKind, formatToken, splitTitles, pickColors, hhmm, formatTpl, formatPrep, styleFromTokens, myStyleOf, isZipFile }
9400
+ exports._test = { autoSize, pulseStep, MarketDetail, dragKind, droppedOf, pickInfo, pickLabel, pickRanges, pickRule, chipOf, chipRanges, makeT, ZH, EN, formatCheck, formatTweaks, markHit, markPath, STYLES, formatStyle, accentUse, autoStyle, styleLook, padInsert, pageOf, pxOf, styleChanges, formatStyleAsk, takesOf, formatCheckTool, poller, frameOf, paletteOf, formatRef, layoutRow, keyOfPath, flowsOf, flowLines, flowMarks, formatPick, whereOf, parseMeta, sizeOf, layoutGrid, fitCam, tokenKind, formatToken, splitTitles, pickColors, hhmm, formatTpl, formatPrep, styleFromTokens, myStyleOf, isZipFile }
9370
9401
  return module.exports
9371
9402
  },
9372
9403
  })
package/lib/index.js CHANGED
@@ -60,7 +60,7 @@ import { createPhone } from './phone.js'
60
60
  import { qrMatrix } from './qr.js'
61
61
  import { makePdf, makePptx, jpegInfo } from './doc.js'
62
62
  import { createBrowser } from './browser.js'
63
- import { designOf, metaOfFile, sizeOf, pickScreens, measureAll, shootAll, cropIssues, formatMeasure, issueSnapshot, formatShots, whereOf, MAX_SCREENS, MAX_SIZES, MAX_SHOTS, MAX_CROPS } from './look.js'
63
+ import { designOf, fitSize, metaOfFile, sizeOf, pickScreens, measureAll, shootAll, cropIssues, formatMeasure, issueSnapshot, formatShots, whereOf, MAX_SCREENS, MAX_SIZES, MAX_SHOTS, MAX_CROPS } from './look.js'
64
64
  import { SYSTEMS_DIR, extractSystem, listSystems, setDefault, skillSection } from './system.js'
65
65
  import { readTweaks, applyTweaks, lintTweaks } from './tweaks.js'
66
66
  import { FRAMEWORKS, detectProject, projectFor, defaultFramework, buildHandoff, compareScreens, implUrl, defaultDest, pickedDest, within } from './handoff.js'
@@ -176,6 +176,9 @@ export const previewTool = {
176
176
  },
177
177
  }
178
178
 
179
+ /** 无头浏览器用的尺寸:没写尺寸的幻灯片 / 海报按页面实际大小(和右侧栏的画幅一样) */
180
+ const fitted = (b, d, names) => (d.auto && b && b.available() ? fitSize(b, pickScreens(d.snap, names, names && names.length ? 40 : 6).list, d.kind, d.size) : d.size)
181
+
179
182
  /** 这个会话在预览的设计:哪几屏、哪类、多大(工具在无头浏览器里量 / 截图用) */
180
183
  function designFor(id) {
181
184
  const snap = studio.tracker.snapshot(id)
@@ -236,14 +239,15 @@ export const checkTool = {
236
239
  const id = toolSession(exec, '体检')
237
240
  const a = args || {}
238
241
  if (!studio.tracker.snapshot(id).url) return { text: '还没有在预览的页面:先用 preview_page 打开设计文件,再体检。' }
239
- const { snap, kind, size } = designFor(id)
242
+ const design = designFor(id)
243
+ const { snap, kind } = design
240
244
  const want = sizesFrom(a.sizes, kind)
241
245
  const b = studio.browser
242
246
  if (b && b.available()) {
243
247
  try {
244
248
  const pick = pickScreens(snap, a.screens, MAX_SCREENS)
245
249
  if (!pick.list.length) return { text: `没找到要量的屏:${pick.missing.join('、')}。现在有:${(snap.screens || []).map((x) => x.key).join('、') || snap.focus}` }
246
- const sizes = want.sizes.length ? want.sizes : [size]
250
+ const sizes = want.sizes.length ? want.sizes : [await fitted(b, design, a.screens)]
247
251
  const rows = await measureAll(b, { screens: pick.list, sizes, kind, dark: !!a.dark, locate: (items) => locateRules(studio.tracker.stateOf(id), items) })
248
252
  if (rows.some((r) => !r.error)) {
249
253
  // 同一个会话上次量的结果:这次比一比修好了几个、新出了几个
@@ -369,8 +373,9 @@ export const lookTool = {
369
373
  if (!studio.tracker.snapshot(id).url) return { text: '还没有在预览的页面:先用 preview_page 打开设计文件,再看。' }
370
374
  const b = studio.browser
371
375
  if (!b || !b.available()) return { text: '这台电脑上没找到 Chrome / Edge(无头浏览器截图要用),看不了。用 check_design 拿测量数字,或者让用户看右侧栏。' }
372
- const { snap, kind, size } = designFor(id)
373
- const want = a.size ? sizeOf(a.size, kind) : size
376
+ const design = designFor(id)
377
+ const { snap, kind } = design
378
+ const want = a.size ? sizeOf(a.size, kind) : await fitted(b, design, a.screens)
374
379
  if (!want) return { text: `认不出尺寸「${String(a.size).slice(0, 20)}」:写成 375x667,或只写宽度 375。` }
375
380
  const at = ['top', 'bottom', 'full'].includes(a.at) ? a.at : 'top'
376
381
  const pick = pickScreens(snap, a.screens, MAX_SHOTS)
@@ -469,7 +474,9 @@ export const handoffTool = {
469
474
  if (!snap.url) return { text: '还没有在预览的设计:先用 preview_page 打开设计文件。' }
470
475
  const t = studio.tracker.stateOf(id).target
471
476
  if (!t || t.kind !== 'file') return { text: '现在预览的是开发服务器上的页面,它已经是代码了:直接在那个项目里改;要比对设计稿就先用 preview_page 打开设计的 HTML 文件。' }
472
- const { kind, size } = designFor(id)
477
+ const design = designFor(id)
478
+ const { kind } = design
479
+ const size = await fitted(studio.browser, design, a.screens)
473
480
  const pick = pickScreens(snap, a.screens, 12)
474
481
  if (!pick.list.length) return { text: `没找到要转的屏:${pick.missing.join('、')}。现在有:${(snap.screens || []).map((x) => x.key).join('、')}` }
475
482
  const cwd = studio.tracker.cwdOf(id)
@@ -524,8 +531,9 @@ export const compareTool = {
524
531
  if (!snap.url) return { text: '还没有在预览的设计稿:先用 preview_page 打开设计文件,再比。' }
525
532
  const b = studio.browser
526
533
  if (!b || !b.available()) return { text: '这台电脑上没找到 Chrome / Edge(比对要用无头浏览器截图),比不了。' }
527
- const { kind, size } = designFor(id)
528
- const want = a.size ? sizeOf(a.size, kind) : size
534
+ const design = designFor(id)
535
+ const { kind } = design
536
+ const want = a.size ? sizeOf(a.size, kind) : await fitted(b, design, typeof a.screen === 'string' && a.screen.trim() ? [a.screen] : null)
529
537
  if (!want) return { text: `认不出尺寸「${String(a.size).slice(0, 20)}」:写成 375x667,或只写宽度 375。` }
530
538
  // 指定了哪屏就只认那屏,找不到就说(别悄悄拿第一屏比,结果对不上号);没指定才用正在看的 / 第一屏
531
539
  const asked = typeof a.screen === 'string' && a.screen.trim()
package/lib/look.js CHANGED
@@ -51,6 +51,28 @@ export function sizeOf(v, kind) {
51
51
  return [w, dh]
52
52
  }
53
53
 
54
+ // 定死画面的类型:页面比默认画幅大(写死了 1440×810 又没写进 meta)就按页面实际大小。比例不像一页的(几页竖着堆的)不算
55
+ const AUTO_RATIO = { slides: [1.2, 2.4], poster: [0.45, 2.5] }
56
+
57
+ /** 各屏量到的实际大小 → 'WxH'(都没比默认大:'')。和右侧栏 client.js 的 autoSize 一样 */
58
+ export function autoSize(list, kind) {
59
+ const range = AUTO_RATIO[kind]
60
+ if (!range) return ''
61
+ const [dw, dh] = KIND_SIZE[kind]
62
+ let W = 0, H = 0
63
+ for (const n of list) {
64
+ if (!Array.isArray(n)) continue
65
+ const w = Math.ceil(+n[0]), h = Math.ceil(+n[1])
66
+ if (!(w >= 200 && h >= 200 && w <= 4000 && h <= 6000)) continue
67
+ if ((w <= dw && h <= dh) || w / h < range[0] || w / h > range[1]) continue
68
+ W = Math.max(W, w)
69
+ H = Math.max(H, h)
70
+ }
71
+ if (!W) return ''
72
+ // 一组屏共用一个画幅:别的屏量不出比默认大(正好默认大小的也报不出来),画幅不能比默认小,不然它们被裁
73
+ return list.length > 1 ? `${Math.max(W, dw)}x${Math.max(H, dh)}` : `${W}x${H}`
74
+ }
75
+
54
76
  /** 设计稿是哪类、多大:preview_page 给的 → 页面里的 <meta name="dsh-design"> → 默认网页 */
55
77
  export function designOf(snap, readHead) {
56
78
  let meta = { kind: '', size: '' }
@@ -58,8 +80,9 @@ export function designOf(snap, readHead) {
58
80
  try { meta = parseMeta(readHead() || '') } catch { /* 读不到就按默认 */ }
59
81
  }
60
82
  const kind = KIND_LIST.includes(snap.kind) ? snap.kind : meta.kind || 'web'
61
- const size = sizeOf(snap.size, kind) || sizeOf(meta.size, kind) || KIND_SIZE[kind]
62
- return { kind, size }
83
+ const named = sizeOf(snap.size, kind) || sizeOf(meta.size, kind)
84
+ const size = named || KIND_SIZE[kind]
85
+ return !named && AUTO_RATIO[kind] ? { kind, size, auto: true } : { kind, size }
63
86
  }
64
87
 
65
88
  /** 本地 HTML 文件里 dsh-design 那个 meta 的内容(只读开头一段) */
@@ -109,6 +132,13 @@ export async function prepare(page, dark) {
109
132
  return { info: await page.eval('window.__dshDesign.info()'), darkOk }
110
133
  }
111
134
 
135
+ /** 没写尺寸的幻灯片 / 海报:开页面量一下,比默认画幅大就按页面实际大小(和右侧栏的画幅一样)。量不到就还是默认 */
136
+ export async function fitSize(browser, screens, kind, size) {
137
+ const [w, h] = size
138
+ const seen = await Promise.all(screens.map((sc) => browser.withPage({ url: sc.url, w, h }, async (page) => (await prepare(page, false)).info.natural).catch(() => null)))
139
+ return sizeOf(autoSize(seen, kind), kind) || size
140
+ }
141
+
112
142
  /**
113
143
  * 量:每屏 × 每种尺寸开一个页面跑体检。
114
144
  * @returns [{ key, label, w, h, issues, metrics, error }]
package/lib/picker.js CHANGED
@@ -993,6 +993,35 @@
993
993
 
994
994
  const seedNow = () => ({ href: here(), scroll: { x: scrollX, y: scrollY }, sig: snapshot().map })
995
995
 
996
+ /**
997
+ * 最外层那块的实际大小 [宽, 高](幻灯片 / 海报常写死 1440×810,比框大的话画幅要跟它走)。
998
+ * 正好和框一样大(自适应的,或画幅已经跟过来了)、几页竖着堆的、还有别的东西往外撑的:null
999
+ */
1000
+ function naturalSize() {
1001
+ const b = document.body
1002
+ if (!b) return null
1003
+ const skip = (el) => el.hasAttribute('data-dsh-design') || /^(SCRIPT|STYLE|LINK|NOSCRIPT|TEMPLATE)$/.test(el.tagName)
1004
+ let best = null
1005
+ let box = null
1006
+ let area = 0
1007
+ for (const el of [b, ...b.children]) {
1008
+ if (skip(el)) continue
1009
+ const r = el.getBoundingClientRect()
1010
+ if (r.width * r.height > area) { area = r.width * r.height; best = r; box = el }
1011
+ }
1012
+ if (!best) return null
1013
+ // 一样大的几页竖着堆(一个文件里放了几张幻灯片 / 海报):不是一页
1014
+ const kids = [...box.children].filter((k) => !skip(k)).map((k) => k.getBoundingClientRect()).filter((r) => r.width && r.height)
1015
+ if (kids.length >= 2 && kids[0].height >= innerHeight * 0.8 && kids.every((r) => Math.abs(r.width - kids[0].width) < 2 && Math.abs(r.height - kids[0].height) < 2) && kids[1].top >= kids[0].bottom - 1) return null
1016
+ const right = best.right + scrollX
1017
+ const bottom = best.bottom + scrollY
1018
+ const w = Math.ceil(Math.max(best.width, right))
1019
+ const h = Math.ceil(Math.max(best.height, bottom))
1020
+ if (Math.abs(w - innerWidth) <= 1 && Math.abs(h - innerHeight) <= 1) return null
1021
+ const de = document.scrollingElement || document.documentElement
1022
+ return de.scrollWidth > Math.max(innerWidth, right) + 40 || de.scrollHeight > Math.max(innerHeight, bottom) + 40 ? null : [w, h]
1023
+ }
1024
+
996
1025
  function boot(seed) {
997
1026
  if (booted) return
998
1027
  booted = true
@@ -1008,6 +1037,7 @@
1008
1037
  tokens: tokensOf(),
1009
1038
  links: firstLinks(),
1010
1039
  dark: hasDark(),
1040
+ natural: naturalSize(),
1011
1041
  changes: pending ? { count: pending.count, whole: pending.whole } : null,
1012
1042
  })
1013
1043
  postTop()
@@ -4011,7 +4041,7 @@ background:rgba(20,20,22,.86);color:#e8e8ea;font:12px/18px -apple-system,system-
4011
4041
  links: () => linksOf().map(({ path, text }) => ({ path, text })),
4012
4042
  info: () => {
4013
4043
  const meta = document.querySelector('meta[name="dsh-design"]')
4014
- return { title: document.title, meta: meta ? String(meta.getAttribute('content') || '').slice(0, 80) : '', page: Math.max(se().scrollHeight, innerHeight), w: innerWidth, h: innerHeight, topDark: topDark() }
4044
+ return { title: document.title, meta: meta ? String(meta.getAttribute('content') || '').slice(0, 80) : '', page: Math.max(se().scrollHeight, innerHeight), w: innerWidth, h: innerHeight, topDark: topDark(), natural: naturalSize() }
4015
4045
  },
4016
4046
  /** 'end':所有能滚的地方都滚到底;数字 i:主要的那块滚到第 i 屏(相邻两屏留出顶栏、底栏的高度,栏后面的内容下一屏能看到) */
4017
4047
  // 滚完等页面跟上再截图
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-design-studio",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1 test/*.mjs"