@cavuno/board 3.2.0 → 4.0.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 +406 -0
- package/README.md +6 -2
- package/dist/{_spec-nkWAraS0.d.mts → _spec-GVe6WAfT.d.mts} +68 -59
- package/dist/{_spec-nkWAraS0.d.ts → _spec-GVe6WAfT.d.ts} +68 -59
- package/dist/bin.mjs +7 -2
- package/dist/{blog-iV9XEIGf.d.ts → blog-BsmKizpd.d.ts} +1 -1
- package/dist/{blog-DGBHmExV.d.mts → blog-Cf64mI8A.d.mts} +1 -1
- package/dist/{board-BJbXd_9_.d.mts → board-DUhgiacm.d.ts} +7 -6
- package/dist/{board-JhdUWIoq.d.ts → board-GGWJ4coe.d.mts} +7 -6
- package/dist/breadcrumbs-DFKr0HzU.d.mts +372 -0
- package/dist/breadcrumbs-DiccJhk4.d.ts +372 -0
- package/dist/chunk-BGKNXG4H.mjs +146 -0
- package/dist/{chunk-NXY3OWGF.mjs → chunk-T2KC6XGV.mjs} +1 -1
- package/dist/{chunk-STE77MAT.mjs → chunk-YEAQ3JEI.mjs} +143 -134
- package/dist/doctor.js +1 -1
- package/dist/doctor.mjs +1 -1
- package/dist/filters.d.mts +25 -20
- package/dist/filters.d.ts +25 -20
- package/dist/filters.js +3 -692
- package/dist/filters.mjs +3 -29
- package/dist/format.d.mts +57 -231
- package/dist/format.d.ts +57 -231
- package/dist/format.js +327 -968
- package/dist/format.mjs +207 -264
- package/dist/go.js +11 -1
- package/dist/go.mjs +1 -1
- package/dist/index.d.mts +23 -76
- package/dist/index.d.ts +23 -76
- package/dist/index.js +11 -36
- package/dist/index.mjs +11 -36
- package/dist/{jobs-BiEvL9d7.d.ts → jobs-BuLpV8iy.d.mts} +4 -4
- package/dist/{jobs-lVvKCBCU.d.mts → jobs-QrbXOYB9.d.ts} +4 -4
- package/dist/paths.d.mts +89 -1
- package/dist/paths.d.ts +89 -1
- package/dist/paths.js +62 -14
- package/dist/paths.mjs +11 -3
- package/dist/route-contract.js +23 -13
- package/dist/route-contract.mjs +2 -2
- package/dist/{search-CyRvVMUp.d.ts → search-Cr4noJJZ.d.mts} +14 -3
- package/dist/{search-CCyFWPi3.d.mts → search-DdsniXQv.d.ts} +14 -3
- package/dist/seo.d.mts +30 -29
- package/dist/seo.d.ts +30 -29
- package/dist/seo.js +197 -718
- package/dist/seo.mjs +26 -24
- package/dist/server.d.mts +5 -5
- package/dist/server.d.ts +5 -5
- package/dist/sitemap.d.mts +5 -5
- package/dist/sitemap.d.ts +5 -5
- package/dist/sitemap.js +25 -14
- package/dist/sitemap.mjs +3 -2
- package/dist/skills.js +1 -1
- package/dist/skills.mjs +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/dist/suggest.js +17 -3
- package/dist/suggest.mjs +17 -3
- package/dist/well-known.js +23 -13
- package/dist/well-known.mjs +2 -2
- package/package.json +2 -4
- package/skills/cavuno-board-applications/SKILL.md +4 -1
- package/skills/cavuno-board-filters/SKILL.md +11 -12
- package/skills/cavuno-board-format/SKILL.md +68 -56
- package/skills/cavuno-board-i18n/SKILL.md +102 -21
- package/skills/cavuno-board-search-suggestions/SKILL.md +8 -3
- package/skills/cavuno-board-seo/BLOG.md +4 -1
- package/skills/cavuno-board-seo/JOB_AND_BREADCRUMBS.md +5 -2
- package/skills/cavuno-board-seo/LISTING.md +35 -12
- package/skills/cavuno-board-seo/SALARY.md +83 -18
- package/skills/cavuno-board-seo/SKILL.md +3 -2
- package/skills/manifest.json +3 -3
- package/dist/breadcrumbs-1hMjcsqA.d.mts +0 -171
- package/dist/breadcrumbs-DexU07TT.d.ts +0 -171
- package/dist/chunk-IJCJ26SQ.mjs +0 -596
- package/dist/chunk-L7ZOYWHW.mjs +0 -98
- package/dist/chunk-LUGJWOT7.mjs +0 -96
- package/dist/ui-copy-CCkz6CVF.d.mts +0 -230
- package/dist/ui-copy-CCkz6CVF.d.ts +0 -230
package/dist/seo.mjs
CHANGED
|
@@ -9,17 +9,19 @@ import {
|
|
|
9
9
|
createJobPostingJsonLd,
|
|
10
10
|
crossAxisSalaryJsonLd,
|
|
11
11
|
faqJsonLd,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
formatUsd,
|
|
12
|
+
formatSalaryStat,
|
|
13
|
+
formatSalaryStatRange,
|
|
15
14
|
itemListJsonLd,
|
|
16
15
|
locationSalaryJsonLd,
|
|
17
16
|
normalizeWebsiteUrl,
|
|
18
17
|
skillSalaryJsonLd,
|
|
19
18
|
sortBySeniority,
|
|
20
19
|
titleSalaryJsonLd
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import
|
|
20
|
+
} from "./chunk-YEAQ3JEI.mjs";
|
|
21
|
+
import {
|
|
22
|
+
boardUrl,
|
|
23
|
+
jobDetailPath
|
|
24
|
+
} from "./chunk-BGKNXG4H.mjs";
|
|
23
25
|
|
|
24
26
|
// src/seo/blog.ts
|
|
25
27
|
function createBlogArticleJsonLd({
|
|
@@ -138,9 +140,8 @@ function buildAbsoluteUrl(origin, path) {
|
|
|
138
140
|
|
|
139
141
|
// src/seo/listing.ts
|
|
140
142
|
function listingHead(options) {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const description = `Browse ${countPrefix}${options.heading.toLowerCase()} on ${options.boardName}.`;
|
|
143
|
+
const title = options.title;
|
|
144
|
+
const description = options.description;
|
|
144
145
|
const url = `${options.origin}${options.path}`;
|
|
145
146
|
return {
|
|
146
147
|
meta: [
|
|
@@ -155,31 +156,33 @@ function listingHead(options) {
|
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
function listingJsonLd(options) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
{
|
|
159
|
+
const objects = [];
|
|
160
|
+
if (options.breadcrumbs.length >= 2) {
|
|
161
|
+
objects.push({
|
|
161
162
|
"@context": "https://schema.org",
|
|
162
|
-
// Deliberately NOT createBreadcrumbJsonLd (breadcrumbs.ts): listing trails
|
|
163
|
-
// are code-constructed (never user copy), so the hosted listing page skips
|
|
164
|
-
// the trim/drop-blank/min-2 gate that module transcribes. Same schema.org
|
|
165
|
-
// fragment, different (intentional) semantics — do not unify blindly.
|
|
166
163
|
"@type": "BreadcrumbList",
|
|
167
164
|
itemListElement: options.breadcrumbs.map((crumb, index) => ({
|
|
168
165
|
"@type": "ListItem",
|
|
169
166
|
position: index + 1,
|
|
170
167
|
name: crumb.name,
|
|
171
|
-
...crumb.path ? { item:
|
|
168
|
+
...crumb.path ? { item: boardUrl(options.origin, crumb.path) } : {}
|
|
172
169
|
}))
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const detailJobs = (options.jobs ?? []).filter(
|
|
173
|
+
(job) => job.company != null && Boolean(job.company.slug)
|
|
174
|
+
);
|
|
175
|
+
if (detailJobs.length > 0) {
|
|
176
176
|
objects.push({
|
|
177
177
|
"@context": "https://schema.org",
|
|
178
178
|
"@type": "ItemList",
|
|
179
|
-
itemListElement:
|
|
179
|
+
itemListElement: detailJobs.map((job, index) => ({
|
|
180
180
|
"@type": "ListItem",
|
|
181
181
|
position: index + 1,
|
|
182
|
-
url:
|
|
182
|
+
url: boardUrl(
|
|
183
|
+
options.origin,
|
|
184
|
+
jobDetailPath(job.company.slug, job.slug)
|
|
185
|
+
)
|
|
183
186
|
}))
|
|
184
187
|
});
|
|
185
188
|
}
|
|
@@ -198,9 +201,8 @@ export {
|
|
|
198
201
|
createJobPostingJsonLd,
|
|
199
202
|
crossAxisSalaryJsonLd,
|
|
200
203
|
faqJsonLd,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
formatUsd,
|
|
204
|
+
formatSalaryStat,
|
|
205
|
+
formatSalaryStatRange,
|
|
204
206
|
itemListJsonLd,
|
|
205
207
|
listingHead,
|
|
206
208
|
listingJsonLd,
|
package/dist/server.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.mjs';
|
|
2
|
-
import './_spec-
|
|
3
|
-
import './search-
|
|
4
|
-
import './board-
|
|
5
|
-
import './jobs-
|
|
6
|
-
import './blog-
|
|
2
|
+
import './_spec-GVe6WAfT.mjs';
|
|
3
|
+
import './search-Cr4noJJZ.mjs';
|
|
4
|
+
import './board-GGWJ4coe.mjs';
|
|
5
|
+
import './jobs-BuLpV8iy.mjs';
|
|
6
|
+
import './blog-Cf64mI8A.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Session cookie codec — pure (no framework imports, no node imports) so it
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.js';
|
|
2
|
-
import './_spec-
|
|
3
|
-
import './search-
|
|
4
|
-
import './board-
|
|
5
|
-
import './jobs-
|
|
6
|
-
import './blog-
|
|
2
|
+
import './_spec-GVe6WAfT.js';
|
|
3
|
+
import './search-DdsniXQv.js';
|
|
4
|
+
import './board-DUhgiacm.js';
|
|
5
|
+
import './jobs-QrbXOYB9.js';
|
|
6
|
+
import './blog-BsmKizpd.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Session cookie codec — pure (no framework imports, no node imports) so it
|
package/dist/sitemap.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.mjs';
|
|
2
|
-
import './_spec-
|
|
3
|
-
import './search-
|
|
4
|
-
import './board-
|
|
5
|
-
import './jobs-
|
|
6
|
-
import './blog-
|
|
2
|
+
import './_spec-GVe6WAfT.mjs';
|
|
3
|
+
import './search-Cr4noJJZ.mjs';
|
|
4
|
+
import './board-GGWJ4coe.mjs';
|
|
5
|
+
import './jobs-BuLpV8iy.mjs';
|
|
6
|
+
import './blog-Cf64mI8A.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Sitemap primitives — the pure XML + bucket-filename logic behind a board
|
package/dist/sitemap.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { BoardSdk } from './index.js';
|
|
2
|
-
import './_spec-
|
|
3
|
-
import './search-
|
|
4
|
-
import './board-
|
|
5
|
-
import './jobs-
|
|
6
|
-
import './blog-
|
|
2
|
+
import './_spec-GVe6WAfT.js';
|
|
3
|
+
import './search-DdsniXQv.js';
|
|
4
|
+
import './board-DUhgiacm.js';
|
|
5
|
+
import './jobs-QrbXOYB9.js';
|
|
6
|
+
import './blog-BsmKizpd.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Sitemap primitives — the pure XML + bucket-filename logic behind a board
|
package/dist/sitemap.js
CHANGED
|
@@ -165,44 +165,54 @@ function paginate(listFn, query, options) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// src/paths/index.ts
|
|
168
|
+
function encodePathSegment(segment) {
|
|
169
|
+
try {
|
|
170
|
+
return encodeURIComponent(decodeURIComponent(segment)).replace(
|
|
171
|
+
/%3A/gi,
|
|
172
|
+
":"
|
|
173
|
+
);
|
|
174
|
+
} catch {
|
|
175
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
168
178
|
function jobDetailPath(companySlug, jobSlug) {
|
|
169
|
-
return `/companies/${companySlug}/jobs/${jobSlug}`;
|
|
179
|
+
return `/companies/${encodePathSegment(companySlug)}/jobs/${encodePathSegment(jobSlug)}`;
|
|
170
180
|
}
|
|
171
181
|
function jobsCategoryPath(categorySlug) {
|
|
172
|
-
return `/jobs/${categorySlug}`;
|
|
182
|
+
return `/jobs/${encodePathSegment(categorySlug)}`;
|
|
173
183
|
}
|
|
174
184
|
function jobsSkillPath(skillSlug) {
|
|
175
|
-
return `/jobs/skills/${skillSlug}`;
|
|
185
|
+
return `/jobs/skills/${encodePathSegment(skillSlug)}`;
|
|
176
186
|
}
|
|
177
187
|
function jobsLocationPath(placeSlug) {
|
|
178
|
-
return `/jobs/locations/${placeSlug}`;
|
|
188
|
+
return `/jobs/locations/${encodePathSegment(placeSlug)}`;
|
|
179
189
|
}
|
|
180
190
|
function companyPath(companySlug) {
|
|
181
|
-
return `/companies/${companySlug}`;
|
|
191
|
+
return `/companies/${encodePathSegment(companySlug)}`;
|
|
182
192
|
}
|
|
183
193
|
function companyMarketPath(marketSlug) {
|
|
184
|
-
return `/companies/markets/${marketSlug}`;
|
|
194
|
+
return `/companies/markets/${encodePathSegment(marketSlug)}`;
|
|
185
195
|
}
|
|
186
196
|
function companySalaryPath(companySlug) {
|
|
187
|
-
return `/companies/${companySlug}/salaries`;
|
|
197
|
+
return `/companies/${encodePathSegment(companySlug)}/salaries`;
|
|
188
198
|
}
|
|
189
199
|
function salaryTitlePath(titleSlug) {
|
|
190
|
-
return `/salaries/titles/${titleSlug}`;
|
|
200
|
+
return `/salaries/titles/${encodePathSegment(titleSlug)}`;
|
|
191
201
|
}
|
|
192
202
|
function salarySkillPath(skillSlug) {
|
|
193
|
-
return `/salaries/skills/${skillSlug}`;
|
|
203
|
+
return `/salaries/skills/${encodePathSegment(skillSlug)}`;
|
|
194
204
|
}
|
|
195
205
|
function salaryLocationPath(placeSlug) {
|
|
196
|
-
return `/salaries/locations/${placeSlug}`;
|
|
206
|
+
return `/salaries/locations/${encodePathSegment(placeSlug)}`;
|
|
197
207
|
}
|
|
198
208
|
function blogPostPath(postSlug) {
|
|
199
|
-
return `/blog/${postSlug}`;
|
|
209
|
+
return `/blog/${encodePathSegment(postSlug)}`;
|
|
200
210
|
}
|
|
201
211
|
function blogTagPath(tagSlug) {
|
|
202
|
-
return `/blog/tag/${tagSlug}`;
|
|
212
|
+
return `/blog/tag/${encodePathSegment(tagSlug)}`;
|
|
203
213
|
}
|
|
204
214
|
function blogAuthorPath(authorSlug) {
|
|
205
|
-
return `/blog/author/${authorSlug}`;
|
|
215
|
+
return `/blog/author/${encodePathSegment(authorSlug)}`;
|
|
206
216
|
}
|
|
207
217
|
var BOARD_PATHS = {
|
|
208
218
|
home: "/",
|
|
@@ -320,7 +330,8 @@ async function marketing(board, origin) {
|
|
|
320
330
|
`${origin}${BOARD_PATHS.cookiePolicy}`
|
|
321
331
|
];
|
|
322
332
|
if (features.impressum) urls.push(`${origin}${BOARD_PATHS.impressum}`);
|
|
323
|
-
if (features.talentDirectory
|
|
333
|
+
if (features.talentDirectory === "public")
|
|
334
|
+
urls.push(`${origin}${BOARD_PATHS.talent}`);
|
|
324
335
|
if (features.employers) urls.push(`${origin}${BOARD_PATHS.employers}`);
|
|
325
336
|
return urls;
|
|
326
337
|
}
|
package/dist/sitemap.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
salaryLocationPath,
|
|
17
17
|
salarySkillPath,
|
|
18
18
|
salaryTitlePath
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-BGKNXG4H.mjs";
|
|
20
20
|
|
|
21
21
|
// src/sitemap/xml.ts
|
|
22
22
|
var SITEMAP_BUCKETS = [
|
|
@@ -208,7 +208,8 @@ async function marketing(board, origin) {
|
|
|
208
208
|
`${origin}${BOARD_PATHS.cookiePolicy}`
|
|
209
209
|
];
|
|
210
210
|
if (features.impressum) urls.push(`${origin}${BOARD_PATHS.impressum}`);
|
|
211
|
-
if (features.talentDirectory
|
|
211
|
+
if (features.talentDirectory === "public")
|
|
212
|
+
urls.push(`${origin}${BOARD_PATHS.talent}`);
|
|
212
213
|
if (features.employers) urls.push(`${origin}${BOARD_PATHS.employers}`);
|
|
213
214
|
return urls;
|
|
214
215
|
}
|
package/dist/skills.js
CHANGED
|
@@ -57,7 +57,7 @@ function loadSkillCorpus(baseDir) {
|
|
|
57
57
|
).map((entry) => ({
|
|
58
58
|
path: entry.name,
|
|
59
59
|
content: (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(skillDir, entry.name), "utf8")
|
|
60
|
-
})).sort((a, b) => a.path.
|
|
60
|
+
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
61
61
|
return {
|
|
62
62
|
...skill,
|
|
63
63
|
content: (0, import_node_fs.readFileSync)(skillPath, "utf8"),
|
package/dist/skills.mjs
CHANGED
|
@@ -24,7 +24,7 @@ function loadSkillCorpus(baseDir) {
|
|
|
24
24
|
).map((entry) => ({
|
|
25
25
|
path: entry.name,
|
|
26
26
|
content: readFileSync(resolve(skillDir, entry.name), "utf8")
|
|
27
|
-
})).sort((a, b) => a.path.
|
|
27
|
+
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
28
28
|
return {
|
|
29
29
|
...skill,
|
|
30
30
|
content: readFileSync(skillPath, "utf8"),
|
package/dist/suggest.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import './_spec-
|
|
1
|
+
import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-Cr4noJJZ.mjs';
|
|
2
|
+
import './_spec-GVe6WAfT.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@cavuno/board/suggest` — framework-agnostic headless search-suggest
|
package/dist/suggest.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import './_spec-
|
|
1
|
+
import { h as SuggestionItem, b as SearchSuggestQuery, F as FetchOptions, g as SuggestResult } from './search-DdsniXQv.js';
|
|
2
|
+
import './_spec-GVe6WAfT.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@cavuno/board/suggest` — framework-agnostic headless search-suggest
|
package/dist/suggest.js
CHANGED
|
@@ -28,10 +28,24 @@ var DEFAULT_DEBOUNCE_MS = 250;
|
|
|
28
28
|
function isAbortError(error) {
|
|
29
29
|
return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
30
30
|
}
|
|
31
|
+
function queryCharCount(query) {
|
|
32
|
+
try {
|
|
33
|
+
let n = 0;
|
|
34
|
+
for (const _ of new Intl.Segmenter(void 0, {
|
|
35
|
+
granularity: "grapheme"
|
|
36
|
+
}).segment(query)) {
|
|
37
|
+
n += 1;
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
} catch {
|
|
41
|
+
return [...query].length;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
31
44
|
function applyExclusions(items, excluded) {
|
|
32
45
|
if (excluded.size === 0) return items.slice();
|
|
33
46
|
return items.filter(
|
|
34
|
-
(item) => !(item.type === "company" &&
|
|
47
|
+
(item) => !(item.type === "company" && // NFC + toLowerCase — same keying as setExcludedCompanySlugs.
|
|
48
|
+
excluded.has(item.slug.toLowerCase().normalize("NFC")))
|
|
35
49
|
);
|
|
36
50
|
}
|
|
37
51
|
function createSuggestController(board, options) {
|
|
@@ -104,7 +118,7 @@ function createSuggestController(board, options) {
|
|
|
104
118
|
if (disposed) return;
|
|
105
119
|
const trimmed = query.trim();
|
|
106
120
|
cancelTimer();
|
|
107
|
-
if (trimmed
|
|
121
|
+
if (queryCharCount(trimmed) < minChars) {
|
|
108
122
|
sequence += 1;
|
|
109
123
|
abortInFlight();
|
|
110
124
|
rawItems = [];
|
|
@@ -133,7 +147,7 @@ function createSuggestController(board, options) {
|
|
|
133
147
|
setExcludedCompanySlugs(slugs) {
|
|
134
148
|
if (disposed) return;
|
|
135
149
|
excluded = new Set(
|
|
136
|
-
slugs.map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
150
|
+
slugs.map((s) => s.trim().toLowerCase().normalize("NFC")).filter(Boolean)
|
|
137
151
|
);
|
|
138
152
|
setState({
|
|
139
153
|
query: state.query,
|
package/dist/suggest.mjs
CHANGED
|
@@ -4,10 +4,24 @@ var DEFAULT_DEBOUNCE_MS = 250;
|
|
|
4
4
|
function isAbortError(error) {
|
|
5
5
|
return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
|
|
6
6
|
}
|
|
7
|
+
function queryCharCount(query) {
|
|
8
|
+
try {
|
|
9
|
+
let n = 0;
|
|
10
|
+
for (const _ of new Intl.Segmenter(void 0, {
|
|
11
|
+
granularity: "grapheme"
|
|
12
|
+
}).segment(query)) {
|
|
13
|
+
n += 1;
|
|
14
|
+
}
|
|
15
|
+
return n;
|
|
16
|
+
} catch {
|
|
17
|
+
return [...query].length;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
function applyExclusions(items, excluded) {
|
|
8
21
|
if (excluded.size === 0) return items.slice();
|
|
9
22
|
return items.filter(
|
|
10
|
-
(item) => !(item.type === "company" &&
|
|
23
|
+
(item) => !(item.type === "company" && // NFC + toLowerCase — same keying as setExcludedCompanySlugs.
|
|
24
|
+
excluded.has(item.slug.toLowerCase().normalize("NFC")))
|
|
11
25
|
);
|
|
12
26
|
}
|
|
13
27
|
function createSuggestController(board, options) {
|
|
@@ -80,7 +94,7 @@ function createSuggestController(board, options) {
|
|
|
80
94
|
if (disposed) return;
|
|
81
95
|
const trimmed = query.trim();
|
|
82
96
|
cancelTimer();
|
|
83
|
-
if (trimmed
|
|
97
|
+
if (queryCharCount(trimmed) < minChars) {
|
|
84
98
|
sequence += 1;
|
|
85
99
|
abortInFlight();
|
|
86
100
|
rawItems = [];
|
|
@@ -109,7 +123,7 @@ function createSuggestController(board, options) {
|
|
|
109
123
|
setExcludedCompanySlugs(slugs) {
|
|
110
124
|
if (disposed) return;
|
|
111
125
|
excluded = new Set(
|
|
112
|
-
slugs.map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
126
|
+
slugs.map((s) => s.trim().toLowerCase().normalize("NFC")).filter(Boolean)
|
|
113
127
|
);
|
|
114
128
|
setState({
|
|
115
129
|
query: state.query,
|
package/dist/well-known.js
CHANGED
|
@@ -32,44 +32,54 @@ __export(well_known_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(well_known_exports);
|
|
33
33
|
|
|
34
34
|
// src/paths/index.ts
|
|
35
|
+
function encodePathSegment(segment) {
|
|
36
|
+
try {
|
|
37
|
+
return encodeURIComponent(decodeURIComponent(segment)).replace(
|
|
38
|
+
/%3A/gi,
|
|
39
|
+
":"
|
|
40
|
+
);
|
|
41
|
+
} catch {
|
|
42
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
35
45
|
function jobDetailPath(companySlug, jobSlug) {
|
|
36
|
-
return `/companies/${companySlug}/jobs/${jobSlug}`;
|
|
46
|
+
return `/companies/${encodePathSegment(companySlug)}/jobs/${encodePathSegment(jobSlug)}`;
|
|
37
47
|
}
|
|
38
48
|
function jobsCategoryPath(categorySlug) {
|
|
39
|
-
return `/jobs/${categorySlug}`;
|
|
49
|
+
return `/jobs/${encodePathSegment(categorySlug)}`;
|
|
40
50
|
}
|
|
41
51
|
function jobsSkillPath(skillSlug) {
|
|
42
|
-
return `/jobs/skills/${skillSlug}`;
|
|
52
|
+
return `/jobs/skills/${encodePathSegment(skillSlug)}`;
|
|
43
53
|
}
|
|
44
54
|
function jobsLocationPath(placeSlug) {
|
|
45
|
-
return `/jobs/locations/${placeSlug}`;
|
|
55
|
+
return `/jobs/locations/${encodePathSegment(placeSlug)}`;
|
|
46
56
|
}
|
|
47
57
|
function companyPath(companySlug) {
|
|
48
|
-
return `/companies/${companySlug}`;
|
|
58
|
+
return `/companies/${encodePathSegment(companySlug)}`;
|
|
49
59
|
}
|
|
50
60
|
function companyMarketPath(marketSlug) {
|
|
51
|
-
return `/companies/markets/${marketSlug}`;
|
|
61
|
+
return `/companies/markets/${encodePathSegment(marketSlug)}`;
|
|
52
62
|
}
|
|
53
63
|
function companySalaryPath(companySlug) {
|
|
54
|
-
return `/companies/${companySlug}/salaries`;
|
|
64
|
+
return `/companies/${encodePathSegment(companySlug)}/salaries`;
|
|
55
65
|
}
|
|
56
66
|
function salaryTitlePath(titleSlug) {
|
|
57
|
-
return `/salaries/titles/${titleSlug}`;
|
|
67
|
+
return `/salaries/titles/${encodePathSegment(titleSlug)}`;
|
|
58
68
|
}
|
|
59
69
|
function salarySkillPath(skillSlug) {
|
|
60
|
-
return `/salaries/skills/${skillSlug}`;
|
|
70
|
+
return `/salaries/skills/${encodePathSegment(skillSlug)}`;
|
|
61
71
|
}
|
|
62
72
|
function salaryLocationPath(placeSlug) {
|
|
63
|
-
return `/salaries/locations/${placeSlug}`;
|
|
73
|
+
return `/salaries/locations/${encodePathSegment(placeSlug)}`;
|
|
64
74
|
}
|
|
65
75
|
function blogPostPath(postSlug) {
|
|
66
|
-
return `/blog/${postSlug}`;
|
|
76
|
+
return `/blog/${encodePathSegment(postSlug)}`;
|
|
67
77
|
}
|
|
68
78
|
function blogTagPath(tagSlug) {
|
|
69
|
-
return `/blog/tag/${tagSlug}`;
|
|
79
|
+
return `/blog/tag/${encodePathSegment(tagSlug)}`;
|
|
70
80
|
}
|
|
71
81
|
function blogAuthorPath(authorSlug) {
|
|
72
|
-
return `/blog/author/${authorSlug}`;
|
|
82
|
+
return `/blog/author/${encodePathSegment(authorSlug)}`;
|
|
73
83
|
}
|
|
74
84
|
var BOARD_PATHS = {
|
|
75
85
|
home: "/",
|
package/dist/well-known.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavuno/board",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Build custom job boards and careers pages with Cavuno’s TypeScript SDK.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -203,9 +203,7 @@
|
|
|
203
203
|
"clean": "git clean -xdf .turbo dist node_modules src/generated",
|
|
204
204
|
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.node.json",
|
|
205
205
|
"test": "vitest run",
|
|
206
|
-
"check:publish": "publint --strict && attw --pack . --profile node16",
|
|
207
|
-
"check:surface": "node ../../scripts/check-public-surface.mjs",
|
|
208
|
-
"gen:surface": "node ../../scripts/check-public-surface.mjs --write",
|
|
206
|
+
"check:publish": "publint --strict && attw --pack . --profile node16 && node scripts/check-no-private-kit-types.mjs",
|
|
209
207
|
"gen:skills-manifest": "tsx scripts/generate-skills-manifest.ts",
|
|
210
208
|
"assert-publish-target": "node -e \"const p=require('./package.json'); if(p.name!=='@cavuno/board'){throw new Error('Refusing to publish: package.json name is '+p.name+', expected @cavuno/board')}; if(p.private){throw new Error('Refusing to publish: package.json has private:true')}\"",
|
|
211
209
|
"prepublishOnly": "pnpm run assert-publish-target && pnpm run gen:skills-manifest && pnpm run build"
|
|
@@ -76,7 +76,10 @@ await board.me.applications.withdraw(applicationId);
|
|
|
76
76
|
|
|
77
77
|
## Save jobs
|
|
78
78
|
|
|
79
|
-
Saved rows embed the
|
|
79
|
+
Saved rows embed the same slim `PublicJobCard` as the jobs list — render them
|
|
80
|
+
with the same card view-model, and do not expect full-job fields (description,
|
|
81
|
+
custom field values) on saved rows. `save` converges on the existing row and
|
|
82
|
+
`unsave` is idempotent.
|
|
80
83
|
|
|
81
84
|
```ts snippet
|
|
82
85
|
const saved = await board.me.savedJobs.list({ limit: 20 });
|
|
@@ -49,21 +49,19 @@ import {
|
|
|
49
49
|
JOB_SORTS,
|
|
50
50
|
REMOTE_OPTIONS,
|
|
51
51
|
SENIORITIES,
|
|
52
|
-
seniorityLabels,
|
|
53
|
-
sortLabels,
|
|
54
52
|
} from '@cavuno/board/filters';
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
54
|
+
// Wire enums only — display labels are application-owned chrome.
|
|
55
|
+
const seniorityOptions = SENIORITIES;
|
|
56
|
+
const sortOptions = JOB_SORTS;
|
|
59
57
|
```
|
|
60
58
|
|
|
61
59
|
Render seniority as a multi-select with all eight `SENIORITIES`.
|
|
62
60
|
`EMPLOYMENT_TYPES` contains five listing options; `volunteer` and `other`
|
|
63
61
|
remain valid job wire values but are absent from the filter control.
|
|
64
62
|
`JOB_SORTS` contains exactly `relevance`, `newest`, and `salary_high`;
|
|
65
|
-
`relevance` is the featured-ranked default.
|
|
66
|
-
|
|
63
|
+
`relevance` is the featured-ranked default. Label each option with your
|
|
64
|
+
application's copy (message catalog or hard-coded board language).
|
|
67
65
|
|
|
68
66
|
## Load taxonomy options
|
|
69
67
|
|
|
@@ -95,15 +93,16 @@ const second = first.nextCursor
|
|
|
95
93
|
: null;
|
|
96
94
|
```
|
|
97
95
|
|
|
98
|
-
Keyword suggestions accept `q` and `
|
|
99
|
-
characters returns no results.
|
|
100
|
-
because both
|
|
101
|
-
`
|
|
96
|
+
Keyword suggestions accept `q`, `limit`, and optional `types`. A present query
|
|
97
|
+
shorter than two characters returns no results. Restrict to taxonomy terms with
|
|
98
|
+
`types: ['category', 'skill']`; because both may share a slug, key each option
|
|
99
|
+
by `termType + canonicalSlug`.
|
|
102
100
|
|
|
103
101
|
```ts snippet
|
|
104
|
-
const
|
|
102
|
+
const { items } = await board.search.suggest({
|
|
105
103
|
q: searchText,
|
|
106
104
|
limit: 10,
|
|
105
|
+
types: ['category', 'skill'],
|
|
107
106
|
});
|
|
108
107
|
```
|
|
109
108
|
|