terminalhire 0.42.1 → 0.42.3
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/dist/bin/claim-push-bg.js +3 -2
- package/dist/bin/jpi-bounties.js +59 -7
- package/dist/bin/jpi-claim.js +22 -10
- package/dist/bin/jpi-contribute.js +5 -3
- package/dist/bin/jpi-devs.js +2 -1
- package/dist/bin/jpi-dispatch.js +239 -59
- package/dist/bin/jpi-hub.js +92 -18
- package/dist/bin/jpi-jobs.js +47 -34
- package/dist/bin/jpi-mcp.js +23 -11
- package/dist/bin/jpi-protocol.js +114 -30
- package/dist/bin/jpi-refresh.js +18 -3
- package/dist/bin/jpi-repo.js +3 -2
- package/dist/bin/jpi-update.js +114 -30
- package/dist/bin/sanitize.js +9 -1
- package/dist/bin/spinner-render.js +9 -1
- package/dist/bin/spinner.js +9 -1
- package/dist/src/claims.js +2 -1
- package/dist/src/protocol.js +113 -29
- package/package.json +2 -2
|
@@ -167,7 +167,7 @@ function nowISO() {
|
|
|
167
167
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
168
168
|
}
|
|
169
169
|
function defangText(s) {
|
|
170
|
-
return typeof s === "string" ? s.replace(CONTROL_CHARS, "") : s;
|
|
170
|
+
return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "") : s;
|
|
171
171
|
}
|
|
172
172
|
function finiteAmount(a) {
|
|
173
173
|
if (typeof a === "number") return Number.isFinite(a) ? a : null;
|
|
@@ -305,7 +305,7 @@ function acceptedPRRate(claims = readClaims()) {
|
|
|
305
305
|
const merged = claims.filter((c) => c.state === "merged").length;
|
|
306
306
|
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
307
307
|
}
|
|
308
|
-
var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS;
|
|
308
|
+
var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
|
|
309
309
|
var init_claims = __esm({
|
|
310
310
|
"src/claims.ts"() {
|
|
311
311
|
"use strict";
|
|
@@ -361,6 +361,7 @@ var init_claims = __esm({
|
|
|
361
361
|
]),
|
|
362
362
|
submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
|
|
363
363
|
};
|
|
364
|
+
WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
|
|
364
365
|
CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
365
366
|
}
|
|
366
367
|
});
|
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -11339,7 +11339,7 @@ function nowISO2() {
|
|
|
11339
11339
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
11340
11340
|
}
|
|
11341
11341
|
function defangText(s) {
|
|
11342
|
-
return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
|
|
11342
|
+
return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
|
|
11343
11343
|
}
|
|
11344
11344
|
function finiteAmount(a) {
|
|
11345
11345
|
if (typeof a === "number") return Number.isFinite(a) ? a : null;
|
|
@@ -11477,7 +11477,7 @@ function acceptedPRRate(claims = readClaims()) {
|
|
|
11477
11477
|
const merged = claims.filter((c) => c.state === "merged").length;
|
|
11478
11478
|
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
11479
11479
|
}
|
|
11480
|
-
var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
|
|
11480
|
+
var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
|
|
11481
11481
|
var init_claims = __esm({
|
|
11482
11482
|
"src/claims.ts"() {
|
|
11483
11483
|
"use strict";
|
|
@@ -11533,6 +11533,7 @@ var init_claims = __esm({
|
|
|
11533
11533
|
]),
|
|
11534
11534
|
submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
|
|
11535
11535
|
};
|
|
11536
|
+
WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
|
|
11536
11537
|
CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
|
|
11537
11538
|
}
|
|
11538
11539
|
});
|
|
@@ -11748,17 +11749,38 @@ var init_founder_paid_badge = __esm({
|
|
|
11748
11749
|
|
|
11749
11750
|
// bin/jpi-bounties.js
|
|
11750
11751
|
init_src();
|
|
11751
|
-
init_cache_store();
|
|
11752
11752
|
import { readFileSync as readFileSync7 } from "fs";
|
|
11753
11753
|
import { join as join10 } from "path";
|
|
11754
11754
|
import { homedir as homedir7 } from "os";
|
|
11755
11755
|
import { createInterface } from "readline";
|
|
11756
11756
|
|
|
11757
|
+
// ../../packages/core/src/payout-split.ts
|
|
11758
|
+
var PLATFORM_TAKE_BPS = 1e3;
|
|
11759
|
+
function platformFeeCents(totalCents) {
|
|
11760
|
+
return Math.ceil(totalCents * PLATFORM_TAKE_BPS / 1e4);
|
|
11761
|
+
}
|
|
11762
|
+
function developerShareCents(totalCents) {
|
|
11763
|
+
return totalCents - platformFeeCents(totalCents);
|
|
11764
|
+
}
|
|
11765
|
+
function formatCents(cents) {
|
|
11766
|
+
return cents % 100 === 0 ? `$${cents / 100}` : `$${(cents / 100).toFixed(2)}`;
|
|
11767
|
+
}
|
|
11768
|
+
|
|
11769
|
+
// bin/jpi-bounties.js
|
|
11770
|
+
init_cache_store();
|
|
11771
|
+
|
|
11757
11772
|
// bin/sanitize.js
|
|
11773
|
+
var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
|
|
11758
11774
|
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
11759
11775
|
function sanitizeText(s) {
|
|
11760
11776
|
if (s == null) return "";
|
|
11761
|
-
return String(s).replace(CONTROL_CHARS, "");
|
|
11777
|
+
return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
|
|
11778
|
+
}
|
|
11779
|
+
function formatUsd(a) {
|
|
11780
|
+
if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
|
|
11781
|
+
if (typeof a !== "string" || a.trim() === "") return "$\u2014";
|
|
11782
|
+
const n = Number(a);
|
|
11783
|
+
return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
|
|
11762
11784
|
}
|
|
11763
11785
|
function safeHttpUrl(url) {
|
|
11764
11786
|
if (url == null) return null;
|
|
@@ -11831,13 +11853,33 @@ function prompt(question) {
|
|
|
11831
11853
|
});
|
|
11832
11854
|
}
|
|
11833
11855
|
function formatAmount(b) {
|
|
11834
|
-
return b.amountUSD
|
|
11856
|
+
return formatUsd(b.amountUSD);
|
|
11835
11857
|
}
|
|
11836
11858
|
var EFFORT_LABEL = {
|
|
11837
11859
|
small: "small (~\xBD day)",
|
|
11838
11860
|
medium: "medium (~1 day)",
|
|
11839
11861
|
large: "large (multi-day)"
|
|
11840
11862
|
};
|
|
11863
|
+
function founderClaimBlurb(amountUSD, paidWork) {
|
|
11864
|
+
if (paidWork === false) {
|
|
11865
|
+
return "Claim it from here \u2014 this posting records a credential for the work, not a payment. Take it for the credential, not the money.";
|
|
11866
|
+
}
|
|
11867
|
+
if (paidWork !== true) {
|
|
11868
|
+
return "Claim it from here \u2014 this listing is missing its payment status, so it is not safe to say whether the work pays. Try `terminalhire refresh`; if it is still missing, do not count on being paid for this one.";
|
|
11869
|
+
}
|
|
11870
|
+
let share = "";
|
|
11871
|
+
if (typeof amountUSD === "number" && Number.isFinite(amountUSD) && amountUSD > 0) {
|
|
11872
|
+
share = ` You'd receive ${formatCents(developerShareCents(Math.round(amountUSD * 100)))} of the ${formatCents(Math.round(amountUSD * 100))} posted; terminalhire keeps 10%.`;
|
|
11873
|
+
}
|
|
11874
|
+
return `Claim it from here \u2014 the founder pays through terminalhire rather than off-platform, and their acceptance is what triggers your payout.${share}`;
|
|
11875
|
+
}
|
|
11876
|
+
function founderClaimBlock(job) {
|
|
11877
|
+
const ref = opportunityShortToken(job.id);
|
|
11878
|
+
return `
|
|
11879
|
+
${founderClaimBlurb(job.bounty?.amountUSD, job.bounty?.paidWork)}
|
|
11880
|
+
terminalhire claim ${ref}
|
|
11881
|
+
${sanitizeText(job.bounty?.claimUrl ?? job.url)}`;
|
|
11882
|
+
}
|
|
11841
11883
|
function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set(), continuityNote = null) {
|
|
11842
11884
|
const b = job.bounty ?? {};
|
|
11843
11885
|
const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
|
|
@@ -11980,7 +12022,9 @@ async function run() {
|
|
|
11980
12022
|
const rows = founderRows2(surface, "bounties");
|
|
11981
12023
|
console.log("\n\u26A1 Your TerminalHire postings\n");
|
|
11982
12024
|
if (!rows.length) {
|
|
11983
|
-
console.log(
|
|
12025
|
+
console.log(
|
|
12026
|
+
" No current posting data. Run `terminalhire refresh`, or lead with work:"
|
|
12027
|
+
);
|
|
11984
12028
|
console.log(" terminalhire config set lead dev\n");
|
|
11985
12029
|
} else {
|
|
11986
12030
|
rows.forEach((row, index) => {
|
|
@@ -11989,7 +12033,9 @@ async function run() {
|
|
|
11989
12033
|
);
|
|
11990
12034
|
});
|
|
11991
12035
|
console.log("\n Review and act: https://terminalhire.com/dashboard?tab=postings");
|
|
11992
|
-
console.log(
|
|
12036
|
+
console.log(
|
|
12037
|
+
" Lead with developer bounties instead: terminalhire config set lead dev\n"
|
|
12038
|
+
);
|
|
11993
12039
|
}
|
|
11994
12040
|
return;
|
|
11995
12041
|
}
|
|
@@ -12064,6 +12110,10 @@ Enter a number to open a bounty's claim page, or press Enter to exit: `
|
|
|
12064
12110
|
const idx = parseInt(pick, 10) - 1;
|
|
12065
12111
|
if (Number.isNaN(idx) || idx < 0 || idx >= shown.length) return;
|
|
12066
12112
|
const chosen = shown[idx];
|
|
12113
|
+
if (chosen.bounty?.bountySource === "founder") {
|
|
12114
|
+
console.log(founderClaimBlock(chosen));
|
|
12115
|
+
return;
|
|
12116
|
+
}
|
|
12067
12117
|
console.log(
|
|
12068
12118
|
`
|
|
12069
12119
|
Open this to claim/work the bounty (you go straight to the source \u2014 we never touch payment):
|
|
@@ -12079,6 +12129,8 @@ export {
|
|
|
12079
12129
|
classifyEmptyStatus,
|
|
12080
12130
|
continuityNoteForRow,
|
|
12081
12131
|
filterPaidVisibility,
|
|
12132
|
+
founderClaimBlock,
|
|
12133
|
+
founderClaimBlurb,
|
|
12082
12134
|
getBounties,
|
|
12083
12135
|
isPinnedFounderBounty,
|
|
12084
12136
|
printBounty,
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -10639,7 +10639,7 @@ function nowISO() {
|
|
|
10639
10639
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
10640
10640
|
}
|
|
10641
10641
|
function defangText(s) {
|
|
10642
|
-
return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
|
|
10642
|
+
return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
|
|
10643
10643
|
}
|
|
10644
10644
|
function finiteAmount(a) {
|
|
10645
10645
|
if (typeof a === "number") return Number.isFinite(a) ? a : null;
|
|
@@ -10777,7 +10777,7 @@ function acceptedPRRate(claims = readClaims()) {
|
|
|
10777
10777
|
const merged = claims.filter((c) => c.state === "merged").length;
|
|
10778
10778
|
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
10779
10779
|
}
|
|
10780
|
-
var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
|
|
10780
|
+
var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
|
|
10781
10781
|
var init_claims = __esm({
|
|
10782
10782
|
"src/claims.ts"() {
|
|
10783
10783
|
"use strict";
|
|
@@ -10833,6 +10833,7 @@ var init_claims = __esm({
|
|
|
10833
10833
|
]),
|
|
10834
10834
|
submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
|
|
10835
10835
|
};
|
|
10836
|
+
WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
|
|
10836
10837
|
CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
|
|
10837
10838
|
}
|
|
10838
10839
|
});
|
|
@@ -26151,10 +26152,17 @@ function openInBrowser(url) {
|
|
|
26151
26152
|
}
|
|
26152
26153
|
|
|
26153
26154
|
// bin/sanitize.js
|
|
26155
|
+
var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
|
|
26154
26156
|
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
26155
26157
|
function sanitizeText(s) {
|
|
26156
26158
|
if (s == null) return "";
|
|
26157
|
-
return String(s).replace(CONTROL_CHARS, "");
|
|
26159
|
+
return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
|
|
26160
|
+
}
|
|
26161
|
+
function formatUsd(a) {
|
|
26162
|
+
if (typeof a === "number") return Number.isFinite(a) ? "$" + a.toLocaleString() : "$\u2014";
|
|
26163
|
+
if (typeof a !== "string" || a.trim() === "") return "$\u2014";
|
|
26164
|
+
const n = Number(a);
|
|
26165
|
+
return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
|
|
26158
26166
|
}
|
|
26159
26167
|
|
|
26160
26168
|
// src/policy-acks.ts
|
|
@@ -27081,10 +27089,7 @@ async function pollPR(prUrl) {
|
|
|
27081
27089
|
}
|
|
27082
27090
|
}
|
|
27083
27091
|
function fmtAmount(a) {
|
|
27084
|
-
|
|
27085
|
-
if (typeof a === "string" && a.trim() === "") return "$\u2014";
|
|
27086
|
-
const n = typeof a === "number" ? a : Number(a);
|
|
27087
|
-
return Number.isFinite(n) ? "$" + n.toLocaleString() : "$\u2014";
|
|
27092
|
+
return formatUsd(a);
|
|
27088
27093
|
}
|
|
27089
27094
|
function fmtClaimAmount(c) {
|
|
27090
27095
|
return c.kind === "contribution" ? "contribution" : fmtAmount(c.amountUSD);
|
|
@@ -27225,8 +27230,15 @@ async function resolveBounty(arg) {
|
|
|
27225
27230
|
return {
|
|
27226
27231
|
bountyId,
|
|
27227
27232
|
indexNativeId,
|
|
27228
|
-
|
|
27229
|
-
|
|
27233
|
+
// DEFANGED HERE, at the one place every tier of this resolver converges.
|
|
27234
|
+
// `claims.ts` cleans what comes OUT of the local store, which covers every later
|
|
27235
|
+
// read — but this object is printed (the preview card, the record card) and
|
|
27236
|
+
// handed to `recordClaim` BEFORE any store exists for it, so on a claim's first
|
|
27237
|
+
// use the store boundary has not run yet. `repoFullName` also goes on to be
|
|
27238
|
+
// interpolated into GitHub API URLs from here. Found by a cross-model review of
|
|
27239
|
+
// TERM-380; the store fix alone left this path open.
|
|
27240
|
+
title: sanitizeText(title),
|
|
27241
|
+
repoFullName: repoFullName == null ? repoFullName : sanitizeText(repoFullName),
|
|
27230
27242
|
issueUrl,
|
|
27231
27243
|
amountUSD,
|
|
27232
27244
|
source,
|
|
@@ -28466,7 +28478,7 @@ async function cmdStart(id, flags = {}) {
|
|
|
28466
28478
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
28467
28479
|
process.exit(1);
|
|
28468
28480
|
}
|
|
28469
|
-
if (claim.approval?.state === "pending"
|
|
28481
|
+
if (claim.approval?.state === "pending") {
|
|
28470
28482
|
({ approvalsChecked, approvalsUnavailable } = await syncFounderApprovals(claims, [claim]));
|
|
28471
28483
|
claim = claims.findClaim(id);
|
|
28472
28484
|
}
|
|
@@ -3618,7 +3618,7 @@ function nowISO2() {
|
|
|
3618
3618
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3619
3619
|
}
|
|
3620
3620
|
function defangText(s) {
|
|
3621
|
-
return typeof s === "string" ? s.replace(CONTROL_CHARS2, "") : s;
|
|
3621
|
+
return typeof s === "string" ? s.replace(WHITESPACE_CONTROLS2, " ").replace(CONTROL_CHARS2, "") : s;
|
|
3622
3622
|
}
|
|
3623
3623
|
function finiteAmount(a) {
|
|
3624
3624
|
if (typeof a === "number") return Number.isFinite(a) ? a : null;
|
|
@@ -3756,7 +3756,7 @@ function acceptedPRRate(claims = readClaims()) {
|
|
|
3756
3756
|
const merged = claims.filter((c) => c.state === "merged").length;
|
|
3757
3757
|
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
3758
3758
|
}
|
|
3759
|
-
var TERMINALHIRE_DIR7, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, CONTROL_CHARS2;
|
|
3759
|
+
var TERMINALHIRE_DIR7, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
|
|
3760
3760
|
var init_claims = __esm({
|
|
3761
3761
|
"src/claims.ts"() {
|
|
3762
3762
|
"use strict";
|
|
@@ -3812,6 +3812,7 @@ var init_claims = __esm({
|
|
|
3812
3812
|
]),
|
|
3813
3813
|
submitted: /* @__PURE__ */ new Set(["claimed", "working", "in-review", "ready"])
|
|
3814
3814
|
};
|
|
3815
|
+
WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
|
|
3815
3816
|
CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
|
|
3816
3817
|
}
|
|
3817
3818
|
});
|
|
@@ -3896,10 +3897,11 @@ function isContributeEnabled() {
|
|
|
3896
3897
|
init_state_dir();
|
|
3897
3898
|
|
|
3898
3899
|
// bin/sanitize.js
|
|
3900
|
+
var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
|
|
3899
3901
|
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
3900
3902
|
function sanitizeText(s) {
|
|
3901
3903
|
if (s == null) return "";
|
|
3902
|
-
return String(s).replace(CONTROL_CHARS, "");
|
|
3904
|
+
return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
|
|
3903
3905
|
}
|
|
3904
3906
|
function safeHttpUrl(url) {
|
|
3905
3907
|
if (url == null) return null;
|
package/dist/bin/jpi-devs.js
CHANGED
|
@@ -11053,10 +11053,11 @@ function excludeOwnCard(results, ownLogin) {
|
|
|
11053
11053
|
}
|
|
11054
11054
|
|
|
11055
11055
|
// bin/sanitize.js
|
|
11056
|
+
var WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
|
|
11056
11057
|
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
11057
11058
|
function sanitizeText(s) {
|
|
11058
11059
|
if (s == null) return "";
|
|
11059
|
-
return String(s).replace(CONTROL_CHARS, "");
|
|
11060
|
+
return String(s).replace(WHITESPACE_CONTROLS, " ").replace(CONTROL_CHARS, "");
|
|
11060
11061
|
}
|
|
11061
11062
|
function safeHttpUrl(url) {
|
|
11062
11063
|
if (url == null) return null;
|