dsh-fonttune 0.7.4 → 0.7.6
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/CHANGELOG.en.md +10 -0
- package/CHANGELOG.md +10 -0
- package/lib/client.js +154 -25
- package/lib/index.js +60 -13
- package/lib/shared.cjs +18 -0
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.7.6] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **The plugin did not actually load on some hosts**: the Font tune settings card and the first-frame type stylesheet were missing while the conversation still looked completely normal, so it was easy to miss; the plugin now waits for its dependency to finish loading before it registers, and no longer starts half-loaded.
|
|
9
|
+
|
|
10
|
+
## [0.7.5] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **The floating dot covered the composer in a new session**: before conversation text appeared, the dot could remain over the input area; it now moves to the lower-left of the main content area, clear of both the composer and the sidebar.
|
|
14
|
+
|
|
5
15
|
## [0.7.4] - 2026-10-30
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.7.6] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
- **部分环境下插件启动后没有真正加载**:字体增强的设置卡片与首帧字体样式会缺失,而对话外观看起来一切正常,所以很难发现;现在启动时会等依赖就绪再注册,不再出现这种「看着正常、其实没加载」的状态。
|
|
9
|
+
|
|
10
|
+
## [0.7.5] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
- **新会话中的悬浮圆点遮挡输入框**:对话正文尚未出现时,圆点可能停在输入框内;现在会将这类位置移至主内容区左下角,避开输入框与侧栏。
|
|
14
|
+
|
|
5
15
|
## [0.7.4] - 2026-10-30
|
|
6
16
|
|
|
7
17
|
### 修复
|
package/lib/client.js
CHANGED
|
@@ -1238,6 +1238,23 @@ function floatBoundsFrom(rects, viewport) {
|
|
|
1238
1238
|
return floatBoundsPick(rects, viewport).bounds;
|
|
1239
1239
|
}
|
|
1240
1240
|
|
|
1241
|
+
/**
|
|
1242
|
+
* The fallback region to use when the conversation has no measurable text yet.
|
|
1243
|
+
*
|
|
1244
|
+
* The page's whole-window stand-in starts at x=0, which puts the dot over the left
|
|
1245
|
+
* navigation rail. On a host shell that exposes a main-column rectangle, using that
|
|
1246
|
+
* rectangle as the fallback puts the dot at the content column's left corner while
|
|
1247
|
+
* the conversation itself is mounting. If the shell supplies no usable region, the
|
|
1248
|
+
* viewport stays the fallback.
|
|
1249
|
+
* @param {unknown} region - the main content column, or null when unavailable.
|
|
1250
|
+
* @param {{width: number, height: number}} viewport - the window size.
|
|
1251
|
+
* @returns {{left:number,top:number,right:number,bottom:number,width:number,height:number}}
|
|
1252
|
+
* the clipped content box, or the whole viewport.
|
|
1253
|
+
*/
|
|
1254
|
+
function floatFallbackBounds(region, viewport) {
|
|
1255
|
+
return floatBoundsPick(region === null || region === undefined ? [] : [region], viewport).bounds;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1241
1258
|
/**
|
|
1242
1259
|
* Whether a second reading of the winning rectangle is the same shape as the last.
|
|
1243
1260
|
*
|
|
@@ -4966,6 +4983,7 @@ var shared = {
|
|
|
4966
4983
|
floatBoxOf: floatBoxOf,
|
|
4967
4984
|
floatBoundsPick: floatBoundsPick,
|
|
4968
4985
|
floatBoundsFrom: floatBoundsFrom,
|
|
4986
|
+
floatFallbackBounds: floatFallbackBounds,
|
|
4969
4987
|
sameWatchBounds: sameWatchBounds,
|
|
4970
4988
|
floatTargetsConnected: floatTargetsConnected,
|
|
4971
4989
|
floatQuadrantIn: floatQuadrantIn,
|
|
@@ -11361,9 +11379,9 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11361
11379
|
return null;
|
|
11362
11380
|
};
|
|
11363
11381
|
|
|
11364
|
-
// Whether the last
|
|
11365
|
-
//
|
|
11366
|
-
//
|
|
11382
|
+
// Whether the last bounds reading was a REAL conversation region or a stand-in
|
|
11383
|
+
// (main column, then viewport; see `floatBounds`). A stand-in means "no conversation
|
|
11384
|
+
// to measure right now", not "the conversation is that whole shell column".
|
|
11367
11385
|
var boundsAreReal = false;
|
|
11368
11386
|
|
|
11369
11387
|
/** The window size the bounds are cut to, with the shared fallback. */
|
|
@@ -11381,9 +11399,62 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11381
11399
|
* session had 36. The first in document order is usually an old turn scrolled
|
|
11382
11400
|
* thousands of pixels off screen, and its scroll container can measure zero;
|
|
11383
11401
|
* reading the bounds from that one element alone threw both of its candidates
|
|
11384
|
-
* away and snapped the dot to the
|
|
11402
|
+
* away and snapped the dot to the fallback area instead of the conversation.
|
|
11385
11403
|
* @returns {Array<object>} the prose elements (possibly empty).
|
|
11386
11404
|
*/
|
|
11405
|
+
var EDITABLE_ANCESTOR =
|
|
11406
|
+
"[contenteditable]:not([contenteditable='false']),textarea,input,[role='textbox']";
|
|
11407
|
+
|
|
11408
|
+
/**
|
|
11409
|
+
* Whether a node sits inside something the reader types into.
|
|
11410
|
+
* @param {object} node - the element to test (it counts as its own ancestor).
|
|
11411
|
+
* @returns {boolean} true for the composer and anything inside it.
|
|
11412
|
+
*/
|
|
11413
|
+
var insideEditable = function (node) {
|
|
11414
|
+
try {
|
|
11415
|
+
return node.closest(EDITABLE_ANCESTOR) !== null;
|
|
11416
|
+
} catch (error) {
|
|
11417
|
+
return false;
|
|
11418
|
+
}
|
|
11419
|
+
};
|
|
11420
|
+
|
|
11421
|
+
/**
|
|
11422
|
+
* Whether the dot's stored position lies over a field the reader types into.
|
|
11423
|
+
*
|
|
11424
|
+
* On a fresh session no conversation region is measurable, and a stand-in never
|
|
11425
|
+
* moves a dot that already has a position. A dot saved over the composer (the
|
|
11426
|
+
* reported bug: its region was the input's own box) would therefore stay inside
|
|
11427
|
+
* the input for good. That place is wrong whatever the region is, so this is the
|
|
11428
|
+
* one case the stand-in may relocate.
|
|
11429
|
+
* @returns {boolean} true when the stored square overlaps an editable field.
|
|
11430
|
+
*/
|
|
11431
|
+
var strandedInField = function () {
|
|
11432
|
+
if (pos === null) return false;
|
|
11433
|
+
var left = pos.x;
|
|
11434
|
+
var top = pos.y;
|
|
11435
|
+
var right = pos.x + shared.FLOAT_DOT;
|
|
11436
|
+
var bottom = pos.y + shared.FLOAT_DOT;
|
|
11437
|
+
try {
|
|
11438
|
+
var fields = document.querySelectorAll(EDITABLE_ANCESTOR);
|
|
11439
|
+
for (var index = 0; index < fields.length; index += 1) {
|
|
11440
|
+
var box = fields[index].getBoundingClientRect();
|
|
11441
|
+
if (
|
|
11442
|
+
box.width > 0 &&
|
|
11443
|
+
box.height > 0 &&
|
|
11444
|
+
left < box.right &&
|
|
11445
|
+
right > box.left &&
|
|
11446
|
+
top < box.bottom &&
|
|
11447
|
+
bottom > box.top
|
|
11448
|
+
) {
|
|
11449
|
+
return true;
|
|
11450
|
+
}
|
|
11451
|
+
}
|
|
11452
|
+
} catch (error) {
|
|
11453
|
+
return false;
|
|
11454
|
+
}
|
|
11455
|
+
return false;
|
|
11456
|
+
};
|
|
11457
|
+
|
|
11387
11458
|
var proseElements = function () {
|
|
11388
11459
|
var found = [];
|
|
11389
11460
|
var add = function (nodes) {
|
|
@@ -11398,7 +11469,17 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11398
11469
|
} catch (error) {
|
|
11399
11470
|
// A page without a queryable document keeps the empty list (the window).
|
|
11400
11471
|
}
|
|
11401
|
-
|
|
11472
|
+
// The composer is part of the conversation container on a fresh session, so a
|
|
11473
|
+
// paragraph the input holds matches the conversation selector too. Measured
|
|
11474
|
+
// live: one typed character left a `<p>` in the input, and the dot's region
|
|
11475
|
+
// became the input's own scroll box, which parked the dot inside the input. A
|
|
11476
|
+
// message is never typed into a field, so anything inside one is not the
|
|
11477
|
+
// conversation, whatever class or attribute it carries.
|
|
11478
|
+
var kept = [];
|
|
11479
|
+
for (var at = 0; at < found.length; at += 1) {
|
|
11480
|
+
if (!insideEditable(found[at])) kept.push(found[at]);
|
|
11481
|
+
}
|
|
11482
|
+
return kept;
|
|
11402
11483
|
};
|
|
11403
11484
|
|
|
11404
11485
|
/**
|
|
@@ -11490,25 +11571,71 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
11490
11571
|
return true;
|
|
11491
11572
|
};
|
|
11492
11573
|
|
|
11574
|
+
/**
|
|
11575
|
+
* The main content column while the conversation itself has no measurable prose.
|
|
11576
|
+
*
|
|
11577
|
+
* DSH's shell marks its bottom-row element with a stable data attribute and puts
|
|
11578
|
+
* it directly inside the three-column frame. The frame's direct children are the
|
|
11579
|
+
* left rail, the main column and the right rail; taking the main column's actual
|
|
11580
|
+
* rectangle means its left edge follows an open or collapsed sidebar without a
|
|
11581
|
+
* plugin-specific width guess, and its right edge leaves room for the right rail.
|
|
11582
|
+
* If the shell changes shape or is not present, the viewport remains the fallback.
|
|
11583
|
+
* @returns {object|null} the main column's rectangle, or null when it is not usable.
|
|
11584
|
+
*/
|
|
11585
|
+
var shellMainColumnRect = function () {
|
|
11586
|
+
try {
|
|
11587
|
+
var bottom = document.querySelector("[data-shell-bottom]");
|
|
11588
|
+
var frame = bottom === null ? null : bottom.parentElement;
|
|
11589
|
+
if (frame === null || frame === undefined || frame.children.length < 2) return null;
|
|
11590
|
+
var sidebar = frame.children[0];
|
|
11591
|
+
var main = frame.children[1];
|
|
11592
|
+
if (
|
|
11593
|
+
main === null ||
|
|
11594
|
+
main === undefined ||
|
|
11595
|
+
main.hasAttribute("data-rightbar-col") ||
|
|
11596
|
+
main.hasAttribute("data-shell-bottom") ||
|
|
11597
|
+
main.hasAttribute("data-shell-overlay")
|
|
11598
|
+
) {
|
|
11599
|
+
return null;
|
|
11600
|
+
}
|
|
11601
|
+
var sidebarRect = rectOf(sidebar);
|
|
11602
|
+
var mainRect = rectOf(main);
|
|
11603
|
+
if (
|
|
11604
|
+
sidebarRect === null ||
|
|
11605
|
+
mainRect === null ||
|
|
11606
|
+
!(sidebarRect.width > 0) ||
|
|
11607
|
+
!(mainRect.width > 0) ||
|
|
11608
|
+
!(mainRect.height > 0) ||
|
|
11609
|
+
mainRect.left + 1 < sidebarRect.right
|
|
11610
|
+
) {
|
|
11611
|
+
return null;
|
|
11612
|
+
}
|
|
11613
|
+
return mainRect;
|
|
11614
|
+
} catch (error) {
|
|
11615
|
+
return null;
|
|
11616
|
+
}
|
|
11617
|
+
};
|
|
11618
|
+
|
|
11493
11619
|
/**
|
|
11494
11620
|
* The rectangle the dot snaps to, drags within and reads its own quadrant from.
|
|
11495
11621
|
*
|
|
11496
|
-
* The conversation when it can be found
|
|
11497
|
-
*
|
|
11498
|
-
*
|
|
11622
|
+
* The conversation when it can be found; when no conversation is measurable,
|
|
11623
|
+
* the host's main column stands in so the bottom-left home is just past the left
|
|
11624
|
+
* rail, rather than underneath its avatar. On pages that do not expose a main
|
|
11625
|
+
* column, the whole window remains the fallback.
|
|
11499
11626
|
*
|
|
11500
|
-
* WHICH one answered is remembered in `boundsAreReal`, because the
|
|
11501
|
-
*
|
|
11502
|
-
*
|
|
11503
|
-
*
|
|
11504
|
-
* a page that does have a conversation is exactly the reported "it sits on the
|
|
11505
|
-
* window's left edge instead of the conversation's corner".
|
|
11627
|
+
* WHICH one answered is remembered in `boundsAreReal`, because the main column
|
|
11628
|
+
* is still a stand-in, not a real conversation region: a stored dot only moves
|
|
11629
|
+
* within it when its saved position sits over an editable field (see
|
|
11630
|
+
* `strandedInField`).
|
|
11506
11631
|
* @returns {object} the bounds, in viewport coordinates.
|
|
11507
11632
|
*/
|
|
11508
11633
|
var floatBounds = function () {
|
|
11509
|
-
var
|
|
11634
|
+
var viewport = floatViewport();
|
|
11635
|
+
var picked = shared.floatBoundsPick(floatBoundsRects(), viewport);
|
|
11510
11636
|
boundsAreReal = picked.index >= 0;
|
|
11511
|
-
return picked.bounds;
|
|
11637
|
+
if (boundsAreReal) return picked.bounds;
|
|
11638
|
+
return shared.floatFallbackBounds(shellMainColumnRect(), viewport);
|
|
11512
11639
|
};
|
|
11513
11640
|
|
|
11514
11641
|
/**
|
|
@@ -12064,13 +12191,12 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12064
12191
|
*/
|
|
12065
12192
|
var snapToCorner = function (instant) {
|
|
12066
12193
|
var bounds = floatBounds();
|
|
12067
|
-
// A stand-in region is not a home: the
|
|
12068
|
-
//
|
|
12069
|
-
//
|
|
12070
|
-
// conversation's corner". A dot the document already placed keeps the corner
|
|
12194
|
+
// A stand-in region is not a home: the main-column corner is only where an
|
|
12195
|
+
// editable-field position can be re-homed while the conversation is not
|
|
12196
|
+
// measurable yet. A dot the document already placed keeps the corner
|
|
12071
12197
|
// the last REAL region gave it; the observer settles it again the moment one
|
|
12072
12198
|
// exists (see `reflowIntoBounds`).
|
|
12073
|
-
if (!boundsAreReal && pos !== null) return pos;
|
|
12199
|
+
if (!boundsAreReal && pos !== null && !strandedInField()) return pos;
|
|
12074
12200
|
var snapped;
|
|
12075
12201
|
if (pos !== null) {
|
|
12076
12202
|
snapped = cornerHome(bounds, pos);
|
|
@@ -12212,10 +12338,13 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12212
12338
|
var bounds = floatBounds();
|
|
12213
12339
|
// Nothing real to settle onto: a shut panel on a settings page, or a
|
|
12214
12340
|
// conversation that is not measurable at this instant, leaves the dot where
|
|
12215
|
-
// it is — moving it onto the
|
|
12341
|
+
// it is — moving it onto the fallback corner is the bug, not the fallback.
|
|
12216
12342
|
// With nothing stored at all there is no home to keep, so the stylesheet's
|
|
12217
|
-
// own corner is pinned as before.
|
|
12218
|
-
|
|
12343
|
+
// own corner is pinned as before. The one exception is a dot stored INSIDE a
|
|
12344
|
+
// field the reader types into (see `strandedInField`): that place is wrong
|
|
12345
|
+
// whatever the region is, so it is re-homed to the main column and persisted.
|
|
12346
|
+
var rehomeEditable = !boundsAreReal && pos !== null && strandedInField();
|
|
12347
|
+
if (!boundsAreReal && pos !== null && !rehomeEditable) return false;
|
|
12219
12348
|
var at = pos;
|
|
12220
12349
|
if (at === null) {
|
|
12221
12350
|
var rect = rectOf(host);
|
|
@@ -12260,7 +12389,7 @@ function mountFloatPanel(scope, t, hostCtx) {
|
|
|
12260
12389
|
// told which corner that is, in the same `mutate`. A region that changed
|
|
12261
12390
|
// shape is the caller that has to store the move it just made, wherever the
|
|
12262
12391
|
// dot was standing before (see the note above).
|
|
12263
|
-
if (outside || naming) persistIfDirty();
|
|
12392
|
+
if (outside || naming || rehomeEditable) persistIfDirty();
|
|
12264
12393
|
if (regionMoved === true) persistIfDirty();
|
|
12265
12394
|
return true;
|
|
12266
12395
|
};
|
package/lib/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { createRequire } from "node:module";
|
|
24
24
|
import { dirname, sep } from "node:path";
|
|
25
|
-
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
26
|
// Default import, not named: `shared.cjs` is a CommonJS module, and Node's
|
|
27
27
|
// CJS-to-ESM interop only guarantees `module.exports` as the default binding
|
|
28
28
|
// (statically-detected named exports would break this half at boot).
|
|
@@ -47,26 +47,73 @@ import shared from "./shared.cjs";
|
|
|
47
47
|
* entry (its `node_modules` holds the authoritative copy); anything resolving
|
|
48
48
|
* back inside THIS package is skipped, so the offline suites — which have no
|
|
49
49
|
* host — still fall through to the devDependency.
|
|
50
|
-
*
|
|
50
|
+
*
|
|
51
|
+
* RESOLVE with `require`, LOAD with the ESM loader. The package resolves to its
|
|
52
|
+
* CommonJS build, and that build `require`s `@deepseek-ai/cosmokit`, which ships
|
|
53
|
+
* as ES module only. Requiring it while the host is still loading that same
|
|
54
|
+
* module graph throws `ERR_REQUIRE_ESM_RACE_CONDITION` — "Cannot require() ES
|
|
55
|
+
* Module … because it is not yet fully loaded" — and then the whole host half
|
|
56
|
+
* fails to import: no settings namespace, no first-frame stylesheet, while the
|
|
57
|
+
* browser half keeps working and hides the damage (issue #7, reproduced here).
|
|
58
|
+
* `import()` waits for the graph instead of racing it, and it lands on the same
|
|
59
|
+
* instance: a `.cjs` file loaded through the ESM loader goes through the
|
|
60
|
+
* CommonJS cache, so the host's copy is still the only copy.
|
|
61
|
+
* @returns {Promise<object>} the schemastery export.
|
|
51
62
|
*/
|
|
52
|
-
function loadSchemastery() {
|
|
63
|
+
async function loadSchemastery() {
|
|
53
64
|
const own = dirname(dirname(fileURLToPath(import.meta.url))) + sep;
|
|
54
65
|
const bases = [process.argv[1], fileURLToPath(import.meta.url)];
|
|
55
66
|
for (const base of bases) {
|
|
56
67
|
if (typeof base !== "string" || base === "") continue;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const resolved = resolveSchemastery(createRequire(base), own);
|
|
69
|
+
if (resolved !== null) return importSchemastery(resolved);
|
|
70
|
+
}
|
|
71
|
+
// Nothing outside this package offers it (the offline suites have no host), so
|
|
72
|
+
// the devDependency is the answer — still loaded through the ESM loader.
|
|
73
|
+
const fallback = resolveSchemastery(createRequire(import.meta.url), "");
|
|
74
|
+
if (fallback === null) {
|
|
75
|
+
throw new Error("@deepseek-ai/schemastery is not resolvable from the host or this package");
|
|
76
|
+
}
|
|
77
|
+
return importSchemastery(fallback);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Locate the schema library from one anchor WITHOUT loading it.
|
|
82
|
+
*
|
|
83
|
+
* Only the resolution is anchored to the host: `createRequire(base).resolve()`
|
|
84
|
+
* answers "which file would the host use", while nothing is executed. An answer
|
|
85
|
+
* that points back inside this package is refused, so a linked install cannot
|
|
86
|
+
* shadow the host's copy with this package's devDependency.
|
|
87
|
+
* @param {NodeRequire} from - the resolver anchored at the host entry.
|
|
88
|
+
* @param {string} own - this package's directory prefix, "" to accept anything.
|
|
89
|
+
* @returns {string|null} the file to load, or null when this anchor cannot offer it.
|
|
90
|
+
*/
|
|
91
|
+
function resolveSchemastery(from, own) {
|
|
92
|
+
try {
|
|
93
|
+
const resolved = from.resolve("@deepseek-ai/schemastery");
|
|
94
|
+
if (typeof resolved !== "string" || resolved === "") return null;
|
|
95
|
+
if (own !== "" && resolved.startsWith(own)) return null;
|
|
96
|
+
return resolved;
|
|
97
|
+
} catch {
|
|
98
|
+
// Not resolvable from that anchor (or has no `require` entry): try the next.
|
|
99
|
+
return null;
|
|
65
100
|
}
|
|
66
|
-
return createRequire(import.meta.url)("@deepseek-ai/schemastery");
|
|
67
101
|
}
|
|
68
102
|
|
|
69
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Load one resolved file through the ESM loader.
|
|
105
|
+
*
|
|
106
|
+
* A CommonJS build arrives as its `default` binding, which is the module's own
|
|
107
|
+
* `module.exports`; a build that is already ESM answers with itself.
|
|
108
|
+
* @param {string} resolved - an absolute path, from `resolveSchemastery`.
|
|
109
|
+
* @returns {Promise<object>} the schemastery export.
|
|
110
|
+
*/
|
|
111
|
+
async function importSchemastery(resolved) {
|
|
112
|
+
const loaded = await import(pathToFileURL(resolved).href);
|
|
113
|
+
return loaded.default ?? loaded;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const z = await loadSchemastery();
|
|
70
117
|
|
|
71
118
|
const {
|
|
72
119
|
buildFontCss,
|
package/lib/shared.cjs
CHANGED
|
@@ -1229,6 +1229,23 @@ function floatBoundsFrom(rects, viewport) {
|
|
|
1229
1229
|
return floatBoundsPick(rects, viewport).bounds;
|
|
1230
1230
|
}
|
|
1231
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* The fallback region to use when the conversation has no measurable text yet.
|
|
1234
|
+
*
|
|
1235
|
+
* The page's whole-window stand-in starts at x=0, which puts the dot over the left
|
|
1236
|
+
* navigation rail. On a host shell that exposes a main-column rectangle, using that
|
|
1237
|
+
* rectangle as the fallback puts the dot at the content column's left corner while
|
|
1238
|
+
* the conversation itself is mounting. If the shell supplies no usable region, the
|
|
1239
|
+
* viewport stays the fallback.
|
|
1240
|
+
* @param {unknown} region - the main content column, or null when unavailable.
|
|
1241
|
+
* @param {{width: number, height: number}} viewport - the window size.
|
|
1242
|
+
* @returns {{left:number,top:number,right:number,bottom:number,width:number,height:number}}
|
|
1243
|
+
* the clipped content box, or the whole viewport.
|
|
1244
|
+
*/
|
|
1245
|
+
function floatFallbackBounds(region, viewport) {
|
|
1246
|
+
return floatBoundsPick(region === null || region === undefined ? [] : [region], viewport).bounds;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1232
1249
|
/**
|
|
1233
1250
|
* Whether a second reading of the winning rectangle is the same shape as the last.
|
|
1234
1251
|
*
|
|
@@ -4957,6 +4974,7 @@ var shared = {
|
|
|
4957
4974
|
floatBoxOf: floatBoxOf,
|
|
4958
4975
|
floatBoundsPick: floatBoundsPick,
|
|
4959
4976
|
floatBoundsFrom: floatBoundsFrom,
|
|
4977
|
+
floatFallbackBounds: floatFallbackBounds,
|
|
4960
4978
|
sameWatchBounds: sameWatchBounds,
|
|
4961
4979
|
floatTargetsConnected: floatTargetsConnected,
|
|
4962
4980
|
floatQuadrantIn: floatQuadrantIn,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "Font plugin for the DeepSeek Harness Web GUI: the conversation gets its own font, size, line height and weight, the interface font and weight follow it, code keeps its own axis with ligature control, and whole setups save as presets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "icon.svg",
|