fastbrowser_cli 1.0.35 → 1.0.39
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/README.md +26 -5
- package/dist/contribs/_shared/fastbrowser_helper.d.ts +13 -0
- package/dist/contribs/_shared/fastbrowser_helper.d.ts.map +1 -0
- package/dist/contribs/_shared/fastbrowser_helper.js +39 -0
- package/dist/contribs/_shared/fastbrowser_helper.js.map +1 -0
- package/dist/contribs/linkedin_cli/src/cli.d.ts +3 -0
- package/dist/contribs/linkedin_cli/src/cli.d.ts.map +1 -0
- package/dist/contribs/linkedin_cli/src/cli.js +299 -0
- package/dist/contribs/linkedin_cli/src/cli.js.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.d.ts +73 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.d.ts.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.js +866 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.js.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.d.ts +61 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.d.ts.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.js +885 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.js.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.d.ts +11 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.d.ts.map +1 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.js +145 -0
- package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.js.map +1 -0
- package/dist/contribs/twitter_cli/src/cli.d.ts +3 -0
- package/dist/contribs/twitter_cli/src/cli.d.ts.map +1 -0
- package/dist/contribs/twitter_cli/src/cli.js +273 -0
- package/dist/contribs/twitter_cli/src/cli.js.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.d.ts +28 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.d.ts.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.js +274 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.js.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.d.ts +43 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.d.ts.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.js +519 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.js.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.d.ts +11 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.d.ts.map +1 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.js +213 -0
- package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.js.map +1 -0
- package/dist/fastbrowser_cli/fastbrowser_cli.js +43 -0
- package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts +4 -0
- package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts.map +1 -1
- package/dist/fastbrowser_httpd/libs/tool-schemas.js +4 -0
- package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js +36 -2
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +2 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +12 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +1 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/response_formatter.js +27 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -1
- package/dist/shared/fastbrowser_helper.d.ts +13 -0
- package/dist/shared/fastbrowser_helper.d.ts.map +1 -0
- package/dist/shared/fastbrowser_helper.js +39 -0
- package/dist/shared/fastbrowser_helper.js.map +1 -0
- package/examples/linkedin_cli_TOREMOVE/README.md +7 -0
- package/examples/linkedin_cli_TOREMOVE/linkedin_dm.sh +40 -0
- package/examples/linkedin_cli_TOREMOVE/linkedin_dm.ts +326 -0
- package/examples/linkedin_cli_TOREMOVE/linkedin_dm_messages.ts +279 -0
- package/examples/linkedin_cli_TOREMOVE/linkedin_full_cycle.sh +5 -0
- package/examples/{linkedin_cli/linked_post.sh → linkedin_cli_TOREMOVE/linkedin_post.sh} +3 -0
- package/examples/linkedin_cli_TOREMOVE/message_thread.a11y.txt +252 -0
- package/examples/whatsapp/whatapp.a11y.txt +1521 -0
- package/examples/whatsapp/whatsapp.sh +10 -0
- package/listitem +7 -0
- package/package.json +7 -3
- package/skills/fastbrowser/SKILL.md +116 -29
- package/src/contribs/_shared/fastbrowser_helper.ts +49 -0
- package/src/contribs/linkedin_cli/README.md +80 -0
- package/src/contribs/linkedin_cli/data/linkedin_posts_jeromeetienne.a11y.txt +2364 -0
- package/src/contribs/linkedin_cli/data/linkedin_posts_jontwigge.a11y.txt +2740 -0
- package/src/contribs/linkedin_cli/data/linkedin_posts_julien_guezennec.a11y.txt +2073 -0
- package/src/contribs/linkedin_cli/data/linkedin_profile_jeromeetienne.a11y.txt +1863 -0
- package/src/contribs/linkedin_cli/data/linkedin_profile_jontwigge.a11y.txt +1738 -0
- package/src/contribs/linkedin_cli/data/linkedin_profile_julien_guezennec.a11y.txt +2182 -0
- package/src/contribs/linkedin_cli/src/cli.ts +345 -0
- package/src/contribs/linkedin_cli/src/libs/linkedin_profile_helper.ts +964 -0
- package/src/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.ts +982 -0
- package/src/contribs/linkedin_cli/src/libs/linkedin_thread_helper.ts +171 -0
- package/src/contribs/twitter_cli/README.md +79 -0
- package/src/contribs/twitter_cli/data/twitter_chat.a11y.txt +215 -0
- package/src/contribs/twitter_cli/data/twitter_home.a11y.txt +467 -0
- package/src/contribs/twitter_cli/data/twitter_profile.a11y.txt +418 -0
- package/src/contribs/twitter_cli/data/twitter_profile_jontwigge.a11y.txt +484 -0
- package/src/contribs/twitter_cli/data/twitter_profile_molokoloco.a11y.txt +483 -0
- package/src/contribs/twitter_cli/src/cli.ts +315 -0
- package/src/contribs/twitter_cli/src/libs/twitter_profile_helper.ts +328 -0
- package/src/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.ts +607 -0
- package/src/contribs/twitter_cli/src/libs/twitter_thread_helper.ts +240 -0
- package/src/fastbrowser_cli/fastbrowser_cli.ts +51 -0
- package/src/fastbrowser_httpd/libs/tool-schemas.ts +6 -0
- package/src/fastbrowser_mcp/fastbrowser_mcp.ts +46 -3
- package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +11 -0
- package/src/fastbrowser_mcp/libs/response_formatter.ts +29 -0
- package/src/shared/fastbrowser_helper.ts +49 -0
- package/tsconfig.json +1 -1
- package/examples/linkedin_cli/linked_dm.sh +0 -19
- package/examples/mcp_client_playwright.ts +0 -34
- /package/examples/{linkedin_cli → linkedin_cli_TOREMOVE}/linkedin.snapshot.txt +0 -0
- /package/examples/{twitter_cli → twitter_cli_TOREMOVE}/twitter_post.sh +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// npm imports
|
|
2
|
+
import { A11yQuery, A11yTree, AxNode } from 'a11y_parse';
|
|
3
|
+
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
//
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
9
|
+
|
|
10
|
+
export type TwitterProfile = {
|
|
11
|
+
handle: string;
|
|
12
|
+
displayName: string | null;
|
|
13
|
+
bio: string | null;
|
|
14
|
+
location: string | null;
|
|
15
|
+
website: string | null;
|
|
16
|
+
joinedDate: string | null;
|
|
17
|
+
postsCount: string | null;
|
|
18
|
+
followingCount: string | null;
|
|
19
|
+
followersCount: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class TwitterProfileHelper {
|
|
23
|
+
|
|
24
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
25
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
26
|
+
//
|
|
27
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
28
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
29
|
+
|
|
30
|
+
static parseProfile(rawSnapshot: string, handle: string): TwitterProfile {
|
|
31
|
+
const treeText = TwitterProfileHelper.extractAxTreeText(rawSnapshot);
|
|
32
|
+
const profile: TwitterProfile = {
|
|
33
|
+
handle,
|
|
34
|
+
displayName: null,
|
|
35
|
+
bio: null,
|
|
36
|
+
location: null,
|
|
37
|
+
website: null,
|
|
38
|
+
joinedDate: null,
|
|
39
|
+
postsCount: null,
|
|
40
|
+
followingCount: null,
|
|
41
|
+
followersCount: null,
|
|
42
|
+
};
|
|
43
|
+
if (treeText.length === 0) {
|
|
44
|
+
return profile;
|
|
45
|
+
}
|
|
46
|
+
const root = A11yTree.parse(treeText);
|
|
47
|
+
const escaped = handle.replace(/"/g, '\\"');
|
|
48
|
+
|
|
49
|
+
profile.displayName = TwitterProfileHelper.extractDisplayName(root, escaped);
|
|
50
|
+
profile.postsCount = TwitterProfileHelper.stripSuffix(
|
|
51
|
+
TwitterProfileHelper.extractValue(root, 'button[name="Back"] + generic > generic[value]'),
|
|
52
|
+
' posts',
|
|
53
|
+
);
|
|
54
|
+
profile.location = TwitterProfileHelper.extractLocation(root, escaped);
|
|
55
|
+
profile.website = TwitterProfileHelper.extractAttribute(
|
|
56
|
+
root,
|
|
57
|
+
'link[url^="https://t.co/"]',
|
|
58
|
+
'url',
|
|
59
|
+
);
|
|
60
|
+
profile.joinedDate = TwitterProfileHelper.stripPrefix(
|
|
61
|
+
TwitterProfileHelper.extractName(root, `link[url$="/${escaped}/about"]`),
|
|
62
|
+
'Joined ',
|
|
63
|
+
);
|
|
64
|
+
profile.followingCount = TwitterProfileHelper.stripSuffix(
|
|
65
|
+
TwitterProfileHelper.extractName(root, 'link[url$="/following"]'),
|
|
66
|
+
' Following',
|
|
67
|
+
);
|
|
68
|
+
profile.followersCount = TwitterProfileHelper.stripSuffix(
|
|
69
|
+
TwitterProfileHelper.extractName(root, 'link[url$="/verified_followers"]'),
|
|
70
|
+
' Followers',
|
|
71
|
+
);
|
|
72
|
+
profile.bio = TwitterProfileHelper.extractBio(root, escaped);
|
|
73
|
+
|
|
74
|
+
return profile;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
78
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
79
|
+
//
|
|
80
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
81
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
82
|
+
|
|
83
|
+
static async resolveWebsite(url: string): Promise<string> {
|
|
84
|
+
if (url.startsWith('https://t.co/') === false) {
|
|
85
|
+
return url;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
89
|
+
await response.body?.cancel();
|
|
90
|
+
return response.url;
|
|
91
|
+
} catch {
|
|
92
|
+
return url;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
97
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
98
|
+
//
|
|
99
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
100
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
101
|
+
|
|
102
|
+
static formatMarkdown(profile: TwitterProfile): string {
|
|
103
|
+
const headerName = profile.displayName !== null ? profile.displayName : profile.handle;
|
|
104
|
+
const lines: string[] = [];
|
|
105
|
+
lines.push(`# ${headerName} (@${profile.handle})`);
|
|
106
|
+
const fields: { label: string; value: string | null; }[] = [
|
|
107
|
+
{ label: 'Bio', value: profile.bio },
|
|
108
|
+
{ label: 'Location', value: profile.location },
|
|
109
|
+
{ label: 'Website', value: profile.website },
|
|
110
|
+
{ label: 'Joined', value: profile.joinedDate },
|
|
111
|
+
{ label: 'Posts', value: profile.postsCount },
|
|
112
|
+
{ label: 'Following', value: profile.followingCount },
|
|
113
|
+
{ label: 'Followers', value: profile.followersCount },
|
|
114
|
+
];
|
|
115
|
+
for (const field of fields) {
|
|
116
|
+
if (field.value === null) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
lines.push(`- ${field.label}: ${field.value}`);
|
|
120
|
+
}
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
125
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
126
|
+
//
|
|
127
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
128
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
129
|
+
|
|
130
|
+
private static extractAxTreeText(rawOutput: string): string {
|
|
131
|
+
const lines: string[] = [];
|
|
132
|
+
for (const line of rawOutput.split('\n')) {
|
|
133
|
+
if (/^\s*uid=/.test(line) === true) {
|
|
134
|
+
lines.push(line);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private static extractValue(root: AxNode, selector: string): string | null {
|
|
141
|
+
const node = A11yQuery.querySelector(root, selector);
|
|
142
|
+
if (node === undefined) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const value = node.attributes['value'];
|
|
146
|
+
if (value === undefined) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const trimmed = value.trim();
|
|
150
|
+
if (trimmed.length === 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return trimmed;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private static extractName(root: AxNode, selector: string): string | null {
|
|
157
|
+
const node = A11yQuery.querySelector(root, selector);
|
|
158
|
+
if (node === undefined) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (node.name === undefined) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const trimmed = node.name.trim();
|
|
165
|
+
if (trimmed.length === 0) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return trimmed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private static extractDisplayName(root: AxNode, escapedHandle: string): string | null {
|
|
172
|
+
const handleNode = A11yQuery.querySelector(root, `generic[value="@${escapedHandle}"]`);
|
|
173
|
+
if (handleNode === undefined) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const handleValue = `@${escapedHandle}`;
|
|
177
|
+
let cursor: AxNode | undefined = handleNode;
|
|
178
|
+
for (let level = 0; level < 5; level++) {
|
|
179
|
+
if (cursor === undefined) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
const prev = A11yTree.previousSibling(cursor);
|
|
183
|
+
if (prev !== undefined) {
|
|
184
|
+
const found = TwitterProfileHelper.findFirstValue(prev, handleValue);
|
|
185
|
+
if (found !== null) {
|
|
186
|
+
return found;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
cursor = cursor.parent;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private static findFirstValue(node: AxNode, exclude: string): string | null {
|
|
195
|
+
for (const descendant of A11yTree.walk(node)) {
|
|
196
|
+
const value = descendant.attributes['value'];
|
|
197
|
+
if (value !== undefined) {
|
|
198
|
+
const trimmed = value.trim();
|
|
199
|
+
if (trimmed.length > 0 && trimmed !== exclude) {
|
|
200
|
+
return trimmed;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (descendant.role === 'StaticText' && descendant.name !== undefined) {
|
|
204
|
+
const trimmed = descendant.name.trim();
|
|
205
|
+
if (trimmed.length > 0 && trimmed !== exclude) {
|
|
206
|
+
return trimmed;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private static extractLocation(root: AxNode, escapedHandle: string): string | null {
|
|
214
|
+
const aboutLink = A11yQuery.querySelector(root, `link[url$="/${escapedHandle}/about"]`);
|
|
215
|
+
if (aboutLink === undefined) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const parent = aboutLink.parent;
|
|
219
|
+
if (parent === undefined) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
for (const child of parent.children) {
|
|
223
|
+
if (child.role !== 'generic') {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const value = child.attributes['value'];
|
|
227
|
+
if (value === undefined) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const trimmed = value.trim();
|
|
231
|
+
if (trimmed.length === 0) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
return trimmed;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private static extractAttribute(root: AxNode, selector: string, attribute: string): string | null {
|
|
240
|
+
const node = A11yQuery.querySelector(root, selector);
|
|
241
|
+
if (node === undefined) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const value = node.attributes[attribute];
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const trimmed = value.trim();
|
|
249
|
+
if (trimmed.length === 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return trimmed;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private static stripPrefix(value: string | null, prefix: string): string | null {
|
|
256
|
+
if (value === null) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
if (value.startsWith(prefix) === true) {
|
|
260
|
+
return value.slice(prefix.length).trim();
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private static stripSuffix(value: string | null, suffix: string): string | null {
|
|
266
|
+
if (value === null) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
if (value.endsWith(suffix) === true) {
|
|
270
|
+
return value.slice(0, value.length - suffix.length).trim();
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
276
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
277
|
+
// Bio extraction — walks up from the about link and scans previous siblings
|
|
278
|
+
// for the closest text-rich subtree, since the bio has no stable selector.
|
|
279
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
280
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
281
|
+
|
|
282
|
+
private static extractBio(root: AxNode, escapedHandle: string): string | null {
|
|
283
|
+
const aboutLink = A11yQuery.querySelector(root, `link[url$="/${escapedHandle}/about"]`);
|
|
284
|
+
if (aboutLink === undefined) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
let cursor: AxNode | undefined = aboutLink.parent;
|
|
288
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
289
|
+
if (cursor === undefined) {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
let sibling = A11yTree.previousSibling(cursor);
|
|
293
|
+
while (sibling !== undefined) {
|
|
294
|
+
const text = TwitterProfileHelper.collectSubtreeText(sibling);
|
|
295
|
+
if (text !== null && text.length >= 8) {
|
|
296
|
+
return text;
|
|
297
|
+
}
|
|
298
|
+
sibling = A11yTree.previousSibling(sibling);
|
|
299
|
+
}
|
|
300
|
+
cursor = cursor.parent;
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private static collectSubtreeText(node: AxNode): string | null {
|
|
306
|
+
const parts: string[] = [];
|
|
307
|
+
for (const descendant of A11yTree.walk(node)) {
|
|
308
|
+
const value = descendant.attributes['value'];
|
|
309
|
+
if (value !== undefined) {
|
|
310
|
+
const trimmed = value.trim();
|
|
311
|
+
if (trimmed.length > 0) {
|
|
312
|
+
parts.push(trimmed);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (descendant.name !== undefined) {
|
|
317
|
+
const trimmed = descendant.name.trim();
|
|
318
|
+
if (trimmed.length > 0) {
|
|
319
|
+
parts.push(trimmed);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (parts.length === 0) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return parts.join(' ').trim();
|
|
327
|
+
}
|
|
328
|
+
}
|