dsh-fonttune 0.2.2 → 0.2.4
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 +44 -0
- package/CHANGELOG.md +44 -0
- package/README.en.md +20 -5
- package/README.md +20 -5
- package/lib/client.js +913 -312
- package/lib/index.js +95 -11
- package/lib/shared.cjs +285 -145
- package/package.json +11 -6
package/lib/shared.cjs
CHANGED
|
@@ -152,9 +152,6 @@ var LIGATURES_DEFAULT = 0;
|
|
|
152
152
|
var LIGATURES_ON = 1;
|
|
153
153
|
var LIGATURES_OFF = 2;
|
|
154
154
|
|
|
155
|
-
/** Marker on both injected style tags, used for scoping every rule we write. */
|
|
156
|
-
var MARKER = "dfp";
|
|
157
|
-
|
|
158
155
|
/** `data-plugin-css` value of the tag applying the configuration. */
|
|
159
156
|
var STYLE_TAG = "dsh-fonttune";
|
|
160
157
|
|
|
@@ -187,20 +184,18 @@ var DEFAULTS = {
|
|
|
187
184
|
};
|
|
188
185
|
|
|
189
186
|
/**
|
|
190
|
-
* The axis fields a preset snapshots
|
|
191
|
-
* the storage fields themselves.
|
|
187
|
+
* The axis fields a preset snapshots and the light/dark sets are compared
|
|
188
|
+
* over: everything the card edits, excluding the storage fields themselves.
|
|
192
189
|
*/
|
|
193
190
|
var VALUE_FIELDS = [
|
|
194
191
|
SANS_FIELD,
|
|
195
192
|
STACK_DIALOG_FIELD,
|
|
196
193
|
MONO_FIELD,
|
|
197
|
-
SIZE_FIELD,
|
|
198
194
|
SIZE_DIALOG_FIELD,
|
|
199
195
|
CODE_SIZE_FIELD,
|
|
200
196
|
WEIGHT_FIELD,
|
|
201
197
|
WEIGHT_DIALOG_FIELD,
|
|
202
198
|
CODE_WEIGHT_FIELD,
|
|
203
|
-
LINE_HEIGHT_FIELD,
|
|
204
199
|
LINE_HEIGHT_DIALOG_FIELD,
|
|
205
200
|
CODE_LINE_HEIGHT_FIELD,
|
|
206
201
|
LIGATURES_FIELD,
|
|
@@ -210,6 +205,25 @@ var VALUE_FIELDS = [
|
|
|
210
205
|
UI_FOLLOWS_FIELD,
|
|
211
206
|
];
|
|
212
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Axes that are still stored but no longer render: the interface's own size
|
|
210
|
+
* and line-height.
|
|
211
|
+
*
|
|
212
|
+
* DSH 0.1.5-rc.2 publishes no interface size or line-height hook at all (the
|
|
213
|
+
* measurement lives in `buildAxisCss`), so these two fields inject nothing.
|
|
214
|
+
* A document written by 0.1.x still carries them, so they keep their schema
|
|
215
|
+
* entries and stay part of normalization — but they are not value axes: a
|
|
216
|
+
* preset no longer snapshots a dead field, and a light/dark pair that differs
|
|
217
|
+
* only here no longer counts as two sets. The card's "reset every axis" clears
|
|
218
|
+
* them too, so a value nothing shows cannot hide in a document forever.
|
|
219
|
+
*/
|
|
220
|
+
var RETIRED_FIELDS = [SIZE_FIELD, LINE_HEIGHT_FIELD];
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Every durable axis field, live and retired: what normalization covers.
|
|
224
|
+
*/
|
|
225
|
+
var DURABLE_FIELDS = VALUE_FIELDS.concat(RETIRED_FIELDS);
|
|
226
|
+
|
|
213
227
|
/**
|
|
214
228
|
* Longest accepted font stack, in characters (mirrored by the host schema).
|
|
215
229
|
*/
|
|
@@ -372,8 +386,8 @@ function normalizeField(field, value) {
|
|
|
372
386
|
function normalizeConfig(value) {
|
|
373
387
|
var source = value !== null && typeof value === "object" ? value : {};
|
|
374
388
|
var config = {};
|
|
375
|
-
for (var index = 0; index <
|
|
376
|
-
var field =
|
|
389
|
+
for (var index = 0; index < DURABLE_FIELDS.length; index += 1) {
|
|
390
|
+
var field = DURABLE_FIELDS[index];
|
|
377
391
|
config[field] = normalizeField(field, source[field]);
|
|
378
392
|
}
|
|
379
393
|
config[PER_THEME_FIELD] = toBool(source[PER_THEME_FIELD]);
|
|
@@ -1023,17 +1037,6 @@ function isShorthandName(name) {
|
|
|
1023
1037
|
return true;
|
|
1024
1038
|
}
|
|
1025
1039
|
|
|
1026
|
-
/**
|
|
1027
|
-
* Whether one token (name + value) is a `font` shorthand this plugin may
|
|
1028
|
-
* rewrite: a bare `--dsw-font-…` name whose value parses as a shorthand.
|
|
1029
|
-
* @param {string} name - custom property name.
|
|
1030
|
-
* @param {string} value - the token's untouched value.
|
|
1031
|
-
* @returns {boolean}
|
|
1032
|
-
*/
|
|
1033
|
-
function isShorthandToken(name, value) {
|
|
1034
|
-
return isShorthandName(name) && parseShorthand(value) !== null;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
1040
|
/**
|
|
1038
1041
|
* Whether a token is one this plugin reads and re-declares at all.
|
|
1039
1042
|
* @param {string} name - custom property name including the leading dashes.
|
|
@@ -1062,35 +1065,6 @@ function tokenNames(baseTokens) {
|
|
|
1062
1065
|
return names;
|
|
1063
1066
|
}
|
|
1064
1067
|
|
|
1065
|
-
/**
|
|
1066
|
-
* The interface PART size tokens: everything scalable except the code and
|
|
1067
|
-
* markdown families and the delta tokens (which the dialog offset shifts
|
|
1068
|
-
* additively, never multiplicatively).
|
|
1069
|
-
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1070
|
-
* @returns {string[]} the names the interface offset re-declares.
|
|
1071
|
-
*/
|
|
1072
|
-
function bodyTokenNames(baseTokens) {
|
|
1073
|
-
return tokenNames(baseTokens).filter(function (name) {
|
|
1074
|
-
return (
|
|
1075
|
-
!isCodeToken(name) &&
|
|
1076
|
-
!isMarkdownToken(name) &&
|
|
1077
|
-
!isDeltaToken(name) &&
|
|
1078
|
-
!isContentToken(name)
|
|
1079
|
-
);
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
* The interface line-height PART tokens (non-code, non-markdown).
|
|
1085
|
-
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1086
|
-
* @returns {string[]}
|
|
1087
|
-
*/
|
|
1088
|
-
function uiLineTokenNames(baseTokens) {
|
|
1089
|
-
return bodyTokenNames(baseTokens).filter(function (name) {
|
|
1090
|
-
return name.indexOf("-line-height") >= 0;
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
1068
|
/**
|
|
1095
1069
|
* The markdown (dialog) PART tokens: sizes and line heights of the markdown
|
|
1096
1070
|
* family, excluding code.
|
|
@@ -1101,30 +1075,6 @@ function markdownTokenNames(baseTokens) {
|
|
|
1101
1075
|
return tokenNames(baseTokens).filter(isMarkdownToken);
|
|
1102
1076
|
}
|
|
1103
1077
|
|
|
1104
|
-
/**
|
|
1105
|
-
* The markdown content-size DELTA tokens present in a base map.
|
|
1106
|
-
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1107
|
-
* @returns {string[]}
|
|
1108
|
-
*/
|
|
1109
|
-
function deltaTokenNames(baseTokens) {
|
|
1110
|
-
if (baseTokens === null || typeof baseTokens !== "object") return [];
|
|
1111
|
-
return Object.keys(baseTokens).filter(isDeltaToken);
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* The code PART tokens: the `font` shorthands' size/height parts, when the
|
|
1116
|
-
* live stylesheets declare them. The shorthands themselves are handled by
|
|
1117
|
-
* `codeShorthandTokens`, so they are excluded here to avoid double
|
|
1118
|
-
* declarations.
|
|
1119
|
-
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1120
|
-
* @returns {string[]}
|
|
1121
|
-
*/
|
|
1122
|
-
function codeTokenNames(baseTokens) {
|
|
1123
|
-
return tokenNames(baseTokens).filter(function (name) {
|
|
1124
|
-
return isCodeToken(name) && !isShorthandName(name);
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
1078
|
/**
|
|
1129
1079
|
* Every `font` shorthand token in a base map that parses, sorted by name
|
|
1130
1080
|
* length (families before variants, mirroring the part-token order).
|
|
@@ -1157,17 +1107,6 @@ function markdownShorthandTokens(baseTokens) {
|
|
|
1157
1107
|
});
|
|
1158
1108
|
}
|
|
1159
1109
|
|
|
1160
|
-
/**
|
|
1161
|
-
* The interface (non-markdown, non-code) `font` shorthand tokens.
|
|
1162
|
-
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1163
|
-
* @returns {{name: string, parts: object}[]}
|
|
1164
|
-
*/
|
|
1165
|
-
function uiShorthandTokens(baseTokens) {
|
|
1166
|
-
return shorthandTokens(baseTokens).filter(function (entry) {
|
|
1167
|
-
return !isMarkdownToken(entry.name) && !isCodeToken(entry.name);
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
1110
|
/**
|
|
1172
1111
|
* The code `font` shorthand tokens.
|
|
1173
1112
|
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
@@ -1183,6 +1122,66 @@ function codeShorthandTokens(baseTokens) {
|
|
|
1183
1122
|
* selectors
|
|
1184
1123
|
* ------------------------------------------------------------------ */
|
|
1185
1124
|
|
|
1125
|
+
/**
|
|
1126
|
+
* Build one exclusion that keeps a surface out of a rule.
|
|
1127
|
+
*
|
|
1128
|
+
* The whole list goes into a single `:not(…)`. The long form —
|
|
1129
|
+
* `:not(pre):not(pre *)…` — repeats `:not(` and `)` per argument, and that
|
|
1130
|
+
* chain is copied into both dialog alternatives and the interface rule, so the
|
|
1131
|
+
* compact form saves roughly 120 bytes per copy.
|
|
1132
|
+
*
|
|
1133
|
+
* The trade is specificity: `:not(a):not(b)` SUMS its arguments while
|
|
1134
|
+
* `:not(a, b)` takes the most specific one. Nothing here depends on that sum.
|
|
1135
|
+
* Every rule below carries `!important`, which already beats the theme's
|
|
1136
|
+
* non-important class rules whatever the specificity; the shipped DSH 0.1.5
|
|
1137
|
+
* assets hold exactly one `!important` typography declaration
|
|
1138
|
+
* (`font-size` on inline markdown code, and no size declaration is ever
|
|
1139
|
+
* emitted from here); and the rules that could collide with each other are
|
|
1140
|
+
* held apart by these very exclusions rather than by their weight. Verified
|
|
1141
|
+
* against the shipped bundle and every installed plugin: no other
|
|
1142
|
+
* `!important` `font-weight`/`font-family` rule exists to lose to.
|
|
1143
|
+
*
|
|
1144
|
+
* @param {string[]} selectors - the selectors to exclude.
|
|
1145
|
+
* @returns {string} one `:not(…)` carrying the whole list.
|
|
1146
|
+
*/
|
|
1147
|
+
function excludeAll(selectors) {
|
|
1148
|
+
return ":not(" + selectors.join(",") + ")";
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* A code surface, and its descendants: a highlight `<span>` inside a `<pre>`
|
|
1153
|
+
* is not a `pre`, so an exclusion has to reach through ancestry.
|
|
1154
|
+
*/
|
|
1155
|
+
var CODE_SURFACE_SELECTORS = [
|
|
1156
|
+
"pre",
|
|
1157
|
+
"pre *",
|
|
1158
|
+
"code",
|
|
1159
|
+
"code *",
|
|
1160
|
+
"kbd",
|
|
1161
|
+
"kbd *",
|
|
1162
|
+
"samp",
|
|
1163
|
+
"samp *",
|
|
1164
|
+
"var",
|
|
1165
|
+
"var *",
|
|
1166
|
+
"tt",
|
|
1167
|
+
"tt *",
|
|
1168
|
+
"textarea",
|
|
1169
|
+
"textarea *",
|
|
1170
|
+
'[class*="code" i]',
|
|
1171
|
+
'[class*="code" i] *',
|
|
1172
|
+
'[class*="terminal" i]',
|
|
1173
|
+
'[class*="terminal" i] *',
|
|
1174
|
+
];
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Everything a code surface must be excluded from a DIALOG-scoped rule: the
|
|
1178
|
+
* code elements themselves AND their descendants (a highlight `<span>` inside
|
|
1179
|
+
* a `<pre>` is not a `pre`, so the exclusion has to reach through ancestry
|
|
1180
|
+
* with a complex `:not()` argument — supported by the Chromium line DSH
|
|
1181
|
+
* targets).
|
|
1182
|
+
*/
|
|
1183
|
+
var CODE_EXCLUDES = excludeAll(CODE_SURFACE_SELECTORS);
|
|
1184
|
+
|
|
1186
1185
|
/**
|
|
1187
1186
|
* What counts as code text for the code axes.
|
|
1188
1187
|
*
|
|
@@ -1221,38 +1220,6 @@ var CODE_SELECTOR = [
|
|
|
1221
1220
|
})
|
|
1222
1221
|
.join(",");
|
|
1223
1222
|
|
|
1224
|
-
/**
|
|
1225
|
-
* Everything a code surface must be excluded from a DIALOG-scoped rule:
|
|
1226
|
-
* the code elements themselves AND their descendants (a highlight `<span>`
|
|
1227
|
-
* inside a `<pre>` is not a `pre`, so the exclusion has to reach through
|
|
1228
|
-
* ancestry with complex `:not()` arguments — supported by the Chromium line
|
|
1229
|
-
* DSH targets).
|
|
1230
|
-
*/
|
|
1231
|
-
var CODE_EXCLUDES = [
|
|
1232
|
-
"pre",
|
|
1233
|
-
"pre *",
|
|
1234
|
-
"code",
|
|
1235
|
-
"code *",
|
|
1236
|
-
"kbd",
|
|
1237
|
-
"kbd *",
|
|
1238
|
-
"samp",
|
|
1239
|
-
"samp *",
|
|
1240
|
-
"var",
|
|
1241
|
-
"var *",
|
|
1242
|
-
"tt",
|
|
1243
|
-
"tt *",
|
|
1244
|
-
"textarea",
|
|
1245
|
-
"textarea *",
|
|
1246
|
-
'[class*="code" i]',
|
|
1247
|
-
'[class*="code" i] *',
|
|
1248
|
-
'[class*="terminal" i]',
|
|
1249
|
-
'[class*="terminal" i] *',
|
|
1250
|
-
]
|
|
1251
|
-
.map(function (selector) {
|
|
1252
|
-
return ":not(" + selector + ")";
|
|
1253
|
-
})
|
|
1254
|
-
.join("");
|
|
1255
|
-
|
|
1256
1223
|
/**
|
|
1257
1224
|
* The dialog surface: DSH renders the conversation markdown in a CSS-module
|
|
1258
1225
|
* container whose class hashes per release (`_markdown_<hash>_…`). A
|
|
@@ -1284,11 +1251,57 @@ var DARK_ATTR = "body[data-ds-dark-theme]";
|
|
|
1284
1251
|
* interface's own preview stays inside the rule, and `dfp-previewCode` is a
|
|
1285
1252
|
* code surface like any other.
|
|
1286
1253
|
*/
|
|
1287
|
-
var INTERFACE_EXCLUDES =
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1254
|
+
var INTERFACE_EXCLUDES = excludeAll(
|
|
1255
|
+
CODE_SURFACE_SELECTORS.concat([
|
|
1256
|
+
".cm-editor",
|
|
1257
|
+
".cm-editor *",
|
|
1258
|
+
".dfp-previewCode",
|
|
1259
|
+
".dfp-previewCode *",
|
|
1260
|
+
'[class*="_markdown_" i]',
|
|
1261
|
+
'[class*="_markdown_" i] *',
|
|
1262
|
+
".dfp-previewDialog",
|
|
1263
|
+
".dfp-previewDialog *",
|
|
1264
|
+
])
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Split a selector list on its top-level commas only.
|
|
1269
|
+
*
|
|
1270
|
+
* The exclusions hold a selector LIST inside `:not(…)`, so a plain
|
|
1271
|
+
* `split(",")` would cut a selector in half — and the per-theme rules prefix
|
|
1272
|
+
* every selector in the list.
|
|
1273
|
+
* @param {string} selectorList - a comma-separated selector list.
|
|
1274
|
+
* @returns {string[]} the top-level parts, verbatim.
|
|
1275
|
+
*/
|
|
1276
|
+
function splitSelectorList(selectorList) {
|
|
1277
|
+
var parts = [];
|
|
1278
|
+
var current = "";
|
|
1279
|
+
var depth = 0;
|
|
1280
|
+
var quote = "";
|
|
1281
|
+
for (var index = 0; index < selectorList.length; index += 1) {
|
|
1282
|
+
var character = selectorList[index];
|
|
1283
|
+
if (quote !== "") {
|
|
1284
|
+
current += character;
|
|
1285
|
+
if (character === quote) quote = "";
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
if (character === '"' || character === "'") {
|
|
1289
|
+
quote = character;
|
|
1290
|
+
current += character;
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (character === "(" || character === "[") depth += 1;
|
|
1294
|
+
if (character === ")" || character === "]") depth -= 1;
|
|
1295
|
+
if (character === "," && depth === 0) {
|
|
1296
|
+
parts.push(current);
|
|
1297
|
+
current = "";
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
current += character;
|
|
1301
|
+
}
|
|
1302
|
+
parts.push(current);
|
|
1303
|
+
return parts;
|
|
1304
|
+
}
|
|
1292
1305
|
|
|
1293
1306
|
/**
|
|
1294
1307
|
* Prefix every selector in a comma list, for the per-theme rules.
|
|
@@ -1297,7 +1310,7 @@ var INTERFACE_EXCLUDES =
|
|
|
1297
1310
|
* @returns {string} the prefixed list.
|
|
1298
1311
|
*/
|
|
1299
1312
|
function prefixSelector(selectorList, prefix) {
|
|
1300
|
-
var parts = selectorList
|
|
1313
|
+
var parts = splitSelectorList(selectorList);
|
|
1301
1314
|
var out = [];
|
|
1302
1315
|
for (var index = 0; index < parts.length; index += 1) {
|
|
1303
1316
|
out.push(prefix + " " + parts[index]);
|
|
@@ -1459,9 +1472,13 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1459
1472
|
*/
|
|
1460
1473
|
function pushScopedCodeFamily() {
|
|
1461
1474
|
var scopedCode = [];
|
|
1462
|
-
var surfaces = CODE_SELECTOR
|
|
1475
|
+
var surfaces = splitSelectorList(CODE_SELECTOR);
|
|
1463
1476
|
for (var cIndex = 0; cIndex < surfaces.length; cIndex += 1) {
|
|
1464
|
-
|
|
1477
|
+
var scoped = '[class*="_markdown_" i] ' + surfaces[cIndex];
|
|
1478
|
+
// The dark variant has to carry the theme attribute like every other
|
|
1479
|
+
// rule here, or a per-theme set would re-assert the LIGHT code family
|
|
1480
|
+
// inside the dark dialog.
|
|
1481
|
+
scopedCode.push(isDark ? DARK_ATTR + " " + scoped : scoped);
|
|
1465
1482
|
}
|
|
1466
1483
|
declarations.push(scopedCode.join(",") + "{font-family:" + mono + " !important}");
|
|
1467
1484
|
}
|
|
@@ -1558,8 +1575,8 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1558
1575
|
// (secondary size, both deltas, delta-based heading sizes, heights) at their
|
|
1559
1576
|
// body-computed values. The offset is therefore added once, at the source —
|
|
1560
1577
|
// `--dsh-content-font-size` becomes "official base + offset" — and the whole
|
|
1561
|
-
// ladder follows by derivation, with nothing counted twice. The
|
|
1562
|
-
// axis never touches this chain
|
|
1578
|
+
// ladder follows by derivation, with nothing counted twice. The retired
|
|
1579
|
+
// interface size axis never touches this chain at all, so the conversation
|
|
1563
1580
|
// moves only when this axis says so, and a dialog that FOLLOWS the interface
|
|
1564
1581
|
// follows it through this value.
|
|
1565
1582
|
var dialogOffsetValue = axis[SIZE_DIALOG_FIELD];
|
|
@@ -1971,18 +1988,137 @@ var PRESETS = {
|
|
|
1971
1988
|
};
|
|
1972
1989
|
|
|
1973
1990
|
/**
|
|
1974
|
-
*
|
|
1975
|
-
* height inside one simple `size/height <family>` shorthand.
|
|
1976
|
-
* @param {string} value - a value shaped like `11px/19px var(--ds-font-family-code)`.
|
|
1977
|
-
* @param {number} scale - the ratio to apply.
|
|
1978
|
-
* @returns {string|null} the scaled shorthand, or null when the shape differs.
|
|
1991
|
+
* The curated groups, in the order the picker lists them.
|
|
1979
1992
|
*/
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1993
|
+
var PRESET_GROUPS = [
|
|
1994
|
+
{ label: "stack.groupMono", families: PRESETS.mono },
|
|
1995
|
+
{ label: "stack.groupCjk", families: PRESETS.cjk },
|
|
1996
|
+
{ label: "stack.groupLatin", families: PRESETS.latin },
|
|
1997
|
+
{ label: "stack.groupGeneric", families: PRESETS.generic },
|
|
1998
|
+
];
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* How many enumerated families the "installed on this machine" group shows.
|
|
2002
|
+
* The curated groups are the short list; this one is the complete one, so the
|
|
2003
|
+
* cap is only a guard against a machine with an absurd number of families.
|
|
2004
|
+
*/
|
|
2005
|
+
var MAX_LOCAL_FONTS = 240;
|
|
2006
|
+
|
|
2007
|
+
/**
|
|
2008
|
+
* Build the family picker's groups.
|
|
2009
|
+
*
|
|
2010
|
+
* The curated groups are ALWAYS listed: whether or not the browser enumerated
|
|
2011
|
+
* local fonts, and whether or not a search is running. The old shape skipped
|
|
2012
|
+
* them whenever a working catalog met a non-empty query, so a curated family
|
|
2013
|
+
* this machine has not installed could not be found by name at all. Every
|
|
2014
|
+
* family is listed in exactly one group — the local enumeration gives way to
|
|
2015
|
+
* the curated headings rather than repeating what they already offer.
|
|
2016
|
+
*
|
|
2017
|
+
* @param {object} input - the picker's state.
|
|
2018
|
+
* @param {string[]} [input.stack] - the families the stored value names.
|
|
2019
|
+
* @param {boolean} [input.single] - single-pick mode (a West/CJK slot).
|
|
2020
|
+
* @param {string} [input.query] - the search text.
|
|
2021
|
+
* @param {{status: string, families: string[]}} [input.catalog] - enumeration.
|
|
2022
|
+
* @param {number} [input.maxLocal] - cap on the local group's rows.
|
|
2023
|
+
* @returns {Array<{label: string, families: string[]}>} the groups, in the
|
|
2024
|
+
* order the picker displays them.
|
|
2025
|
+
*/
|
|
2026
|
+
function pickerGroups(input) {
|
|
2027
|
+
var stack = Array.isArray(input.stack) ? input.stack : [];
|
|
2028
|
+
var single = input.single === true;
|
|
2029
|
+
var query = typeof input.query === "string" ? input.query : "";
|
|
2030
|
+
var catalog =
|
|
2031
|
+
input.catalog && typeof input.catalog === "object"
|
|
2032
|
+
? input.catalog
|
|
2033
|
+
: { status: "loading", families: [] };
|
|
2034
|
+
var maxLocal = typeof input.maxLocal === "number" ? input.maxLocal : MAX_LOCAL_FONTS;
|
|
2035
|
+
var needle = query.trim().toLowerCase();
|
|
2036
|
+
var index;
|
|
2037
|
+
var matches = function (name) {
|
|
2038
|
+
return needle === "" || String(name).toLowerCase().indexOf(needle) >= 0;
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
// Which families the stack already names. In single mode the stack IS the
|
|
2042
|
+
// one pick, and it stays visible in the curated groups (with its check mark)
|
|
2043
|
+
// so the reader can see where it sits among its own kind.
|
|
2044
|
+
var inStack = {};
|
|
2045
|
+
if (!single) {
|
|
2046
|
+
for (index = 0; index < stack.length; index += 1) {
|
|
2047
|
+
inStack[String(stack[index]).toLowerCase()] = true;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// The curated groups are built FIRST, and the local list gives way to them:
|
|
2052
|
+
// they are the short list the reader scans, and on a real Windows machine
|
|
2053
|
+
// most of the Latin group is installed, so letting the local list win would
|
|
2054
|
+
// empty the curated headings. Everything lands in exactly one group.
|
|
2055
|
+
var curatedLower = {};
|
|
2056
|
+
var curatedGroups = [];
|
|
2057
|
+
for (index = 0; index < PRESET_GROUPS.length; index += 1) {
|
|
2058
|
+
var curated = PRESET_GROUPS[index];
|
|
2059
|
+
var rows = [];
|
|
2060
|
+
for (var nameIndex = 0; nameIndex < curated.families.length; nameIndex += 1) {
|
|
2061
|
+
var name = sanitizeFamily(curated.families[nameIndex]);
|
|
2062
|
+
if (name === "" || !matches(name)) continue;
|
|
2063
|
+
var key = name.toLowerCase();
|
|
2064
|
+
if (inStack[key] === true || curatedLower[key] === true) continue;
|
|
2065
|
+
curatedLower[key] = true;
|
|
2066
|
+
rows.push(name);
|
|
2067
|
+
}
|
|
2068
|
+
if (rows.length > 0) curatedGroups.push({ label: curated.label, families: rows });
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// Then the enumeration: the complete list, minus everything already offered
|
|
2072
|
+
// by a heading above it.
|
|
2073
|
+
var localRows = [];
|
|
2074
|
+
if (catalog.status === "ready") {
|
|
2075
|
+
var locals = Array.isArray(catalog.families) ? catalog.families : [];
|
|
2076
|
+
for (index = 0; index < locals.length; index += 1) {
|
|
2077
|
+
var localName = sanitizeFamily(locals[index]);
|
|
2078
|
+
if (localName === "" || !matches(localName)) continue;
|
|
2079
|
+
var localKey = localName.toLowerCase();
|
|
2080
|
+
if (inStack[localKey] === true || curatedLower[localKey] === true) continue;
|
|
2081
|
+
curatedLower[localKey] = true;
|
|
2082
|
+
localRows.push(localName);
|
|
2083
|
+
if (localRows.length >= maxLocal) break;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
var groups = [];
|
|
2088
|
+
var selectedRows = [];
|
|
2089
|
+
for (index = 0; index < stack.length; index += 1) {
|
|
2090
|
+
if (matches(stack[index])) selectedRows.push(stack[index]);
|
|
2091
|
+
}
|
|
2092
|
+
if (selectedRows.length > 0) {
|
|
2093
|
+
groups.push({ label: "stack.groupSelected", families: selectedRows });
|
|
2094
|
+
}
|
|
2095
|
+
for (index = 0; index < curatedGroups.length; index += 1) {
|
|
2096
|
+
groups.push(curatedGroups[index]);
|
|
2097
|
+
}
|
|
2098
|
+
if (localRows.length > 0) {
|
|
2099
|
+
groups.push({ label: "stack.groupLocal", families: localRows });
|
|
2100
|
+
}
|
|
2101
|
+
return groups;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
/**
|
|
2105
|
+
* Whether a cached font-enumeration verdict may be asked for again.
|
|
2106
|
+
*
|
|
2107
|
+
* A working enumeration is final for the session, and a browser that has no
|
|
2108
|
+
* such API at all will never grow one — but a refusal is not: the user can
|
|
2109
|
+
* grant the font permission from the browser's prompt or its site settings.
|
|
2110
|
+
* Retrying is cheap, because a still-blocked permission is rejected instantly
|
|
2111
|
+
* instead of prompting again.
|
|
2112
|
+
*
|
|
2113
|
+
* @param {{status?: string, at?: number, permanent?: boolean}|null} cache - the cached verdict.
|
|
2114
|
+
* @param {number} now - the current time, in milliseconds.
|
|
2115
|
+
* @param {number} retryMs - how long a refusal is trusted.
|
|
2116
|
+
* @returns {boolean} true when the enumeration should run again.
|
|
2117
|
+
*/
|
|
2118
|
+
function catalogRefreshDue(cache, now, retryMs) {
|
|
2119
|
+
if (cache === null || cache === undefined) return true;
|
|
2120
|
+
if (cache.permanent === true || cache.status === "ready") return false;
|
|
2121
|
+
return now - (cache.at || 0) >= retryMs;
|
|
1986
2122
|
}
|
|
1987
2123
|
|
|
1988
2124
|
var shared = {
|
|
@@ -2009,6 +2145,8 @@ var shared = {
|
|
|
2009
2145
|
ACTIVE_PRESET_FIELD: ACTIVE_PRESET_FIELD,
|
|
2010
2146
|
UI_FOLLOWS_FIELD: UI_FOLLOWS_FIELD,
|
|
2011
2147
|
VALUE_FIELDS: VALUE_FIELDS,
|
|
2148
|
+
RETIRED_FIELDS: RETIRED_FIELDS,
|
|
2149
|
+
DURABLE_FIELDS: DURABLE_FIELDS,
|
|
2012
2150
|
SIZE_MIN: SIZE_MIN,
|
|
2013
2151
|
SIZE_MAX: SIZE_MAX,
|
|
2014
2152
|
WEIGHT_MIN: WEIGHT_MIN,
|
|
@@ -2021,7 +2159,6 @@ var shared = {
|
|
|
2021
2159
|
LIGATURES_DEFAULT: LIGATURES_DEFAULT,
|
|
2022
2160
|
LIGATURES_ON: LIGATURES_ON,
|
|
2023
2161
|
LIGATURES_OFF: LIGATURES_OFF,
|
|
2024
|
-
MARKER: MARKER,
|
|
2025
2162
|
STYLE_TAG: STYLE_TAG,
|
|
2026
2163
|
CARD_STYLE_TAG: CARD_STYLE_TAG,
|
|
2027
2164
|
DEFAULTS: DEFAULTS,
|
|
@@ -2032,7 +2169,17 @@ var shared = {
|
|
|
2032
2169
|
MAX_PRESET_NAME: MAX_PRESET_NAME,
|
|
2033
2170
|
FALLBACK_TOKENS: FALLBACK_TOKENS,
|
|
2034
2171
|
PRESETS: PRESETS,
|
|
2172
|
+
PRESET_GROUPS: PRESET_GROUPS,
|
|
2173
|
+
MAX_LOCAL_FONTS: MAX_LOCAL_FONTS,
|
|
2174
|
+
pickerGroups: pickerGroups,
|
|
2175
|
+
catalogRefreshDue: catalogRefreshDue,
|
|
2035
2176
|
MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
|
|
2177
|
+
CODE_SURFACE_SELECTORS: CODE_SURFACE_SELECTORS,
|
|
2178
|
+
CODE_EXCLUDES: CODE_EXCLUDES,
|
|
2179
|
+
INTERFACE_EXCLUDES: INTERFACE_EXCLUDES,
|
|
2180
|
+
excludeAll: excludeAll,
|
|
2181
|
+
splitSelectorList: splitSelectorList,
|
|
2182
|
+
prefixSelector: prefixSelector,
|
|
2036
2183
|
DARK_ATTR: DARK_ATTR,
|
|
2037
2184
|
sanitize: sanitize,
|
|
2038
2185
|
sanitizeFamily: sanitizeFamily,
|
|
@@ -2063,19 +2210,12 @@ var shared = {
|
|
|
2063
2210
|
isMarkdownToken: isMarkdownToken,
|
|
2064
2211
|
isDeltaToken: isDeltaToken,
|
|
2065
2212
|
isShorthandName: isShorthandName,
|
|
2066
|
-
isShorthandToken: isShorthandToken,
|
|
2067
2213
|
isScaledToken: isScaledToken,
|
|
2068
2214
|
tokenNames: tokenNames,
|
|
2069
|
-
codeTokenNames: codeTokenNames,
|
|
2070
2215
|
markdownTokenNames: markdownTokenNames,
|
|
2071
|
-
bodyTokenNames: bodyTokenNames,
|
|
2072
|
-
uiLineTokenNames: uiLineTokenNames,
|
|
2073
|
-
deltaTokenNames: deltaTokenNames,
|
|
2074
2216
|
shorthandTokens: shorthandTokens,
|
|
2075
2217
|
markdownShorthandTokens: markdownShorthandTokens,
|
|
2076
|
-
uiShorthandTokens: uiShorthandTokens,
|
|
2077
2218
|
codeShorthandTokens: codeShorthandTokens,
|
|
2078
|
-
scaleFontShorthand: scaleFontShorthand,
|
|
2079
2219
|
isGenericFamilyName: isGenericFamilyName,
|
|
2080
2220
|
isCJKFamilyName: isCJKFamilyName,
|
|
2081
2221
|
setWestEntry: setWestEntry,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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
|
"main": "lib/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"dsh": ">=0.1.5-rc.2"
|
|
22
|
+
"dsh": ">=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0"
|
|
23
23
|
},
|
|
24
24
|
"dsh": {
|
|
25
25
|
"bundle": {
|
|
@@ -30,13 +30,17 @@
|
|
|
30
30
|
},
|
|
31
31
|
"compatibility": {
|
|
32
32
|
"dshReleases": {
|
|
33
|
-
"0.1.5-rc.
|
|
33
|
+
"0.1.5-rc.1": "compatible",
|
|
34
|
+
"0.1.5-rc.2": "compatible",
|
|
35
|
+
"0.1.5-rc.3": "compatible",
|
|
36
|
+
"0.1.7-alpha.1": "compatible",
|
|
37
|
+
"0.1.7-alpha.2": "compatible"
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"peerDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.0",
|
|
43
|
+
"react": "^18.2.0"
|
|
40
44
|
},
|
|
41
45
|
"peerDependenciesMeta": {
|
|
42
46
|
"@deepseek-ai/schemastery": {
|
|
@@ -44,13 +48,14 @@
|
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
|
47
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
51
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
48
52
|
},
|
|
49
53
|
"scripts": {
|
|
50
54
|
"build": "node build.mjs",
|
|
51
55
|
"watch": "node build.mjs --watch",
|
|
52
56
|
"test": "node test/run.mjs && node test/render-card.mjs",
|
|
53
57
|
"verify": "node test/run.mjs && node test/render-card.mjs && node test/artifacts.mjs",
|
|
58
|
+
"verify:browser": "node test/browser-verify.mjs",
|
|
54
59
|
"prepublishOnly": "node build.mjs && node test/run.mjs && node test/render-card.mjs && node test/artifacts.mjs"
|
|
55
60
|
},
|
|
56
61
|
"license": "MIT",
|