gloomberb 0.8.1 → 0.9.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/README.md +106 -54
- package/package.json +14 -13
- package/patches/{@opentui%2Fcore@0.1.90.patch → @opentui%2Fcore@0.3.2.patch} +175 -90
- package/src/api-client/chat.ts +8 -0
- package/src/api-client/index.test.ts +30 -0
- package/src/api-client/index.ts +4 -0
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/types.ts +1 -0
- package/src/app/global-shortcuts.test.tsx +54 -4
- package/src/app/global-shortcuts.ts +11 -5
- package/src/capabilities/factories.ts +4 -0
- package/src/components/chart/comparison/data.test.ts +54 -0
- package/src/components/chart/comparison/data.ts +47 -24
- package/src/components/chart/core/projection.ts +51 -27
- package/src/components/command-bar/action-runtime.ts +1 -0
- package/src/components/command-bar/layout-items/current-layout.test.ts +115 -0
- package/src/components/command-bar/layout-items/current-layout.ts +33 -0
- package/src/components/command-bar/list/view.tsx +3 -1
- package/src/components/command-bar/pane-templates/items.ts +4 -1
- package/src/components/command-bar/panel/body.tsx +1 -0
- package/src/components/command-bar/routes/root/results.ts +0 -30
- package/src/components/command-bar/routes/root/runtime.ts +2 -20
- package/src/components/command-bar/routes/ticker-search/actions.ts +42 -4
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -1
- package/src/components/command-bar/routing/launch-request.ts +14 -7
- package/src/components/command-bar/routing/list-state.ts +1 -3
- package/src/components/command-bar/surface/index.test.tsx +76 -4
- package/src/components/command-bar/surface/index.tsx +0 -2
- package/src/components/command-bar/surface/pane.test.tsx +1 -0
- package/src/components/command-bar/surface/test-harness.tsx +1 -0
- package/src/components/command-bar/view-model.ts +3 -3
- package/src/components/data-table/stack-view.test.tsx +14 -11
- package/src/components/data-table/stack-view.tsx +1 -2
- package/src/components/data-table/view.test.tsx +24 -18
- package/src/components/data-table/view.tsx +313 -83
- package/src/components/feed-data-table/stack-view.tsx +6 -8
- package/src/components/layout/pane/footer/index.test.tsx +6 -0
- package/src/components/layout/pane/footer/index.tsx +14 -2
- package/src/components/layout/pane/title.ts +5 -1
- package/src/components/layout/shell/index.test.tsx +120 -2
- package/src/components/layout/shell/index.tsx +2 -0
- package/src/components/layout/shell/menu.ts +1 -7
- package/src/components/layout/shell/pane/actions.ts +8 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +12 -2
- package/src/components/layout/shell/shortcuts.ts +14 -6
- package/src/components/layout/shell/window-mode/index.ts +8 -3
- package/src/components/layout/window-edit/presentation.ts +4 -4
- package/src/components/onboarding/onboarding-steps.tsx +3 -2
- package/src/components/pane-settings-dialog/field-dialogs.tsx +0 -2
- package/src/components/pane-settings-dialog/tui.tsx +1 -1
- package/src/components/ticker/list-table-view.tsx +17 -25
- package/src/components/ui/choice-dialog.tsx +12 -2
- package/src/components/ui/multi-select/dialog.tsx +17 -5
- package/src/components/ui/remote-image.tsx +1 -14
- package/src/components/ui/shortcut-hint.tsx +14 -2
- package/src/components/ui/table-layout.test.ts +2 -2
- package/src/components/ui/table-layout.ts +8 -2
- package/src/components/ui/tabs.tsx +115 -86
- package/src/components/ui/ui.test.tsx +83 -4
- package/src/core/state/app/types.ts +12 -4
- package/src/market-data/coordinator/auxiliary.ts +29 -1
- package/src/market-data/coordinator/index.ts +17 -1
- package/src/market-data/hooks.tsx +17 -1
- package/src/market-data/selectors.ts +4 -0
- package/src/plugins/builtin/account-management/form-components.tsx +0 -4
- package/src/plugins/builtin/account-management/model.test.ts +54 -0
- package/src/plugins/builtin/account-management/model.ts +13 -3
- package/src/plugins/builtin/account-management/pane.tsx +5 -11
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +5 -3
- package/src/plugins/builtin/analytics/index.tsx +0 -3
- package/src/plugins/builtin/analytics/view.tsx +6 -5
- package/src/plugins/builtin/broker-manager/index.tsx +6 -6
- package/src/plugins/builtin/buildout/pane/index.tsx +6 -6
- package/src/plugins/builtin/changelog/index.tsx +40 -27
- package/src/plugins/builtin/changelog/model.test.ts +77 -0
- package/src/plugins/builtin/changelog/model.ts +98 -0
- package/src/plugins/builtin/chat/channel-labels.ts +53 -0
- package/src/plugins/builtin/chat/channels.test.ts +28 -0
- package/src/plugins/builtin/chat/channels.ts +36 -19
- package/src/plugins/builtin/chat/chat.test.tsx +264 -5
- package/src/plugins/builtin/chat/content/channel-navigation.ts +50 -11
- package/src/plugins/builtin/chat/content/composer-runtime.test.tsx +144 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +151 -20
- package/src/plugins/builtin/chat/content/composer.tsx +23 -2
- package/src/plugins/builtin/chat/content/index.tsx +66 -4
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +27 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +8 -0
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +41 -0
- package/src/plugins/builtin/chat/controller/messages.ts +18 -0
- package/src/plugins/builtin/chat/controller/notifications.ts +10 -4
- package/src/plugins/builtin/chat/controller/utils.ts +6 -3
- package/src/plugins/builtin/chat/controller/view.ts +1 -1
- package/src/plugins/builtin/chat/controller.test.ts +189 -0
- package/src/plugins/builtin/chat/edit-window.ts +24 -0
- package/src/plugins/builtin/chat/layout.ts +94 -31
- package/src/plugins/builtin/chat/message/action-chip.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +53 -20
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +16 -10
- package/src/plugins/builtin/chat/message/render-state.ts +5 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +49 -20
- package/src/plugins/builtin/chat/message/types.ts +2 -0
- package/src/plugins/builtin/chat/pane.tsx +35 -7
- package/src/plugins/builtin/chat/sidebar.test.tsx +72 -0
- package/src/plugins/builtin/chat/status-widget.tsx +29 -11
- package/src/plugins/builtin/chat/test-harness.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +7 -11
- package/src/plugins/builtin/congress-trades/pane.tsx +14 -22
- package/src/plugins/builtin/earnings/index.tsx +8 -7
- package/src/plugins/builtin/econ/index.tsx +6 -8
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/help/index.test.tsx +105 -30
- package/src/plugins/builtin/help/index.tsx +326 -164
- package/src/plugins/builtin/help/shortcut-model.ts +5 -6
- package/src/plugins/builtin/holders/pane.tsx +7 -6
- package/src/plugins/builtin/market-movers/index.tsx +6 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +7 -6
- package/src/plugins/builtin/options/view.tsx +16 -8
- package/src/plugins/builtin/portfolio-list/column-values.ts +304 -1
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +93 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +7 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +25 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +281 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +27 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +4 -2
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +10 -68
- package/src/plugins/builtin/research/analyst-pane.tsx +1 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +358 -7
- package/src/plugins/builtin/research/index.test.ts +30 -1
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +6 -8
- package/src/plugins/builtin/sec/filing-documents.ts +66 -0
- package/src/plugins/builtin/sec/index.tsx +132 -20
- package/src/plugins/builtin/sectors/index.tsx +13 -23
- package/src/plugins/builtin/shared/ticker-request.ts +1 -0
- package/src/plugins/builtin/substack/api/auth.ts +185 -0
- package/src/plugins/builtin/substack/api/cache.ts +78 -0
- package/src/plugins/builtin/substack/api/loaders.ts +146 -0
- package/src/plugins/builtin/substack/api/store.ts +186 -0
- package/src/plugins/builtin/substack/api/types.ts +40 -0
- package/src/plugins/builtin/substack/api.test.ts +298 -0
- package/src/plugins/builtin/substack/article-detail.tsx +359 -0
- package/src/plugins/builtin/substack/article-stack.tsx +129 -0
- package/src/plugins/builtin/substack/content.ts +478 -0
- package/src/plugins/builtin/substack/feed-tabs.tsx +41 -0
- package/src/plugins/builtin/substack/index.tsx +49 -0
- package/src/plugins/builtin/substack/login-view.tsx +126 -0
- package/src/plugins/builtin/substack/model.test.ts +155 -0
- package/src/plugins/builtin/substack/normalize.ts +343 -0
- package/src/plugins/builtin/substack/pane-footer.ts +71 -0
- package/src/plugins/builtin/substack/pane-state.ts +170 -0
- package/src/plugins/builtin/substack/pane.tsx +500 -0
- package/src/plugins/builtin/substack/table.ts +111 -0
- package/src/plugins/builtin/substack/types.ts +71 -0
- package/src/plugins/builtin/substack/utils.ts +131 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +26 -25
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +6 -5
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +10 -8
- package/src/plugins/builtin/ticker-detail/index.test.tsx +16 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/pane.tsx +13 -1
- package/src/plugins/builtin/world-indices/index.tsx +9 -6
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/pane-manager/layout-state.ts +11 -0
- package/src/plugins/pane-manager.ts +1 -1
- package/src/plugins/prediction-markets/pane.tsx +10 -9
- package/src/plugins/registry/index.ts +3 -0
- package/src/plugins/runtime/context.tsx +1 -0
- package/src/plugins/runtime/index.tsx +4 -0
- package/src/react/use-throttled-commit-value.ts +116 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +42 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +3 -2
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -2
- package/src/renderers/electrobun/view/host/input.tsx +23 -2
- package/src/renderers/electrobun/view/http-fetch.ts +10 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -0
- package/src/sources/provider-router/cache.ts +1 -0
- package/src/sources/provider-router/document-routes.ts +45 -0
- package/src/sources/provider-router/index.ts +4 -0
- package/src/sources/sec-edgar/content.ts +9 -3
- package/src/sources/sec-edgar.test.ts +108 -1
- package/src/sources/sec-edgar.ts +105 -1
- package/src/sources/yahoo-finance.ts +4 -0
- package/src/state/app/context/index.test.tsx +16 -0
- package/src/test-support/plugin-runtime.ts +1 -0
- package/src/tickers/search/index.ts +1 -0
- package/src/types/data-provider.ts +11 -0
- package/src/ui/host.tsx +1 -0
- package/src/version.ts +1 -1
|
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import {
|
|
3
3
|
SecEdgarClient,
|
|
4
4
|
extractFilingContent,
|
|
5
|
+
parseFilingDocuments,
|
|
5
6
|
parseRecentFilings,
|
|
6
7
|
parseTickerLookup,
|
|
7
8
|
} from "./sec-edgar";
|
|
@@ -65,6 +66,59 @@ describe("parseRecentFilings", () => {
|
|
|
65
66
|
});
|
|
66
67
|
});
|
|
67
68
|
|
|
69
|
+
describe("parseFilingDocuments", () => {
|
|
70
|
+
test("maps filing index document rows into primary and exhibit documents", () => {
|
|
71
|
+
const documents = parseFilingDocuments(`
|
|
72
|
+
<html>
|
|
73
|
+
<body>
|
|
74
|
+
<table class="tableFile" summary="Document Format Files">
|
|
75
|
+
<tr>
|
|
76
|
+
<th>Seq</th><th>Description</th><th>Document</th><th>Type</th><th>Size</th>
|
|
77
|
+
</tr>
|
|
78
|
+
<tr>
|
|
79
|
+
<td>1</td>
|
|
80
|
+
<td>Current report</td>
|
|
81
|
+
<td><a href="/ixviewer/doc/action?doc=/Archives/edgar/data/320193/000032019324000123/aapl-8k.htm">aapl-8k.htm</a></td>
|
|
82
|
+
<td>8-K</td>
|
|
83
|
+
<td>10000</td>
|
|
84
|
+
</tr>
|
|
85
|
+
<tr>
|
|
86
|
+
<td>2</td>
|
|
87
|
+
<td>Results of Operations and Financial Condition</td>
|
|
88
|
+
<td><a href="/Archives/edgar/data/320193/000032019324000123/aapl-ex991.htm">aapl-ex991.htm</a></td>
|
|
89
|
+
<td>EX-99.1</td>
|
|
90
|
+
<td>42000</td>
|
|
91
|
+
</tr>
|
|
92
|
+
</table>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
`, {
|
|
96
|
+
accessionNumber: "0000320193-24-000123",
|
|
97
|
+
form: "8-K",
|
|
98
|
+
filingDate: new Date("2024-08-01T00:00:00Z"),
|
|
99
|
+
cik: "0000320193",
|
|
100
|
+
filingUrl: "https://www.sec.gov/Archives/edgar/data/320193/0000320193-24-000123-index.htm",
|
|
101
|
+
primaryDocument: "aapl-8k.htm",
|
|
102
|
+
primaryDocumentUrl: "https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/aapl-8k.htm",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(documents).toMatchObject([
|
|
106
|
+
{
|
|
107
|
+
type: "8-K",
|
|
108
|
+
document: "aapl-8k.htm",
|
|
109
|
+
isPrimary: true,
|
|
110
|
+
url: "https://www.sec.gov/Archives/edgar/data/320193/000032019324000123/aapl-8k.htm",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: "EX-99.1",
|
|
114
|
+
description: "Results of Operations and Financial Condition",
|
|
115
|
+
document: "aapl-ex991.htm",
|
|
116
|
+
isPrimary: false,
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
68
122
|
describe("SecEdgarClient", () => {
|
|
69
123
|
test("extracts readable text from filing html", () => {
|
|
70
124
|
const content = extractFilingContent(`
|
|
@@ -81,6 +135,30 @@ describe("SecEdgarClient", () => {
|
|
|
81
135
|
expect(content).toContain("Revenue increased 12% year over year.");
|
|
82
136
|
});
|
|
83
137
|
|
|
138
|
+
test("trims SEC document wrapper metadata before exhibit content", () => {
|
|
139
|
+
const content = extractFilingContent(`
|
|
140
|
+
<SEC-DOCUMENT>
|
|
141
|
+
<DOCUMENT>
|
|
142
|
+
<TYPE>EX-99.1
|
|
143
|
+
<SEQUENCE>2
|
|
144
|
+
<FILENAME>q42026er-991.htm
|
|
145
|
+
<DESCRIPTION>EX-99.1
|
|
146
|
+
<TEXT>
|
|
147
|
+
<html><body>
|
|
148
|
+
<p>Exhibit 99.1</p>
|
|
149
|
+
<h1>e.l.f. Beauty Announces Fourth Quarter Fiscal 2026 Results</h1>
|
|
150
|
+
<p>Delivered fiscal 2026 net sales growth of 25% year over year.</p>
|
|
151
|
+
</body></html>
|
|
152
|
+
</TEXT>
|
|
153
|
+
</DOCUMENT>
|
|
154
|
+
</SEC-DOCUMENT>
|
|
155
|
+
`, "text/html", { form: "EX-99.1" });
|
|
156
|
+
|
|
157
|
+
expect(content?.startsWith("Exhibit 99.1")).toBe(true);
|
|
158
|
+
expect(content).toContain("Fourth Quarter Fiscal 2026 Results");
|
|
159
|
+
expect(content).not.toContain("q42026er-991.htm");
|
|
160
|
+
});
|
|
161
|
+
|
|
84
162
|
test("drops hidden inline xbrl boilerplate from filing html", () => {
|
|
85
163
|
const content = extractFilingContent(`
|
|
86
164
|
<?xml version="1.0"?>
|
|
@@ -119,7 +197,7 @@ describe("SecEdgarClient", () => {
|
|
|
119
197
|
sourceUrl: "https://www.sec.gov/Archives/edgar/data/2488/example.pdf",
|
|
120
198
|
});
|
|
121
199
|
|
|
122
|
-
expect(content).toContain("
|
|
200
|
+
expect(content).toContain("document is a PDF");
|
|
123
201
|
expect(content).not.toContain("%PDF-1.6");
|
|
124
202
|
});
|
|
125
203
|
|
|
@@ -283,4 +361,33 @@ describe("SecEdgarClient", () => {
|
|
|
283
361
|
expect(content).toContain("Proxy Statement");
|
|
284
362
|
expect(content).toContain("Annual meeting details.");
|
|
285
363
|
});
|
|
364
|
+
|
|
365
|
+
test("loads filing documents from the filing index", async () => {
|
|
366
|
+
globalThis.fetch = (async () => new Response(`
|
|
367
|
+
<html>
|
|
368
|
+
<body>
|
|
369
|
+
<table class="tableFile">
|
|
370
|
+
<tr><td>1</td><td>Current report</td><td><a href="/Archives/edgar/data/320193/000032019324000123/aapl-8k.htm">aapl-8k.htm</a></td><td>8-K</td><td>10000</td></tr>
|
|
371
|
+
<tr><td>2</td><td>Investor presentation</td><td><a href="/Archives/edgar/data/320193/000032019324000123/aapl-ex992.pdf">aapl-ex992.pdf</a></td><td>EX-99.2</td><td>90000</td></tr>
|
|
372
|
+
</table>
|
|
373
|
+
</body>
|
|
374
|
+
</html>
|
|
375
|
+
`, {
|
|
376
|
+
status: 200,
|
|
377
|
+
headers: { "content-type": "text/html" },
|
|
378
|
+
})) as unknown as typeof fetch;
|
|
379
|
+
|
|
380
|
+
const client = new SecEdgarClient();
|
|
381
|
+
const documents = await client.getFilingDocuments({
|
|
382
|
+
accessionNumber: "0000320193-24-000123",
|
|
383
|
+
form: "8-K",
|
|
384
|
+
filingDate: new Date("2024-08-01T00:00:00Z"),
|
|
385
|
+
cik: "0000320193",
|
|
386
|
+
filingUrl: "https://www.sec.gov/Archives/edgar/data/320193/0000320193-24-000123-index.htm",
|
|
387
|
+
primaryDocument: "aapl-8k.htm",
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(documents.map((document) => document.type)).toEqual(["8-K", "EX-99.2"]);
|
|
391
|
+
expect(documents[1]?.description).toBe("Investor presentation");
|
|
392
|
+
});
|
|
286
393
|
});
|
package/src/sources/sec-edgar.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SecFilingItem } from "../types/data-provider";
|
|
1
|
+
import type { SecFilingDocument, SecFilingItem } from "../types/data-provider";
|
|
2
2
|
import { truncateWithEllipsis } from "../utils/text-wrap";
|
|
3
3
|
import {
|
|
4
4
|
PDF_FALLBACK_MESSAGE,
|
|
@@ -84,6 +84,56 @@ function stripAccessionDashes(accessionNumber: string): string {
|
|
|
84
84
|
return accessionNumber.replace(/-/g, "");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function documentNameFromUrl(url: string): string {
|
|
88
|
+
try {
|
|
89
|
+
return decodeURIComponent(new URL(url).pathname.split("/").pop() ?? "");
|
|
90
|
+
} catch {
|
|
91
|
+
return url.split("/").pop() ?? url;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeDocumentName(value: string | undefined): string {
|
|
96
|
+
return (value ?? "").trim().toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function decodeSecHtmlEntities(text: string): string {
|
|
100
|
+
return text
|
|
101
|
+
.replace(/ /gi, " ")
|
|
102
|
+
.replace(/&/gi, "&")
|
|
103
|
+
.replace(/</gi, "<")
|
|
104
|
+
.replace(/>/gi, ">")
|
|
105
|
+
.replace(/"/gi, "\"")
|
|
106
|
+
.replace(/'|'/gi, "'")
|
|
107
|
+
.replace(/&#(\d+);/g, (_match, digits: string) => String.fromCodePoint(Number(digits)))
|
|
108
|
+
.replace(/&#x([0-9a-f]+);/gi, (_match, hex: string) => String.fromCodePoint(parseInt(hex, 16)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function cleanSecTableText(value: string): string {
|
|
112
|
+
return decodeSecHtmlEntities(
|
|
113
|
+
value
|
|
114
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
115
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
116
|
+
.replace(/<[^>]+>/g, " ")
|
|
117
|
+
.replace(/\s+/g, " ")
|
|
118
|
+
.trim(),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function resolveSecArchiveUrl(href: string, filingUrl: string): string | null {
|
|
123
|
+
const trimmed = href.trim();
|
|
124
|
+
if (!trimmed) return null;
|
|
125
|
+
try {
|
|
126
|
+
const url = new URL(trimmed, filingUrl);
|
|
127
|
+
const ixviewerDocument = url.searchParams.get("doc");
|
|
128
|
+
if (ixviewerDocument?.startsWith("/Archives/")) {
|
|
129
|
+
return new URL(ixviewerDocument, "https://www.sec.gov").toString();
|
|
130
|
+
}
|
|
131
|
+
return url.toString();
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
87
137
|
function isHtmlResponse(body: string): boolean {
|
|
88
138
|
return /^\s*<!DOCTYPE html/i.test(body) || /^\s*<html/i.test(body);
|
|
89
139
|
}
|
|
@@ -201,6 +251,52 @@ export function parseRecentFilings(payload: unknown, count = 15): SecFilingItem[
|
|
|
201
251
|
return results;
|
|
202
252
|
}
|
|
203
253
|
|
|
254
|
+
export function parseFilingDocuments(indexHtml: string, filing: SecFilingItem): SecFilingDocument[] {
|
|
255
|
+
const documents: SecFilingDocument[] = [];
|
|
256
|
+
const primaryUrl = normalizeDocumentName(filing.primaryDocumentUrl);
|
|
257
|
+
const primaryDocument = normalizeDocumentName(filing.primaryDocument);
|
|
258
|
+
const rows = [...indexHtml.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)];
|
|
259
|
+
|
|
260
|
+
for (const rowMatch of rows) {
|
|
261
|
+
const rowHtml = rowMatch[1] ?? "";
|
|
262
|
+
const cells = [...rowHtml.matchAll(/<td\b[^>]*>([\s\S]*?)<\/td>/gi)].map((match) => match[1] ?? "");
|
|
263
|
+
if (cells.length < 3) continue;
|
|
264
|
+
|
|
265
|
+
const hrefMatch = rowHtml.match(/<a\b[^>]*href="([^"]+)"[^>]*>/i);
|
|
266
|
+
if (!hrefMatch?.[1]) continue;
|
|
267
|
+
const url = resolveSecArchiveUrl(hrefMatch[1], filing.filingUrl);
|
|
268
|
+
if (!url || !url.includes("/Archives/edgar/data/")) continue;
|
|
269
|
+
|
|
270
|
+
const sequence = cleanSecTableText(cells[0] ?? "") || undefined;
|
|
271
|
+
const description = cleanSecTableText(cells[1] ?? "") || undefined;
|
|
272
|
+
const linkedDocument = cleanSecTableText(cells[2] ?? "") || documentNameFromUrl(url);
|
|
273
|
+
const type = cleanSecTableText(cells[3] ?? "") || linkedDocument;
|
|
274
|
+
const size = cleanSecTableText(cells[4] ?? "") || undefined;
|
|
275
|
+
const document = linkedDocument || documentNameFromUrl(url);
|
|
276
|
+
const normalizedDocument = normalizeDocumentName(document);
|
|
277
|
+
const normalizedUrl = normalizeDocumentName(url);
|
|
278
|
+
|
|
279
|
+
documents.push({
|
|
280
|
+
sequence,
|
|
281
|
+
type,
|
|
282
|
+
description,
|
|
283
|
+
document,
|
|
284
|
+
url,
|
|
285
|
+
size,
|
|
286
|
+
isPrimary: (
|
|
287
|
+
(primaryDocument.length > 0 && normalizedDocument === primaryDocument)
|
|
288
|
+
|| (primaryUrl.length > 0 && normalizedUrl === primaryUrl)
|
|
289
|
+
|| (
|
|
290
|
+
documents.length === 0
|
|
291
|
+
&& normalizeDocumentName(type) === normalizeDocumentName(filing.form)
|
|
292
|
+
)
|
|
293
|
+
),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return documents;
|
|
298
|
+
}
|
|
299
|
+
|
|
204
300
|
export class SecEdgarClient {
|
|
205
301
|
private lookupPromise: Promise<Map<string, LookupEntry>> | null = null;
|
|
206
302
|
|
|
@@ -311,6 +407,11 @@ export class SecEdgarClient {
|
|
|
311
407
|
return parseRecentFilings(payload, count);
|
|
312
408
|
}
|
|
313
409
|
|
|
410
|
+
async getFilingDocuments(filing: SecFilingItem): Promise<SecFilingDocument[]> {
|
|
411
|
+
const { body } = await this.fetchText(filing.filingUrl);
|
|
412
|
+
return parseFilingDocuments(body, filing);
|
|
413
|
+
}
|
|
414
|
+
|
|
314
415
|
async getFilingContent(filing: Pick<SecFilingItem, "primaryDocumentUrl" | "filingUrl" | "form">): Promise<string | null> {
|
|
315
416
|
let targetUrl = filing.primaryDocumentUrl || filing.filingUrl;
|
|
316
417
|
if (!targetUrl) return null;
|
|
@@ -328,6 +429,9 @@ export class SecEdgarClient {
|
|
|
328
429
|
}
|
|
329
430
|
|
|
330
431
|
if (filing.primaryDocumentUrl && isPdfDocument("", "", filing.primaryDocumentUrl)) {
|
|
432
|
+
if (filing.filingUrl === filing.primaryDocumentUrl) {
|
|
433
|
+
return PDF_FALLBACK_MESSAGE;
|
|
434
|
+
}
|
|
331
435
|
const alternativeUrl = filing.filingUrl
|
|
332
436
|
? await this.findAlternativeHtmlDocumentUrl(filing.filingUrl, filing.primaryDocumentUrl)
|
|
333
437
|
: null;
|
|
@@ -226,6 +226,10 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
226
226
|
return this.secClient.getRecentFilings(ticker, count);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
async getSecFilingDocuments(filing: SecFilingItem) {
|
|
230
|
+
return this.secClient.getFilingDocuments(filing);
|
|
231
|
+
}
|
|
232
|
+
|
|
229
233
|
async getSecFilingContent(filing: SecFilingItem): Promise<string | null> {
|
|
230
234
|
return this.secClient.getFilingContent(filing);
|
|
231
235
|
}
|
|
@@ -46,6 +46,22 @@ describe("appReducer command bar state", () => {
|
|
|
46
46
|
expect(closed.commandBarLaunchRequest).toBeNull();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
test("stores ticker search command bar launch requests", () => {
|
|
50
|
+
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
51
|
+
const opened = appReducer(initial, {
|
|
52
|
+
type: "SET_COMMAND_BAR",
|
|
53
|
+
open: true,
|
|
54
|
+
launch: { kind: "ticker-search", query: "" },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(opened.commandBarOpen).toBe(true);
|
|
58
|
+
expect(opened.commandBarLaunchRequest).toEqual({
|
|
59
|
+
kind: "ticker-search",
|
|
60
|
+
query: "",
|
|
61
|
+
sequence: 1,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
49
65
|
test("toggle opens blank and clears on close", () => {
|
|
50
66
|
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
51
67
|
const opened = appReducer(initial, { type: "TOGGLE_COMMAND_BAR" });
|
|
@@ -34,6 +34,16 @@ export interface SecFilingItem {
|
|
|
34
34
|
primaryDocumentUrl?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export interface SecFilingDocument {
|
|
38
|
+
sequence?: string;
|
|
39
|
+
type: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
document: string;
|
|
42
|
+
url: string;
|
|
43
|
+
size?: string;
|
|
44
|
+
isPrimary: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
export interface EarningsEvent {
|
|
38
48
|
symbol: string;
|
|
39
49
|
name: string;
|
|
@@ -127,6 +137,7 @@ export interface AssetDataProvider {
|
|
|
127
137
|
getAnalystResearch?(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<AnalystResearchData>;
|
|
128
138
|
getCorporateActions?(ticker: string, exchange?: string, context?: MarketDataRequestContext): Promise<CorporateActionsData>;
|
|
129
139
|
getEarningsCalendar?(symbols: string[], context?: MarketDataRequestContext): Promise<EarningsEvent[]>;
|
|
140
|
+
getSecFilingDocuments?(filing: SecFilingItem): Promise<SecFilingDocument[]>;
|
|
130
141
|
getSecFilingContent?(filing: SecFilingItem): Promise<string | null>;
|
|
131
142
|
/** Fetch article summary/description by URL (lazy-loaded on selection) */
|
|
132
143
|
getArticleSummary(url: string): Promise<string | null>;
|
package/src/ui/host.tsx
CHANGED
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.9.0";
|