@sonordev/contracts 1.0.0 → 2.1.0
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.md +76 -0
- package/README.md +117 -2
- package/dist/site-cache.d.ts +55 -0
- package/dist/site-cache.js +23 -0
- package/dist/site-cache.mjs +19 -0
- package/dist/site-edit-param.d.ts +7 -0
- package/dist/site-edit.d.ts +77 -0
- package/dist/site-edit.js +179 -0
- package/dist/site-edit.mjs +170 -0
- package/dist/slot-content.d.ts +111 -0
- package/dist/slot-content.js +184 -0
- package/dist/slot-content.mjs +169 -0
- package/dist/slots.d.ts +39 -25
- package/dist/slots.js +206 -10
- package/dist/slots.mjs +195 -11
- package/dist/voice.d.ts +56 -0
- package/dist/voice.js +200 -0
- package/dist/voice.mjs +195 -0
- package/package.json +22 -2
package/dist/voice.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/contracts/voice.ts
|
|
4
|
+
var VOICE_MASK = "\u2800";
|
|
5
|
+
var TABLE = [
|
|
6
|
+
["do not", "don't"],
|
|
7
|
+
["does not", "doesn't"],
|
|
8
|
+
["did not", "didn't"],
|
|
9
|
+
["is not", "isn't"],
|
|
10
|
+
["are not", "aren't"],
|
|
11
|
+
["was not", "wasn't"],
|
|
12
|
+
["were not", "weren't"],
|
|
13
|
+
["cannot", "can't"],
|
|
14
|
+
["can not", "can't"],
|
|
15
|
+
["will not", "won't"],
|
|
16
|
+
["would not", "wouldn't"],
|
|
17
|
+
["should not", "shouldn't"],
|
|
18
|
+
["could not", "couldn't"],
|
|
19
|
+
["has not", "hasn't"],
|
|
20
|
+
["have not", "haven't"],
|
|
21
|
+
["had not", "hadn't"],
|
|
22
|
+
["must not", "mustn't"],
|
|
23
|
+
["need not", "needn't"],
|
|
24
|
+
["it is", "it's"],
|
|
25
|
+
["that is", "that's"],
|
|
26
|
+
["there is", "there's"],
|
|
27
|
+
["here is", "here's"],
|
|
28
|
+
["who is", "who's"],
|
|
29
|
+
["what is", "what's"],
|
|
30
|
+
["you are", "you're"],
|
|
31
|
+
["we are", "we're"],
|
|
32
|
+
["they are", "they're"],
|
|
33
|
+
["I am", "I'm"],
|
|
34
|
+
["you will", "you'll"],
|
|
35
|
+
["we will", "we'll"],
|
|
36
|
+
["they will", "they'll"],
|
|
37
|
+
["it will", "it'll"],
|
|
38
|
+
["we would", "we'd"],
|
|
39
|
+
["you would", "you'd"],
|
|
40
|
+
["they would", "they'd"],
|
|
41
|
+
["let us", "let's"]
|
|
42
|
+
];
|
|
43
|
+
var PAIRS = TABLE.map(([full, contracted]) => ({
|
|
44
|
+
full,
|
|
45
|
+
contracted,
|
|
46
|
+
negation: / not$|^cannot$/.test(full),
|
|
47
|
+
pattern: new RegExp(`\\b${full.replace(/ /g, "[ \\t]+")}\\b`, "gi")
|
|
48
|
+
}));
|
|
49
|
+
var CAN_NOT_ONLY = /[ \t]+only\b/iy;
|
|
50
|
+
var CLAUSE_END = /[ \t]*(?:[.,;:!?)\]}"\u201d\u2019\u2026\u2014\u2013-]|\r?\n|\r?$)/y;
|
|
51
|
+
var CONJUNCTION = /[ \t]+(?:and|or|but|nor)\b/iy;
|
|
52
|
+
var LET_US_SUGGESTIONS = /[ \t]+(?:look|start|begin|consider|turn|see|review|compare|break|dig|recap|unpack|explore|examine|revisit|focus|go|talk|move|get started|step back|zoom out)\b/iy;
|
|
53
|
+
var PROSE_GAP = /[\s*_\u2800]/;
|
|
54
|
+
var preserveCase = (match, replacement) => match[0] === match[0].toUpperCase() && match[0] !== match[0].toLowerCase() ? replacement[0].toUpperCase() + replacement.slice(1) : replacement;
|
|
55
|
+
var maskAll = (text) => text.replace(/[^\r\n]/g, VOICE_MASK);
|
|
56
|
+
function maskProtected(text, ranges) {
|
|
57
|
+
if (!ranges) return text;
|
|
58
|
+
if (!Array.isArray(ranges)) return maskAll(text);
|
|
59
|
+
if (ranges.length === 0) return text;
|
|
60
|
+
const length = text.length;
|
|
61
|
+
const edges = new Int32Array(length + 1);
|
|
62
|
+
let any = false;
|
|
63
|
+
for (const range of ranges) {
|
|
64
|
+
const from = range?.[0];
|
|
65
|
+
const to = range?.[1];
|
|
66
|
+
if (typeof from !== "number" || typeof to !== "number" || Number.isNaN(from) || Number.isNaN(to) || from > to) return maskAll(text);
|
|
67
|
+
const start = Math.max(0, Math.floor(from));
|
|
68
|
+
const end = Math.min(length, Math.ceil(to));
|
|
69
|
+
if (end <= start) continue;
|
|
70
|
+
edges[start] += 1;
|
|
71
|
+
edges[end] -= 1;
|
|
72
|
+
any = true;
|
|
73
|
+
}
|
|
74
|
+
if (!any) return text;
|
|
75
|
+
const parts = [];
|
|
76
|
+
let depth = 0;
|
|
77
|
+
let runStart = 0;
|
|
78
|
+
let runProtected = false;
|
|
79
|
+
for (let i = 0; i <= length; i++) {
|
|
80
|
+
const isProtected = i < length && (depth += edges[i]) > 0;
|
|
81
|
+
if (i === length || isProtected !== runProtected) {
|
|
82
|
+
if (i > runStart) parts.push(runProtected ? maskAll(text.slice(runStart, i)) : text.slice(runStart, i));
|
|
83
|
+
runStart = i;
|
|
84
|
+
runProtected = isProtected;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return parts.join("");
|
|
88
|
+
}
|
|
89
|
+
function walkBackOverGap(text, start) {
|
|
90
|
+
let at = start;
|
|
91
|
+
while (at > 0 && text[at - 1] !== "\n" && PROSE_GAP.test(text[at - 1])) at--;
|
|
92
|
+
return at;
|
|
93
|
+
}
|
|
94
|
+
function firstStop(text, from) {
|
|
95
|
+
for (let i = from; i < text.length; i++) {
|
|
96
|
+
const code = text.charCodeAt(i);
|
|
97
|
+
if (code === 46 || code === 33 || code === 63 || code === 10) return i;
|
|
98
|
+
}
|
|
99
|
+
return text.length;
|
|
100
|
+
}
|
|
101
|
+
function mustStayFull(pair, text, titleLike, start, end, look) {
|
|
102
|
+
if (pair.full === "can not") {
|
|
103
|
+
CAN_NOT_ONLY.lastIndex = end;
|
|
104
|
+
return CAN_NOT_ONLY.test(text);
|
|
105
|
+
}
|
|
106
|
+
if (pair.negation) return false;
|
|
107
|
+
CLAUSE_END.lastIndex = end;
|
|
108
|
+
if (CLAUSE_END.test(text)) return true;
|
|
109
|
+
CONJUNCTION.lastIndex = end;
|
|
110
|
+
if (CONJUNCTION.test(text)) return true;
|
|
111
|
+
if (pair.full === "what is") {
|
|
112
|
+
if (look.stop < end) look.stop = firstStop(text, end);
|
|
113
|
+
if (text.charCodeAt(look.stop) === 63) return true;
|
|
114
|
+
if (titleLike) {
|
|
115
|
+
const at = walkBackOverGap(text, start);
|
|
116
|
+
if (at === 0 || text[at - 1] === "\n") return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (pair.full === "let us") {
|
|
120
|
+
const at = walkBackOverGap(text, start);
|
|
121
|
+
if (at > 0 && !".!?:\n".includes(text[at - 1])) return true;
|
|
122
|
+
LET_US_SUGGESTIONS.lastIndex = end;
|
|
123
|
+
return !LET_US_SUGGESTIONS.test(text);
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function scanUncontracted(text, masked, titleLike) {
|
|
128
|
+
if (masked.length < 4) return [];
|
|
129
|
+
const hits = [];
|
|
130
|
+
const claimed = new Uint8Array(masked.length);
|
|
131
|
+
for (const pair of PAIRS) {
|
|
132
|
+
const look = { stop: -1 };
|
|
133
|
+
for (const found of masked.matchAll(pair.pattern)) {
|
|
134
|
+
const start = found.index ?? 0;
|
|
135
|
+
const end = start + found[0].length;
|
|
136
|
+
let overlaps = false;
|
|
137
|
+
for (let i = start; i < end; i++) {
|
|
138
|
+
if (claimed[i] === 1) {
|
|
139
|
+
overlaps = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (overlaps || mustStayFull(pair, masked, titleLike, start, end, look)) continue;
|
|
144
|
+
claimed.fill(1, start, end);
|
|
145
|
+
const match = text.slice(start, end);
|
|
146
|
+
hits.push({ index: start, length: end - start, match, suggestion: preserveCase(match, pair.contracted) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return hits.sort((a, b) => a.index - b.index);
|
|
150
|
+
}
|
|
151
|
+
function findUncontracted(text, options) {
|
|
152
|
+
if (typeof text !== "string" || !text) return [];
|
|
153
|
+
return scanUncontracted(text, maskProtected(text, options?.protectedRanges), options?.titleLike === true);
|
|
154
|
+
}
|
|
155
|
+
function contractText(text, options) {
|
|
156
|
+
const hits = findUncontracted(text, options);
|
|
157
|
+
if (hits.length === 0) return text;
|
|
158
|
+
let out = "";
|
|
159
|
+
let cursor = 0;
|
|
160
|
+
for (const hit of hits) {
|
|
161
|
+
out += text.slice(cursor, hit.index) + hit.suggestion;
|
|
162
|
+
cursor = hit.index + hit.length;
|
|
163
|
+
}
|
|
164
|
+
return out + text.slice(cursor);
|
|
165
|
+
}
|
|
166
|
+
var CONTRACTION = /\b(?:[A-Za-z]+n['\u2019]t|(?:it|that|what|there|here|who|where|how|when|why|let|he|she)['\u2019]s|(?:you|we|they|who|what)['\u2019]re|(?:i|you|we|they|it|that|who|there|what)['\u2019]ll|(?:i|you|we|they|who|could|would|should|might|must)['\u2019]ve|(?:i|you|we|they|he|she|it|that|who|there)['\u2019]d|i['\u2019]m)\b/gi;
|
|
167
|
+
var IRREGULAR_EXPANSIONS = {
|
|
168
|
+
"can't": "cannot",
|
|
169
|
+
"won't": "will not",
|
|
170
|
+
"shan't": "shall not",
|
|
171
|
+
"let's": "let us",
|
|
172
|
+
"i'm": "I am"
|
|
173
|
+
};
|
|
174
|
+
var SUFFIX_EXPANSIONS = { s: "is", re: "are", ll: "will", ve: "have", d: "would", m: "am" };
|
|
175
|
+
function expandContraction(contraction) {
|
|
176
|
+
const plain = contraction.replace(/\u2019/g, "'");
|
|
177
|
+
const lower = plain.toLowerCase();
|
|
178
|
+
let expanded = IRREGULAR_EXPANSIONS[lower];
|
|
179
|
+
if (!expanded && lower.endsWith("n't")) expanded = `${plain.slice(0, -3)} not`;
|
|
180
|
+
if (!expanded) {
|
|
181
|
+
const [word, suffix] = plain.split("'");
|
|
182
|
+
expanded = `${word} ${SUFFIX_EXPANSIONS[suffix.toLowerCase()] ?? suffix}`;
|
|
183
|
+
}
|
|
184
|
+
return preserveCase(plain, expanded);
|
|
185
|
+
}
|
|
186
|
+
function findContracted(text, options) {
|
|
187
|
+
if (typeof text !== "string" || !text) return [];
|
|
188
|
+
const hits = [];
|
|
189
|
+
for (const found of maskProtected(text, options?.protectedRanges).matchAll(CONTRACTION)) {
|
|
190
|
+
const index = found.index ?? 0;
|
|
191
|
+
const match = text.slice(index, index + found[0].length);
|
|
192
|
+
hits.push({ index, length: match.length, match, suggestion: expandContraction(match) });
|
|
193
|
+
}
|
|
194
|
+
return hits;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
exports.VOICE_MASK = VOICE_MASK;
|
|
198
|
+
exports.contractText = contractText;
|
|
199
|
+
exports.findContracted = findContracted;
|
|
200
|
+
exports.findUncontracted = findUncontracted;
|
package/dist/voice.mjs
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/contracts/voice.ts
|
|
2
|
+
var VOICE_MASK = "\u2800";
|
|
3
|
+
var TABLE = [
|
|
4
|
+
["do not", "don't"],
|
|
5
|
+
["does not", "doesn't"],
|
|
6
|
+
["did not", "didn't"],
|
|
7
|
+
["is not", "isn't"],
|
|
8
|
+
["are not", "aren't"],
|
|
9
|
+
["was not", "wasn't"],
|
|
10
|
+
["were not", "weren't"],
|
|
11
|
+
["cannot", "can't"],
|
|
12
|
+
["can not", "can't"],
|
|
13
|
+
["will not", "won't"],
|
|
14
|
+
["would not", "wouldn't"],
|
|
15
|
+
["should not", "shouldn't"],
|
|
16
|
+
["could not", "couldn't"],
|
|
17
|
+
["has not", "hasn't"],
|
|
18
|
+
["have not", "haven't"],
|
|
19
|
+
["had not", "hadn't"],
|
|
20
|
+
["must not", "mustn't"],
|
|
21
|
+
["need not", "needn't"],
|
|
22
|
+
["it is", "it's"],
|
|
23
|
+
["that is", "that's"],
|
|
24
|
+
["there is", "there's"],
|
|
25
|
+
["here is", "here's"],
|
|
26
|
+
["who is", "who's"],
|
|
27
|
+
["what is", "what's"],
|
|
28
|
+
["you are", "you're"],
|
|
29
|
+
["we are", "we're"],
|
|
30
|
+
["they are", "they're"],
|
|
31
|
+
["I am", "I'm"],
|
|
32
|
+
["you will", "you'll"],
|
|
33
|
+
["we will", "we'll"],
|
|
34
|
+
["they will", "they'll"],
|
|
35
|
+
["it will", "it'll"],
|
|
36
|
+
["we would", "we'd"],
|
|
37
|
+
["you would", "you'd"],
|
|
38
|
+
["they would", "they'd"],
|
|
39
|
+
["let us", "let's"]
|
|
40
|
+
];
|
|
41
|
+
var PAIRS = TABLE.map(([full, contracted]) => ({
|
|
42
|
+
full,
|
|
43
|
+
contracted,
|
|
44
|
+
negation: / not$|^cannot$/.test(full),
|
|
45
|
+
pattern: new RegExp(`\\b${full.replace(/ /g, "[ \\t]+")}\\b`, "gi")
|
|
46
|
+
}));
|
|
47
|
+
var CAN_NOT_ONLY = /[ \t]+only\b/iy;
|
|
48
|
+
var CLAUSE_END = /[ \t]*(?:[.,;:!?)\]}"\u201d\u2019\u2026\u2014\u2013-]|\r?\n|\r?$)/y;
|
|
49
|
+
var CONJUNCTION = /[ \t]+(?:and|or|but|nor)\b/iy;
|
|
50
|
+
var LET_US_SUGGESTIONS = /[ \t]+(?:look|start|begin|consider|turn|see|review|compare|break|dig|recap|unpack|explore|examine|revisit|focus|go|talk|move|get started|step back|zoom out)\b/iy;
|
|
51
|
+
var PROSE_GAP = /[\s*_\u2800]/;
|
|
52
|
+
var preserveCase = (match, replacement) => match[0] === match[0].toUpperCase() && match[0] !== match[0].toLowerCase() ? replacement[0].toUpperCase() + replacement.slice(1) : replacement;
|
|
53
|
+
var maskAll = (text) => text.replace(/[^\r\n]/g, VOICE_MASK);
|
|
54
|
+
function maskProtected(text, ranges) {
|
|
55
|
+
if (!ranges) return text;
|
|
56
|
+
if (!Array.isArray(ranges)) return maskAll(text);
|
|
57
|
+
if (ranges.length === 0) return text;
|
|
58
|
+
const length = text.length;
|
|
59
|
+
const edges = new Int32Array(length + 1);
|
|
60
|
+
let any = false;
|
|
61
|
+
for (const range of ranges) {
|
|
62
|
+
const from = range?.[0];
|
|
63
|
+
const to = range?.[1];
|
|
64
|
+
if (typeof from !== "number" || typeof to !== "number" || Number.isNaN(from) || Number.isNaN(to) || from > to) return maskAll(text);
|
|
65
|
+
const start = Math.max(0, Math.floor(from));
|
|
66
|
+
const end = Math.min(length, Math.ceil(to));
|
|
67
|
+
if (end <= start) continue;
|
|
68
|
+
edges[start] += 1;
|
|
69
|
+
edges[end] -= 1;
|
|
70
|
+
any = true;
|
|
71
|
+
}
|
|
72
|
+
if (!any) return text;
|
|
73
|
+
const parts = [];
|
|
74
|
+
let depth = 0;
|
|
75
|
+
let runStart = 0;
|
|
76
|
+
let runProtected = false;
|
|
77
|
+
for (let i = 0; i <= length; i++) {
|
|
78
|
+
const isProtected = i < length && (depth += edges[i]) > 0;
|
|
79
|
+
if (i === length || isProtected !== runProtected) {
|
|
80
|
+
if (i > runStart) parts.push(runProtected ? maskAll(text.slice(runStart, i)) : text.slice(runStart, i));
|
|
81
|
+
runStart = i;
|
|
82
|
+
runProtected = isProtected;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return parts.join("");
|
|
86
|
+
}
|
|
87
|
+
function walkBackOverGap(text, start) {
|
|
88
|
+
let at = start;
|
|
89
|
+
while (at > 0 && text[at - 1] !== "\n" && PROSE_GAP.test(text[at - 1])) at--;
|
|
90
|
+
return at;
|
|
91
|
+
}
|
|
92
|
+
function firstStop(text, from) {
|
|
93
|
+
for (let i = from; i < text.length; i++) {
|
|
94
|
+
const code = text.charCodeAt(i);
|
|
95
|
+
if (code === 46 || code === 33 || code === 63 || code === 10) return i;
|
|
96
|
+
}
|
|
97
|
+
return text.length;
|
|
98
|
+
}
|
|
99
|
+
function mustStayFull(pair, text, titleLike, start, end, look) {
|
|
100
|
+
if (pair.full === "can not") {
|
|
101
|
+
CAN_NOT_ONLY.lastIndex = end;
|
|
102
|
+
return CAN_NOT_ONLY.test(text);
|
|
103
|
+
}
|
|
104
|
+
if (pair.negation) return false;
|
|
105
|
+
CLAUSE_END.lastIndex = end;
|
|
106
|
+
if (CLAUSE_END.test(text)) return true;
|
|
107
|
+
CONJUNCTION.lastIndex = end;
|
|
108
|
+
if (CONJUNCTION.test(text)) return true;
|
|
109
|
+
if (pair.full === "what is") {
|
|
110
|
+
if (look.stop < end) look.stop = firstStop(text, end);
|
|
111
|
+
if (text.charCodeAt(look.stop) === 63) return true;
|
|
112
|
+
if (titleLike) {
|
|
113
|
+
const at = walkBackOverGap(text, start);
|
|
114
|
+
if (at === 0 || text[at - 1] === "\n") return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (pair.full === "let us") {
|
|
118
|
+
const at = walkBackOverGap(text, start);
|
|
119
|
+
if (at > 0 && !".!?:\n".includes(text[at - 1])) return true;
|
|
120
|
+
LET_US_SUGGESTIONS.lastIndex = end;
|
|
121
|
+
return !LET_US_SUGGESTIONS.test(text);
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
function scanUncontracted(text, masked, titleLike) {
|
|
126
|
+
if (masked.length < 4) return [];
|
|
127
|
+
const hits = [];
|
|
128
|
+
const claimed = new Uint8Array(masked.length);
|
|
129
|
+
for (const pair of PAIRS) {
|
|
130
|
+
const look = { stop: -1 };
|
|
131
|
+
for (const found of masked.matchAll(pair.pattern)) {
|
|
132
|
+
const start = found.index ?? 0;
|
|
133
|
+
const end = start + found[0].length;
|
|
134
|
+
let overlaps = false;
|
|
135
|
+
for (let i = start; i < end; i++) {
|
|
136
|
+
if (claimed[i] === 1) {
|
|
137
|
+
overlaps = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (overlaps || mustStayFull(pair, masked, titleLike, start, end, look)) continue;
|
|
142
|
+
claimed.fill(1, start, end);
|
|
143
|
+
const match = text.slice(start, end);
|
|
144
|
+
hits.push({ index: start, length: end - start, match, suggestion: preserveCase(match, pair.contracted) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return hits.sort((a, b) => a.index - b.index);
|
|
148
|
+
}
|
|
149
|
+
function findUncontracted(text, options) {
|
|
150
|
+
if (typeof text !== "string" || !text) return [];
|
|
151
|
+
return scanUncontracted(text, maskProtected(text, options?.protectedRanges), options?.titleLike === true);
|
|
152
|
+
}
|
|
153
|
+
function contractText(text, options) {
|
|
154
|
+
const hits = findUncontracted(text, options);
|
|
155
|
+
if (hits.length === 0) return text;
|
|
156
|
+
let out = "";
|
|
157
|
+
let cursor = 0;
|
|
158
|
+
for (const hit of hits) {
|
|
159
|
+
out += text.slice(cursor, hit.index) + hit.suggestion;
|
|
160
|
+
cursor = hit.index + hit.length;
|
|
161
|
+
}
|
|
162
|
+
return out + text.slice(cursor);
|
|
163
|
+
}
|
|
164
|
+
var CONTRACTION = /\b(?:[A-Za-z]+n['\u2019]t|(?:it|that|what|there|here|who|where|how|when|why|let|he|she)['\u2019]s|(?:you|we|they|who|what)['\u2019]re|(?:i|you|we|they|it|that|who|there|what)['\u2019]ll|(?:i|you|we|they|who|could|would|should|might|must)['\u2019]ve|(?:i|you|we|they|he|she|it|that|who|there)['\u2019]d|i['\u2019]m)\b/gi;
|
|
165
|
+
var IRREGULAR_EXPANSIONS = {
|
|
166
|
+
"can't": "cannot",
|
|
167
|
+
"won't": "will not",
|
|
168
|
+
"shan't": "shall not",
|
|
169
|
+
"let's": "let us",
|
|
170
|
+
"i'm": "I am"
|
|
171
|
+
};
|
|
172
|
+
var SUFFIX_EXPANSIONS = { s: "is", re: "are", ll: "will", ve: "have", d: "would", m: "am" };
|
|
173
|
+
function expandContraction(contraction) {
|
|
174
|
+
const plain = contraction.replace(/\u2019/g, "'");
|
|
175
|
+
const lower = plain.toLowerCase();
|
|
176
|
+
let expanded = IRREGULAR_EXPANSIONS[lower];
|
|
177
|
+
if (!expanded && lower.endsWith("n't")) expanded = `${plain.slice(0, -3)} not`;
|
|
178
|
+
if (!expanded) {
|
|
179
|
+
const [word, suffix] = plain.split("'");
|
|
180
|
+
expanded = `${word} ${SUFFIX_EXPANSIONS[suffix.toLowerCase()] ?? suffix}`;
|
|
181
|
+
}
|
|
182
|
+
return preserveCase(plain, expanded);
|
|
183
|
+
}
|
|
184
|
+
function findContracted(text, options) {
|
|
185
|
+
if (typeof text !== "string" || !text) return [];
|
|
186
|
+
const hits = [];
|
|
187
|
+
for (const found of maskProtected(text, options?.protectedRanges).matchAll(CONTRACTION)) {
|
|
188
|
+
const index = found.index ?? 0;
|
|
189
|
+
const match = text.slice(index, index + found[0].length);
|
|
190
|
+
hits.push({ index, length: match.length, match, suggestion: expandContraction(match) });
|
|
191
|
+
}
|
|
192
|
+
return hits;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export { VOICE_MASK, contractText, findContracted, findUncontracted };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sonordev/contracts",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "The rules Sonor's sites, APIs and dashboard share: popup blocks and site design tokens, site hosts, SEO page identity, title quality, llms.txt sanitizers, the forms honeypot, fleet heartbeats, slot
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "The rules Sonor's sites, APIs and dashboard share: popup blocks and site design tokens, site hosts, SEO page identity, title quality, llms.txt sanitizers, the forms honeypot, fleet heartbeats, slot content and signing, site cache tags, portfolio metrics and the house voice's contraction rule. Pure functions, no dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,6 +30,21 @@
|
|
|
30
30
|
"import": "./dist/slots.mjs",
|
|
31
31
|
"require": "./dist/slots.js"
|
|
32
32
|
},
|
|
33
|
+
"./slot-content": {
|
|
34
|
+
"types": "./dist/slot-content.d.ts",
|
|
35
|
+
"import": "./dist/slot-content.mjs",
|
|
36
|
+
"require": "./dist/slot-content.js"
|
|
37
|
+
},
|
|
38
|
+
"./site-cache": {
|
|
39
|
+
"types": "./dist/site-cache.d.ts",
|
|
40
|
+
"import": "./dist/site-cache.mjs",
|
|
41
|
+
"require": "./dist/site-cache.js"
|
|
42
|
+
},
|
|
43
|
+
"./site-edit": {
|
|
44
|
+
"types": "./dist/site-edit.d.ts",
|
|
45
|
+
"import": "./dist/site-edit.mjs",
|
|
46
|
+
"require": "./dist/site-edit.js"
|
|
47
|
+
},
|
|
33
48
|
"./website": {
|
|
34
49
|
"types": "./dist/website.d.ts",
|
|
35
50
|
"import": "./dist/website.mjs",
|
|
@@ -65,6 +80,11 @@
|
|
|
65
80
|
"import": "./dist/portfolio.mjs",
|
|
66
81
|
"require": "./dist/portfolio.js"
|
|
67
82
|
},
|
|
83
|
+
"./voice": {
|
|
84
|
+
"types": "./dist/voice.d.ts",
|
|
85
|
+
"import": "./dist/voice.mjs",
|
|
86
|
+
"require": "./dist/voice.js"
|
|
87
|
+
},
|
|
68
88
|
"./package.json": "./package.json"
|
|
69
89
|
},
|
|
70
90
|
"typesVersions": {
|