@profullstack/agenticjobs 0.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/LICENSE +21 -0
- package/README.md +219 -0
- package/bin/agenticjobs-mcp.mjs +2 -0
- package/bin/agenticjobs.mjs +2 -0
- package/dist/cli/args.d.ts +17 -0
- package/dist/cli/args.js +103 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/format.d.ts +15 -0
- package/dist/cli/format.js +24 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.js +620 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/jobfile.d.ts +15 -0
- package/dist/cli/jobfile.js +94 -0
- package/dist/cli/jobfile.js.map +1 -0
- package/dist/client/client.d.ts +105 -0
- package/dist/client/client.js +182 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/config.d.ts +33 -0
- package/dist/client/config.js +92 -0
- package/dist/client/config.js.map +1 -0
- package/dist/client/fanout.d.ts +35 -0
- package/dist/client/fanout.js +77 -0
- package/dist/client/fanout.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/login.d.ts +23 -0
- package/dist/client/login.js +45 -0
- package/dist/client/login.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/core/applications.d.ts +67 -0
- package/dist/core/applications.js +187 -0
- package/dist/core/applications.js.map +1 -0
- package/dist/core/auth.d.ts +120 -0
- package/dist/core/auth.js +246 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/import.d.ts +52 -0
- package/dist/core/import.js +311 -0
- package/dist/core/import.js.map +1 -0
- package/dist/core/jobs.d.ts +91 -0
- package/dist/core/jobs.js +381 -0
- package/dist/core/jobs.js.map +1 -0
- package/dist/core/orgs.d.ts +21 -0
- package/dist/core/orgs.js +111 -0
- package/dist/core/orgs.js.map +1 -0
- package/dist/core/resumes.d.ts +51 -0
- package/dist/core/resumes.js +144 -0
- package/dist/core/resumes.js.map +1 -0
- package/dist/core/zip.d.ts +17 -0
- package/dist/core/zip.js +77 -0
- package/dist/core/zip.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +15 -0
- package/dist/db/migrate.js +67 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/pool.d.ts +13 -0
- package/dist/db/pool.js +68 -0
- package/dist/db/pool.js.map +1 -0
- package/dist/db/seed.d.ts +9 -0
- package/dist/db/seed.js +120 -0
- package/dist/db/seed.js.map +1 -0
- package/dist/directory/announce.d.ts +32 -0
- package/dist/directory/announce.js +59 -0
- package/dist/directory/announce.js.map +1 -0
- package/dist/directory/federate.d.ts +59 -0
- package/dist/directory/federate.js +126 -0
- package/dist/directory/federate.js.map +1 -0
- package/dist/directory/fetch.d.ts +35 -0
- package/dist/directory/fetch.js +138 -0
- package/dist/directory/fetch.js.map +1 -0
- package/dist/directory/index.d.ts +4 -0
- package/dist/directory/index.js +5 -0
- package/dist/directory/index.js.map +1 -0
- package/dist/directory/registry.d.ts +55 -0
- package/dist/directory/registry.js +155 -0
- package/dist/directory/registry.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/markup/escape.d.ts +20 -0
- package/dist/markup/escape.js +56 -0
- package/dist/markup/escape.js.map +1 -0
- package/dist/markup/markdown.d.ts +35 -0
- package/dist/markup/markdown.js +279 -0
- package/dist/markup/markdown.js.map +1 -0
- package/dist/markup/resume.d.ts +72 -0
- package/dist/markup/resume.js +260 -0
- package/dist/markup/resume.js.map +1 -0
- package/dist/mcp/protocol.d.ts +56 -0
- package/dist/mcp/protocol.js +48 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +21 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +13 -0
- package/dist/mcp/stdio.js +85 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.js +351 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +6 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/instance.d.ts +85 -0
- package/dist/schema/instance.js +147 -0
- package/dist/schema/instance.js.map +1 -0
- package/dist/schema/job.d.ts +151 -0
- package/dist/schema/job.js +73 -0
- package/dist/schema/job.js.map +1 -0
- package/dist/schema/jsonld.d.ts +18 -0
- package/dist/schema/jsonld.js +120 -0
- package/dist/schema/jsonld.js.map +1 -0
- package/dist/schema/query.d.ts +47 -0
- package/dist/schema/query.js +103 -0
- package/dist/schema/query.js.map +1 -0
- package/dist/schema/text.d.ts +27 -0
- package/dist/schema/text.js +117 -0
- package/dist/schema/text.js.map +1 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.js +57 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/deps.d.ts +20 -0
- package/dist/server/deps.js +2 -0
- package/dist/server/deps.js.map +1 -0
- package/dist/server/middleware.d.ts +39 -0
- package/dist/server/middleware.js +100 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/routes/api.d.ts +20 -0
- package/dist/server/routes/api.js +584 -0
- package/dist/server/routes/api.js.map +1 -0
- package/dist/server/routes/descriptor.d.ts +11 -0
- package/dist/server/routes/descriptor.js +31 -0
- package/dist/server/routes/descriptor.js.map +1 -0
- package/dist/server/routes/discovery.d.ts +12 -0
- package/dist/server/routes/discovery.js +305 -0
- package/dist/server/routes/discovery.js.map +1 -0
- package/dist/server/routes/mcp.d.ts +19 -0
- package/dist/server/routes/mcp.js +85 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/openapi.d.ts +11 -0
- package/dist/server/routes/openapi.js +297 -0
- package/dist/server/routes/openapi.js.map +1 -0
- package/dist/server/routes/pages.d.ts +11 -0
- package/dist/server/routes/pages.js +445 -0
- package/dist/server/routes/pages.js.map +1 -0
- package/dist/server/routes/passkey.d.ts +20 -0
- package/dist/server/routes/passkey.js +198 -0
- package/dist/server/routes/passkey.js.map +1 -0
- package/dist/server/routes/specs.d.ts +10 -0
- package/dist/server/routes/specs.js +42 -0
- package/dist/server/routes/specs.js.map +1 -0
- package/dist/server/serve.d.ts +11 -0
- package/dist/server/serve.js +79 -0
- package/dist/server/serve.js.map +1 -0
- package/dist/tui/index.d.ts +13 -0
- package/dist/tui/index.js +266 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/state.d.ts +63 -0
- package/dist/tui/state.js +136 -0
- package/dist/tui/state.js.map +1 -0
- package/dist/tui/types.d.ts +2 -0
- package/dist/tui/types.js +2 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/tui/views.d.ts +11 -0
- package/dist/tui/views.js +197 -0
- package/dist/tui/views.js.map +1 -0
- package/dist/views/auth.d.ts +21 -0
- package/dist/views/auth.js +5 -0
- package/dist/views/auth.js.map +1 -0
- package/dist/views/docs.d.ts +17 -0
- package/dist/views/docs.js +40 -0
- package/dist/views/docs.js.map +1 -0
- package/dist/views/jobs.d.ts +49 -0
- package/dist/views/jobs.js +44 -0
- package/dist/views/jobs.js.map +1 -0
- package/dist/views/layout.d.ts +58 -0
- package/dist/views/layout.js +36 -0
- package/dist/views/layout.js.map +1 -0
- package/dist/views/me.d.ts +27 -0
- package/dist/views/me.js +6 -0
- package/dist/views/me.js.map +1 -0
- package/dist/views/network.d.ts +21 -0
- package/dist/views/network.js +17 -0
- package/dist/views/network.js.map +1 -0
- package/dist/views/post.d.ts +22 -0
- package/dist/views/post.js +20 -0
- package/dist/views/post.js.map +1 -0
- package/docs/openjob.md +200 -0
- package/docs/openresume.md +143 -0
- package/migrations/0001_init.sql +109 -0
- package/migrations/0002_auth.sql +82 -0
- package/migrations/0003_directory.sql +27 -0
- package/migrations/0004_resumes.sql +63 -0
- package/migrations/0005_application_drafts.sql +20 -0
- package/package.json +92 -0
- package/web/public/app.css +731 -0
- package/web/public/app.js +173 -0
- package/web/public/icon.svg +5 -0
- package/web/public/sw.js +64 -0
- package/web/public/tokens.css +153 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directory: which instances exist.
|
|
3
|
+
*
|
|
4
|
+
* An instance announces itself by sending nothing but its own URL. The
|
|
5
|
+
* directory then fetches that URL's descriptor itself and records what it
|
|
6
|
+
* actually read. nixamp's stream directory trusts the announcement body, which
|
|
7
|
+
* is right for a stream whose only claim is "I am playing something"; here an
|
|
8
|
+
* announcement carries a name, a topic list and a job count, and a board that
|
|
9
|
+
* could announce someone else's name with its own URL underneath would be a
|
|
10
|
+
* phishing primitive on day one.
|
|
11
|
+
*/
|
|
12
|
+
import { MAX_FAILURES, ONLINE_TTL_MS, publishable, } from "../schema/instance.js";
|
|
13
|
+
import { fetchDescriptor, FetchProblem } from "./fetch.js";
|
|
14
|
+
function toListing(row, now = Date.now()) {
|
|
15
|
+
return {
|
|
16
|
+
id: row.id,
|
|
17
|
+
url: row.url,
|
|
18
|
+
descriptor: row.descriptor,
|
|
19
|
+
updatedAt: row.checked_at,
|
|
20
|
+
firstSeenAt: row.first_seen_at,
|
|
21
|
+
failures: row.failures,
|
|
22
|
+
online: now - Date.parse(row.checked_at) < ONLINE_TTL_MS && row.failures === 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export class Blocked extends Error {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Record an announcement.
|
|
29
|
+
*
|
|
30
|
+
* Idempotent per origin: an instance that heartbeats every ten minutes updates
|
|
31
|
+
* one row forever rather than accumulating a history of itself.
|
|
32
|
+
*/
|
|
33
|
+
export async function announce(pool, rawUrl) {
|
|
34
|
+
const origin = publishable(rawUrl);
|
|
35
|
+
if (origin === null)
|
|
36
|
+
throw new FetchProblem(`${rawUrl} is not an address we will list`);
|
|
37
|
+
const url = origin.origin;
|
|
38
|
+
const existing = await pool.query(`select blocked, blocked_reason from instances where url = $1`, [url]);
|
|
39
|
+
const row = existing.rows[0];
|
|
40
|
+
if (row?.blocked === true) {
|
|
41
|
+
throw new Blocked(row.blocked_reason ?? 'This instance is not accepted here.');
|
|
42
|
+
}
|
|
43
|
+
const descriptor = await fetchDescriptor(url);
|
|
44
|
+
const saved = await pool.query(`insert into instances (url, descriptor, checked_at, failures)
|
|
45
|
+
values ($1, $2::jsonb, now(), 0)
|
|
46
|
+
on conflict (url) do update
|
|
47
|
+
set descriptor = excluded.descriptor,
|
|
48
|
+
checked_at = now(),
|
|
49
|
+
failures = 0
|
|
50
|
+
returning id, url, descriptor, first_seen_at, checked_at, failures, blocked`, [url, JSON.stringify(descriptor)]);
|
|
51
|
+
const result = saved.rows[0];
|
|
52
|
+
if (result === undefined)
|
|
53
|
+
throw new Error('instance upsert returned no row');
|
|
54
|
+
return toListing(result);
|
|
55
|
+
}
|
|
56
|
+
export async function listInstances(pool, options = {}) {
|
|
57
|
+
const params = [];
|
|
58
|
+
const where = ['blocked = false'];
|
|
59
|
+
if (options.onlineOnly === true) {
|
|
60
|
+
params.push(String(ONLINE_TTL_MS));
|
|
61
|
+
where.push(`checked_at > now() - ($${params.length} || ' milliseconds')::interval`);
|
|
62
|
+
where.push('failures = 0');
|
|
63
|
+
}
|
|
64
|
+
if (options.topic) {
|
|
65
|
+
params.push(options.topic.toLowerCase());
|
|
66
|
+
where.push(`descriptor -> 'topics' ? $${params.length}`);
|
|
67
|
+
}
|
|
68
|
+
if (options.q) {
|
|
69
|
+
params.push(`%${options.q.toLowerCase()}%`);
|
|
70
|
+
where.push(`(lower(descriptor ->> 'name') like $${params.length} or lower(descriptor ->> 'tagline') like $${params.length} or url like $${params.length})`);
|
|
71
|
+
}
|
|
72
|
+
params.push(Math.min(500, Math.max(1, options.limit ?? 100)));
|
|
73
|
+
params.push(Math.max(0, options.offset ?? 0));
|
|
74
|
+
const result = await pool.query(`select id, url, descriptor, first_seen_at, checked_at, failures, blocked
|
|
75
|
+
from instances
|
|
76
|
+
where ${where.join(' and ')}
|
|
77
|
+
order by failures asc,
|
|
78
|
+
(descriptor -> 'jobs' ->> 'open')::int desc nulls last,
|
|
79
|
+
checked_at desc
|
|
80
|
+
limit $${params.length - 1} offset $${params.length}`, params);
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
return result.rows.map((row) => toListing(row, now));
|
|
83
|
+
}
|
|
84
|
+
export async function getInstance(pool, url) {
|
|
85
|
+
const origin = publishable(url);
|
|
86
|
+
if (origin === null)
|
|
87
|
+
return null;
|
|
88
|
+
const result = await pool.query(`select id, url, descriptor, first_seen_at, checked_at, failures, blocked
|
|
89
|
+
from instances where url = $1`, [origin.origin]);
|
|
90
|
+
const row = result.rows[0];
|
|
91
|
+
return row === undefined ? null : toListing(row);
|
|
92
|
+
}
|
|
93
|
+
/** Every topic the directory knows about, most-used first. */
|
|
94
|
+
export async function listTopics(pool) {
|
|
95
|
+
const result = await pool.query(`select topic, count(*)::bigint as instances
|
|
96
|
+
from instances, jsonb_array_elements_text(descriptor -> 'topics') as topic
|
|
97
|
+
where blocked = false
|
|
98
|
+
group by topic
|
|
99
|
+
order by instances desc, topic asc
|
|
100
|
+
limit 100`);
|
|
101
|
+
return result.rows;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Re-read every instance the directory knows about.
|
|
105
|
+
*
|
|
106
|
+
* A failure raises a counter rather than deleting the row: an instance is
|
|
107
|
+
* allowed to be down for a redeploy without losing its place, and the URL only
|
|
108
|
+
* goes away after MAX_FAILURES consecutive misses, which at the sweep interval
|
|
109
|
+
* is the better part of a day.
|
|
110
|
+
*/
|
|
111
|
+
export async function sweep(pool, options = {}) {
|
|
112
|
+
const due = await pool.query(`select url from instances
|
|
113
|
+
where blocked = false
|
|
114
|
+
order by checked_at asc
|
|
115
|
+
limit $1`, [Math.min(500, Math.max(1, options.limit ?? 100))]);
|
|
116
|
+
const urls = due.rows.map((row) => row.url);
|
|
117
|
+
const result = { checked: urls.length, ok: 0, failed: 0, dropped: 0 };
|
|
118
|
+
const concurrency = Math.min(16, Math.max(1, options.concurrency ?? 8));
|
|
119
|
+
let cursor = 0;
|
|
120
|
+
const workers = Array.from({ length: Math.min(concurrency, urls.length) }, async () => {
|
|
121
|
+
for (;;) {
|
|
122
|
+
const index = cursor;
|
|
123
|
+
cursor += 1;
|
|
124
|
+
const url = urls[index];
|
|
125
|
+
if (url === undefined)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
const descriptor = await fetchDescriptor(url);
|
|
129
|
+
await pool.query(`update instances set descriptor = $2::jsonb, checked_at = now(), failures = 0
|
|
130
|
+
where url = $1`, [url, JSON.stringify(descriptor)]);
|
|
131
|
+
result.ok += 1;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
const bumped = await pool.query(`update instances set failures = failures + 1 where url = $1 returning failures`, [url]);
|
|
135
|
+
result.failed += 1;
|
|
136
|
+
if ((bumped.rows[0]?.failures ?? 0) >= MAX_FAILURES) {
|
|
137
|
+
await pool.query(`delete from instances where url = $1`, [url]);
|
|
138
|
+
result.dropped += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
await Promise.all(workers);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
export async function blockInstance(pool, url, reason) {
|
|
147
|
+
const origin = publishable(url);
|
|
148
|
+
if (origin === null)
|
|
149
|
+
return false;
|
|
150
|
+
const result = await pool.query(`insert into instances (url, descriptor, blocked, blocked_reason)
|
|
151
|
+
values ($1, '{}'::jsonb, true, $2)
|
|
152
|
+
on conflict (url) do update set blocked = true, blocked_reason = $2`, [origin.origin, reason.slice(0, 300)]);
|
|
153
|
+
return (result.rowCount ?? 0) > 0;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/directory/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's public surface.
|
|
3
|
+
*
|
|
4
|
+
* Kept to the parts another program has a reason to import: the schema (so a
|
|
5
|
+
* tool can speak OpenJob without this board), the client (so it can talk to
|
|
6
|
+
* one), and the markup pipeline. The server half is reachable by path but is
|
|
7
|
+
* not part of the promise.
|
|
8
|
+
*/
|
|
9
|
+
export * from './schema/index.ts';
|
|
10
|
+
export * from './client/index.ts';
|
|
11
|
+
export { renderMarkdown, renderInline, toPlainText } from './markup/markdown.ts';
|
|
12
|
+
export { parseResume, resumeTemplate, resumeSearchText } from './markup/resume.ts';
|
|
13
|
+
export type { OpenResume, ResumeEntry, ResumeSection } from './markup/resume.ts';
|
|
14
|
+
export { VERSION, SOFTWARE_NAME } from './config.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's public surface.
|
|
3
|
+
*
|
|
4
|
+
* Kept to the parts another program has a reason to import: the schema (so a
|
|
5
|
+
* tool can speak OpenJob without this board), the client (so it can talk to
|
|
6
|
+
* one), and the markup pipeline. The server half is reachable by path but is
|
|
7
|
+
* not part of the promise.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./schema/index.js";
|
|
10
|
+
export * from "./client/index.js";
|
|
11
|
+
export { renderMarkdown, renderInline, toPlainText } from "./markup/markdown.js";
|
|
12
|
+
export { parseResume, resumeTemplate, resumeSearchText } from "./markup/resume.js";
|
|
13
|
+
export { VERSION, SOFTWARE_NAME } from "./config.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markup pipeline is safe by construction: source text is escaped as it is
|
|
3
|
+
* emitted, and the only tags that can appear in the output are ones this
|
|
4
|
+
* module writes literally. Raw HTML in a job description, a cover letter or a
|
|
5
|
+
* resume is content, not markup.
|
|
6
|
+
*
|
|
7
|
+
* Do not "improve" this by adding a sanitiser or a raw-HTML passthrough. A
|
|
8
|
+
* sanitiser is a denylist with a long history of bypasses; this is an
|
|
9
|
+
* allowlist that cannot be bypassed, because there is no path from input to a
|
|
10
|
+
* tag at all.
|
|
11
|
+
*/
|
|
12
|
+
export declare function escapeHtml(input: string): string;
|
|
13
|
+
export declare function safeUrl(input: string): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Anything interpolated into a <script> element goes through this. A bare
|
|
16
|
+
* JSON.stringify of text containing `</script>` closes the element and the
|
|
17
|
+
* rest of the document parses as markup, which is stored XSS on a field as
|
|
18
|
+
* ordinary as a job title.
|
|
19
|
+
*/
|
|
20
|
+
export declare function jsonForScript(value: unknown): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markup pipeline is safe by construction: source text is escaped as it is
|
|
3
|
+
* emitted, and the only tags that can appear in the output are ones this
|
|
4
|
+
* module writes literally. Raw HTML in a job description, a cover letter or a
|
|
5
|
+
* resume is content, not markup.
|
|
6
|
+
*
|
|
7
|
+
* Do not "improve" this by adding a sanitiser or a raw-HTML passthrough. A
|
|
8
|
+
* sanitiser is a denylist with a long history of bypasses; this is an
|
|
9
|
+
* allowlist that cannot be bypassed, because there is no path from input to a
|
|
10
|
+
* tag at all.
|
|
11
|
+
*/
|
|
12
|
+
const HTML_ESCAPES = {
|
|
13
|
+
'&': '&',
|
|
14
|
+
'<': '<',
|
|
15
|
+
'>': '>',
|
|
16
|
+
'"': '"',
|
|
17
|
+
"'": ''',
|
|
18
|
+
};
|
|
19
|
+
export function escapeHtml(input) {
|
|
20
|
+
return input.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character] ?? character);
|
|
21
|
+
}
|
|
22
|
+
/** Only these schemes ever reach an href or a src. Everything else is dropped. */
|
|
23
|
+
const SAFE_SCHEME = /^(https?:|mailto:|\/|#)/i;
|
|
24
|
+
export function safeUrl(input) {
|
|
25
|
+
const trimmed = input.trim();
|
|
26
|
+
if (trimmed === '')
|
|
27
|
+
return null;
|
|
28
|
+
// A control character in the middle of a URL is how `javascript:` gets past
|
|
29
|
+
// a scheme check: `java\nscript:` fails a naive test and is still executed
|
|
30
|
+
// by the parser.
|
|
31
|
+
for (const character of trimmed) {
|
|
32
|
+
const code = character.codePointAt(0) ?? 0;
|
|
33
|
+
if (code < 0x20 || code === 0x7f)
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (!SAFE_SCHEME.test(trimmed))
|
|
37
|
+
return null;
|
|
38
|
+
return trimmed;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Anything interpolated into a <script> element goes through this. A bare
|
|
42
|
+
* JSON.stringify of text containing `</script>` closes the element and the
|
|
43
|
+
* rest of the document parses as markup, which is stored XSS on a field as
|
|
44
|
+
* ordinary as a job title.
|
|
45
|
+
*/
|
|
46
|
+
export function jsonForScript(value) {
|
|
47
|
+
return JSON.stringify(value)
|
|
48
|
+
.replace(/</g, '\\u003c')
|
|
49
|
+
.replace(/>/g, '\\u003e')
|
|
50
|
+
.replace(/&/g, '\\u0026')
|
|
51
|
+
.split(String.fromCharCode(0x2028))
|
|
52
|
+
.join('\\u2028')
|
|
53
|
+
.split(String.fromCharCode(0x2029))
|
|
54
|
+
.join('\\u2029');
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=escape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/markup/escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC;AACxF,CAAC;AAED,kFAAkF;AAClF,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAChC,4EAA4E;IAC5E,2EAA2E;IAC3E,iBAAiB;IACjB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;SAClC,IAAI,CAAC,SAAS,CAAC;SACf,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;SAClC,IAAI,CAAC,SAAS,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown, rendered to HTML without ever passing raw HTML through.
|
|
3
|
+
*
|
|
4
|
+
* Everything a person or an agent writes on this board is Markdown: job
|
|
5
|
+
* descriptions, employer profiles, cover letters and whole resumes. One
|
|
6
|
+
* renderer serves all of them, so a table in a resume and a table in a job
|
|
7
|
+
* description behave the same, and neither can introduce a tag.
|
|
8
|
+
*
|
|
9
|
+
* The grammar is CommonMark's common subset plus GitHub tables and
|
|
10
|
+
* strikethrough. It is not a complete CommonMark implementation and does not
|
|
11
|
+
* try to be: the constructs left out are either unsafe here (raw HTML) or
|
|
12
|
+
* unused by the documents this board actually stores.
|
|
13
|
+
*/
|
|
14
|
+
export interface MarkdownOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Added to every heading level, so a document embedded in a page whose h1
|
|
17
|
+
* is the job title does not emit a second h1.
|
|
18
|
+
*/
|
|
19
|
+
headingOffset?: number;
|
|
20
|
+
/** Rendered as links but never as images. Resumes are read by strangers. */
|
|
21
|
+
noImages?: boolean;
|
|
22
|
+
/** Appended to every link. */
|
|
23
|
+
linkRel?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function renderMarkdown(source: string, options?: MarkdownOptions): string;
|
|
26
|
+
/**
|
|
27
|
+
* Inline markup.
|
|
28
|
+
*
|
|
29
|
+
* Code spans are pulled out first and put back last, so a backticked
|
|
30
|
+
* `**not bold**` stays literal and an asterisk inside code cannot open
|
|
31
|
+
* emphasis that then runs off through the rest of the paragraph.
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderInline(source: string, options?: MarkdownOptions): string;
|
|
34
|
+
/** Plain text, for meta descriptions, feeds, the TUI and search snippets. */
|
|
35
|
+
export declare function toPlainText(source: string, limit?: number): string;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown, rendered to HTML without ever passing raw HTML through.
|
|
3
|
+
*
|
|
4
|
+
* Everything a person or an agent writes on this board is Markdown: job
|
|
5
|
+
* descriptions, employer profiles, cover letters and whole resumes. One
|
|
6
|
+
* renderer serves all of them, so a table in a resume and a table in a job
|
|
7
|
+
* description behave the same, and neither can introduce a tag.
|
|
8
|
+
*
|
|
9
|
+
* The grammar is CommonMark's common subset plus GitHub tables and
|
|
10
|
+
* strikethrough. It is not a complete CommonMark implementation and does not
|
|
11
|
+
* try to be: the constructs left out are either unsafe here (raw HTML) or
|
|
12
|
+
* unused by the documents this board actually stores.
|
|
13
|
+
*/
|
|
14
|
+
import { escapeHtml, safeUrl } from "./escape.js";
|
|
15
|
+
const DEFAULT_REL = 'nofollow ugc noopener noreferrer';
|
|
16
|
+
export function renderMarkdown(source, options = {}) {
|
|
17
|
+
const lines = source.replace(/\r\n?/g, '\n').split('\n');
|
|
18
|
+
const out = [];
|
|
19
|
+
let index = 0;
|
|
20
|
+
while (index < lines.length) {
|
|
21
|
+
const line = lines[index] ?? '';
|
|
22
|
+
if (line.trim() === '') {
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Fenced code, taken first so nothing inside a fence is parsed as markup.
|
|
27
|
+
const fence = /^\s{0,3}(`{3,}|~{3,})\s*([A-Za-z0-9_+-]*)\s*$/.exec(line);
|
|
28
|
+
if (fence !== null) {
|
|
29
|
+
const marker = (fence[1] ?? '```').startsWith('`') ? '`{3,}' : '~{3,}';
|
|
30
|
+
const language = fence[2] ?? '';
|
|
31
|
+
const closing = new RegExp(`^\\s{0,3}${marker}\\s*$`);
|
|
32
|
+
const body = [];
|
|
33
|
+
index += 1;
|
|
34
|
+
while (index < lines.length) {
|
|
35
|
+
const current = lines[index] ?? '';
|
|
36
|
+
if (closing.test(current)) {
|
|
37
|
+
index += 1;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
body.push(current);
|
|
41
|
+
index += 1;
|
|
42
|
+
}
|
|
43
|
+
const className = language === '' ? '' : ` class="language-${escapeHtml(language)}"`;
|
|
44
|
+
out.push(`<pre><code${className}>${escapeHtml(body.join('\n'))}\n</code></pre>`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const heading = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line);
|
|
48
|
+
if (heading !== null) {
|
|
49
|
+
const raw = (heading[1] ?? '#').length + (options.headingOffset ?? 0);
|
|
50
|
+
const level = Math.min(6, Math.max(1, raw));
|
|
51
|
+
out.push(`<h${level}>${renderInline(heading[2] ?? '', options)}</h${level}>`);
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (/^\s{0,3}([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
56
|
+
out.push('<hr />');
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (/^\s{0,3}>/.test(line)) {
|
|
61
|
+
const body = [];
|
|
62
|
+
while (index < lines.length && /^\s{0,3}>/.test(lines[index] ?? '')) {
|
|
63
|
+
body.push((lines[index] ?? '').replace(/^\s{0,3}>\s?/, ''));
|
|
64
|
+
index += 1;
|
|
65
|
+
}
|
|
66
|
+
// Recursing means a quote can hold a list or a code block, which is what
|
|
67
|
+
// people actually paste into one.
|
|
68
|
+
out.push(`<blockquote>${renderMarkdown(body.join('\n'), options)}</blockquote>`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const table = tryTable(lines, index, options);
|
|
72
|
+
if (table !== null) {
|
|
73
|
+
out.push(table.html);
|
|
74
|
+
index = table.next;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const list = tryList(lines, index, options);
|
|
78
|
+
if (list !== null) {
|
|
79
|
+
out.push(list.html);
|
|
80
|
+
index = list.next;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const paragraph = [];
|
|
84
|
+
while (index < lines.length) {
|
|
85
|
+
const current = lines[index] ?? '';
|
|
86
|
+
if (current.trim() === '' || startsBlock(current))
|
|
87
|
+
break;
|
|
88
|
+
paragraph.push(current);
|
|
89
|
+
index += 1;
|
|
90
|
+
}
|
|
91
|
+
if (paragraph.length > 0) {
|
|
92
|
+
// A single newline inside a paragraph becomes a break. GitHub does this,
|
|
93
|
+
// and every resume in the wild is written assuming it.
|
|
94
|
+
out.push(`<p>${paragraph.map((text) => renderInline(text, options)).join('<br />')}</p>`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return out.join('\n');
|
|
98
|
+
}
|
|
99
|
+
function startsBlock(line) {
|
|
100
|
+
return (/^\s{0,3}(`{3,}|~{3,})/.test(line) ||
|
|
101
|
+
/^#{1,6}\s/.test(line) ||
|
|
102
|
+
/^\s{0,3}>/.test(line) ||
|
|
103
|
+
/^\s{0,3}([-*_])\s*(\1\s*){2,}$/.test(line) ||
|
|
104
|
+
/^\s{0,3}([-*+]|\d{1,9}[.)])\s+/.test(line));
|
|
105
|
+
}
|
|
106
|
+
/** GitHub-style tables. Resumes and compensation bands both use them. */
|
|
107
|
+
function tryTable(lines, start, options) {
|
|
108
|
+
const header = lines[start] ?? '';
|
|
109
|
+
const divider = lines[start + 1] ?? '';
|
|
110
|
+
if (!header.includes('|'))
|
|
111
|
+
return null;
|
|
112
|
+
if (!/^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(divider))
|
|
113
|
+
return null;
|
|
114
|
+
const aligns = splitRow(divider).map((cell) => {
|
|
115
|
+
const left = cell.startsWith(':');
|
|
116
|
+
const right = cell.endsWith(':');
|
|
117
|
+
if (left && right)
|
|
118
|
+
return ' style="text-align:center"';
|
|
119
|
+
if (right)
|
|
120
|
+
return ' style="text-align:right"';
|
|
121
|
+
return '';
|
|
122
|
+
});
|
|
123
|
+
const head = splitRow(header);
|
|
124
|
+
const rows = [];
|
|
125
|
+
let index = start + 2;
|
|
126
|
+
while (index < lines.length) {
|
|
127
|
+
const line = lines[index] ?? '';
|
|
128
|
+
if (line.trim() === '' || !line.includes('|'))
|
|
129
|
+
break;
|
|
130
|
+
rows.push(splitRow(line));
|
|
131
|
+
index += 1;
|
|
132
|
+
}
|
|
133
|
+
const th = head
|
|
134
|
+
.map((cell, column) => `<th${aligns[column] ?? ''}>${renderInline(cell, options)}</th>`)
|
|
135
|
+
.join('');
|
|
136
|
+
const tbody = rows
|
|
137
|
+
.map((row) => {
|
|
138
|
+
const cells = head
|
|
139
|
+
.map((_, column) => `<td${aligns[column] ?? ''}>${renderInline(row[column] ?? '', options)}</td>`)
|
|
140
|
+
.join('');
|
|
141
|
+
return `<tr>${cells}</tr>`;
|
|
142
|
+
})
|
|
143
|
+
.join('');
|
|
144
|
+
// Wrapped so a wide table scrolls inside itself instead of making the whole
|
|
145
|
+
// page scroll sideways on a phone.
|
|
146
|
+
return {
|
|
147
|
+
html: `<div class="table-wrap"><table><thead><tr>${th}</tr></thead><tbody>${tbody}</tbody></table></div>`,
|
|
148
|
+
next: index,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function splitRow(line) {
|
|
152
|
+
return line
|
|
153
|
+
.trim()
|
|
154
|
+
.replace(/^\|/, '')
|
|
155
|
+
.replace(/\|$/, '')
|
|
156
|
+
.split('|')
|
|
157
|
+
.map((cell) => cell.trim());
|
|
158
|
+
}
|
|
159
|
+
function tryList(lines, start, options) {
|
|
160
|
+
const first = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/.exec(lines[start] ?? '');
|
|
161
|
+
if (first === null)
|
|
162
|
+
return null;
|
|
163
|
+
const ordered = /\d/.test(first[2] ?? '');
|
|
164
|
+
const indent = (first[1] ?? '').length;
|
|
165
|
+
const items = [];
|
|
166
|
+
let index = start;
|
|
167
|
+
while (index < lines.length) {
|
|
168
|
+
const line = lines[index] ?? '';
|
|
169
|
+
const match = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/.exec(line);
|
|
170
|
+
if (match !== null && (match[1] ?? '').length <= indent) {
|
|
171
|
+
// A different marker type at the same level starts a new list rather
|
|
172
|
+
// than silently continuing this one.
|
|
173
|
+
if (/\d/.test(match[2] ?? '') !== ordered)
|
|
174
|
+
break;
|
|
175
|
+
items.push([match[3] ?? '']);
|
|
176
|
+
index += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const current = items[items.length - 1];
|
|
180
|
+
if (current === undefined)
|
|
181
|
+
break;
|
|
182
|
+
// A deeper marker, or a plain continuation line, belongs to the item above.
|
|
183
|
+
if (match !== null || /^\s+\S/.test(line)) {
|
|
184
|
+
const strip = Math.min(line.length - line.trimStart().length, indent + 2);
|
|
185
|
+
current.push(line.slice(strip));
|
|
186
|
+
index += 1;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
if (items.length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
const rendered = items
|
|
194
|
+
.map((item) => {
|
|
195
|
+
const body = item.join('\n');
|
|
196
|
+
// A one-line item stays inline, so a bullet list does not gain a
|
|
197
|
+
// paragraph's worth of vertical space per bullet.
|
|
198
|
+
const inner = item.length === 1
|
|
199
|
+
? renderInline(body, options)
|
|
200
|
+
: renderMarkdown(body, options).replace(/^<p>([\s\S]*)<\/p>$/, '$1');
|
|
201
|
+
return `<li>${inner}</li>`;
|
|
202
|
+
})
|
|
203
|
+
.join('');
|
|
204
|
+
const tag = ordered ? 'ol' : 'ul';
|
|
205
|
+
return { html: `<${tag}>${rendered}</${tag}>`, next: index };
|
|
206
|
+
}
|
|
207
|
+
/** A sentinel that cannot survive escapeHtml, so it cannot be forged in input. */
|
|
208
|
+
const MARK = String.fromCharCode(0);
|
|
209
|
+
/**
|
|
210
|
+
* Inline markup.
|
|
211
|
+
*
|
|
212
|
+
* Code spans are pulled out first and put back last, so a backticked
|
|
213
|
+
* `**not bold**` stays literal and an asterisk inside code cannot open
|
|
214
|
+
* emphasis that then runs off through the rest of the paragraph.
|
|
215
|
+
*/
|
|
216
|
+
export function renderInline(source, options = {}) {
|
|
217
|
+
const codes = [];
|
|
218
|
+
const rel = options.linkRel ?? DEFAULT_REL;
|
|
219
|
+
let text = source.replace(/(`+)([\s\S]*?)\1/g, (_whole, _ticks, body) => {
|
|
220
|
+
codes.push(`<code>${escapeHtml(body.trim())}</code>`);
|
|
221
|
+
return `${MARK}${codes.length - 1}${MARK}`;
|
|
222
|
+
});
|
|
223
|
+
text = escapeHtml(text);
|
|
224
|
+
// Images before links: the syntax differs only by the leading character.
|
|
225
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^&]*")?\)/g, (whole, alt, href) => {
|
|
226
|
+
const url = safeUrl(unescapeUrl(href));
|
|
227
|
+
if (url === null)
|
|
228
|
+
return whole;
|
|
229
|
+
if (options.noImages === true) {
|
|
230
|
+
return `<a href="${escapeHtml(url)}" rel="${rel}">${alt === '' ? escapeHtml(url) : alt}</a>`;
|
|
231
|
+
}
|
|
232
|
+
return `<img src="${escapeHtml(url)}" alt="${alt}" loading="lazy" />`;
|
|
233
|
+
});
|
|
234
|
+
text = text.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^&]*")?\)/g, (whole, label, href) => {
|
|
235
|
+
const url = safeUrl(unescapeUrl(href));
|
|
236
|
+
if (url === null)
|
|
237
|
+
return whole;
|
|
238
|
+
return `<a href="${escapeHtml(url)}" rel="${rel}">${label}</a>`;
|
|
239
|
+
});
|
|
240
|
+
// Bare URLs. Trailing punctuation is left outside the link, because a URL at
|
|
241
|
+
// the end of a sentence is the common case and the full stop is not part of
|
|
242
|
+
// it.
|
|
243
|
+
text = text.replace(/(^|[\s(])(https?:\/\/[^\s<>"']+)/g, (_whole, lead, href) => {
|
|
244
|
+
const trimmed = href.replace(/[.,;:!?)]+$/, '');
|
|
245
|
+
const tail = href.slice(trimmed.length);
|
|
246
|
+
const url = safeUrl(trimmed);
|
|
247
|
+
if (url === null)
|
|
248
|
+
return `${lead}${href}`;
|
|
249
|
+
return `${lead}<a href="${escapeHtml(url)}" rel="${rel}">${escapeHtml(trimmed)}</a>${tail}`;
|
|
250
|
+
});
|
|
251
|
+
text = text
|
|
252
|
+
.replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>')
|
|
253
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
254
|
+
.replace(/(^|[^\w*])\*([^*\n]+)\*(?!\w)/g, '$1<em>$2</em>')
|
|
255
|
+
.replace(/(^|[^\w_])__([^_\n]+)__(?!\w)/g, '$1<strong>$2</strong>')
|
|
256
|
+
.replace(/(^|[^\w_])_([^_\n]+)_(?!\w)/g, '$1<em>$2</em>')
|
|
257
|
+
.replace(/~~([^~\n]+)~~/g, '<del>$1</del>');
|
|
258
|
+
return text.replace(new RegExp(`${MARK}(\\d+)${MARK}`, 'g'), (_whole, id) => {
|
|
259
|
+
return codes[Number(id)] ?? '';
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/** Only `\(` and `\)` matter inside a link target. */
|
|
263
|
+
function unescapeUrl(href) {
|
|
264
|
+
return href.replace(/\\([()])/g, '$1');
|
|
265
|
+
}
|
|
266
|
+
/** Plain text, for meta descriptions, feeds, the TUI and search snippets. */
|
|
267
|
+
export function toPlainText(source, limit = 300) {
|
|
268
|
+
const text = source
|
|
269
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
270
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
271
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
272
|
+
.replace(/[#>*_~`|]/g, ' ')
|
|
273
|
+
.replace(/\s+/g, ' ')
|
|
274
|
+
.trim();
|
|
275
|
+
if (text.length <= limit)
|
|
276
|
+
return text;
|
|
277
|
+
return `${text.slice(0, limit - 1).replace(/\s+\S*$/, '')}...`;
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/markup/markdown.ts"],"names":[],"mappings":"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"}
|