@bitvea/feedback-toolbar 0.0.1
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 +262 -0
- package/dist/FeedbackPanel.d.ts +117 -0
- package/dist/FeedbackPanel.d.ts.map +1 -0
- package/dist/FeedbackPanel.js +622 -0
- package/dist/FeedbackPanel.js.map +1 -0
- package/dist/FeedbackToolbar.d.ts +54 -0
- package/dist/FeedbackToolbar.d.ts.map +1 -0
- package/dist/FeedbackToolbar.js +1149 -0
- package/dist/FeedbackToolbar.js.map +1 -0
- package/dist/InboxSheet.d.ts +80 -0
- package/dist/InboxSheet.d.ts.map +1 -0
- package/dist/InboxSheet.js +580 -0
- package/dist/InboxSheet.js.map +1 -0
- package/dist/PinLayer.d.ts +59 -0
- package/dist/PinLayer.d.ts.map +1 -0
- package/dist/PinLayer.js +1702 -0
- package/dist/PinLayer.js.map +1 -0
- package/dist/RichText.d.ts +6 -0
- package/dist/RichText.d.ts.map +1 -0
- package/dist/RichText.js +135 -0
- package/dist/RichText.js.map +1 -0
- package/dist/browser/entry.d.ts +3 -0
- package/dist/browser/entry.d.ts.map +1 -0
- package/dist/browser/entry.js +13 -0
- package/dist/browser/entry.js.map +1 -0
- package/dist/browser/mount.d.ts +37 -0
- package/dist/browser/mount.d.ts.map +1 -0
- package/dist/browser/mount.js +150 -0
- package/dist/browser/mount.js.map +1 -0
- package/dist/browser/toolbar.js +38 -0
- package/dist/browser/toolbar.js.map +7 -0
- package/dist/capture.d.ts +58 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +98 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +196 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/fs.d.ts +16 -0
- package/dist/cli/fs.d.ts.map +1 -0
- package/dist/cli/fs.js +33 -0
- package/dist/cli/fs.js.map +1 -0
- package/dist/cli/init.d.ts +75 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +266 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/register.d.ts +55 -0
- package/dist/cli/register.d.ts.map +1 -0
- package/dist/cli/register.js +84 -0
- package/dist/cli/register.js.map +1 -0
- package/dist/cli/registration.d.ts +37 -0
- package/dist/cli/registration.d.ts.map +1 -0
- package/dist/cli/registration.js +69 -0
- package/dist/cli/registration.js.map +1 -0
- package/dist/cli/strategies.d.ts +98 -0
- package/dist/cli/strategies.d.ts.map +1 -0
- package/dist/cli/strategies.js +198 -0
- package/dist/cli/strategies.js.map +1 -0
- package/dist/client.d.ts +59 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +113 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/deepLink.d.ts +86 -0
- package/dist/deepLink.d.ts.map +1 -0
- package/dist/deepLink.js +185 -0
- package/dist/deepLink.js.map +1 -0
- package/dist/fontFace.d.ts +70 -0
- package/dist/fontFace.d.ts.map +1 -0
- package/dist/fontFace.js +88 -0
- package/dist/fontFace.js.map +1 -0
- package/dist/gating.d.ts +32 -0
- package/dist/gating.d.ts.map +1 -0
- package/dist/gating.js +49 -0
- package/dist/gating.js.map +1 -0
- package/dist/icons.d.ts +68 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +97 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +59 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +130 -0
- package/dist/markdown.js.map +1 -0
- package/dist/motion.d.ts +178 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +302 -0
- package/dist/motion.js.map +1 -0
- package/dist/preferences.d.ts +71 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +195 -0
- package/dist/preferences.js.map +1 -0
- package/dist/rasterise.d.ts +45 -0
- package/dist/rasterise.d.ts.map +1 -0
- package/dist/rasterise.js +490 -0
- package/dist/rasterise.js.map +1 -0
- package/dist/selector.d.ts +19 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/selector.js +142 -0
- package/dist/selector.js.map +1 -0
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +36 -0
- package/dist/session.js.map +1 -0
- package/dist/shortcut.d.ts +88 -0
- package/dist/shortcut.d.ts.map +1 -0
- package/dist/shortcut.js +149 -0
- package/dist/shortcut.js.map +1 -0
- package/dist/tokens.d.ts +709 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +749 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vendor/types/account.d.ts +26 -0
- package/dist/vendor/types/account.d.ts.map +1 -0
- package/dist/vendor/types/account.js +23 -0
- package/dist/vendor/types/account.js.map +1 -0
- package/dist/vendor/types/apiKey.d.ts +38 -0
- package/dist/vendor/types/apiKey.d.ts.map +1 -0
- package/dist/vendor/types/apiKey.js +36 -0
- package/dist/vendor/types/apiKey.js.map +1 -0
- package/dist/vendor/types/auth.d.ts +10 -0
- package/dist/vendor/types/auth.d.ts.map +1 -0
- package/dist/vendor/types/auth.js +15 -0
- package/dist/vendor/types/auth.js.map +1 -0
- package/dist/vendor/types/entity.d.ts +74 -0
- package/dist/vendor/types/entity.d.ts.map +1 -0
- package/dist/vendor/types/entity.js +111 -0
- package/dist/vendor/types/entity.js.map +1 -0
- package/dist/vendor/types/feedback.d.ts +285 -0
- package/dist/vendor/types/feedback.d.ts.map +1 -0
- package/dist/vendor/types/feedback.js +286 -0
- package/dist/vendor/types/feedback.js.map +1 -0
- package/dist/vendor/types/index.d.ts +14 -0
- package/dist/vendor/types/index.d.ts.map +1 -0
- package/dist/vendor/types/index.js +16 -0
- package/dist/vendor/types/index.js.map +1 -0
- package/dist/vendor/types/invite.d.ts +42 -0
- package/dist/vendor/types/invite.d.ts.map +1 -0
- package/dist/vendor/types/invite.js +30 -0
- package/dist/vendor/types/invite.js.map +1 -0
- package/dist/vendor/types/mcpToken.d.ts +38 -0
- package/dist/vendor/types/mcpToken.d.ts.map +1 -0
- package/dist/vendor/types/mcpToken.js +39 -0
- package/dist/vendor/types/mcpToken.js.map +1 -0
- package/dist/vendor/types/organization.d.ts +13 -0
- package/dist/vendor/types/organization.d.ts.map +1 -0
- package/dist/vendor/types/organization.js +12 -0
- package/dist/vendor/types/organization.js.map +1 -0
- package/dist/vendor/types/project.d.ts +73 -0
- package/dist/vendor/types/project.d.ts.map +1 -0
- package/dist/vendor/types/project.js +117 -0
- package/dist/vendor/types/project.js.map +1 -0
- package/dist/vendor/types/role.d.ts +12 -0
- package/dist/vendor/types/role.d.ts.map +1 -0
- package/dist/vendor/types/role.js +20 -0
- package/dist/vendor/types/role.js.map +1 -0
- package/dist/vendor/types/roleDefinition.d.ts +61 -0
- package/dist/vendor/types/roleDefinition.d.ts.map +1 -0
- package/dist/vendor/types/roleDefinition.js +45 -0
- package/dist/vendor/types/roleDefinition.js.map +1 -0
- package/dist/vendor/types/team.d.ts +25 -0
- package/dist/vendor/types/team.d.ts.map +1 -0
- package/dist/vendor/types/team.js +17 -0
- package/dist/vendor/types/team.js.map +1 -0
- package/dist/vendor/types/util.d.ts +39 -0
- package/dist/vendor/types/util.d.ts.map +1 -0
- package/dist/vendor/types/util.js +31 -0
- package/dist/vendor/types/util.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// How the provider gets into a host app, one strategy per install path.
|
|
2
|
+
//
|
|
3
|
+
// The spec asks for this explicitly: another framework, or another way of
|
|
4
|
+
// mounting into Next.js, should be a new strategy rather than a rewrite of the
|
|
5
|
+
// injector. So the orchestration in `init.ts` knows only this interface, and
|
|
6
|
+
// the one strategy that exists here knows only App Router layouts.
|
|
7
|
+
import { joinPath } from "./fs.js";
|
|
8
|
+
export const PACKAGE_NAME = "@bitvea/feedback-toolbar";
|
|
9
|
+
export const COMPONENT_NAME = "FeedbackToolbar";
|
|
10
|
+
/** The file the script-tag route is served as, relative to the backend URL. */
|
|
11
|
+
export const SCRIPT_BUNDLE_PATH = "/toolbar.js";
|
|
12
|
+
/**
|
|
13
|
+
* The environment a script-tag install declares, when the developer has not
|
|
14
|
+
* said otherwise.
|
|
15
|
+
*
|
|
16
|
+
* The gate treats "absent" as off, and a static HTML file cannot know which
|
|
17
|
+
* deployment served it - so a tag with no `data-environment` mounts nothing,
|
|
18
|
+
* ever. That is the correct default for a value we could only guess, and the
|
|
19
|
+
* wrong default for an installer whose whole job is to leave a working
|
|
20
|
+
* install behind: #17 asks for a toolbar that mounts on a plain page with no
|
|
21
|
+
* build step, and a tag the developer has to hand-edit is not that.
|
|
22
|
+
*
|
|
23
|
+
* So `init` writes an environment, and the value is a review environment
|
|
24
|
+
* because installing a review tool on a page is what the command means. The
|
|
25
|
+
* result is printed as a change and called out as a note, and `--environment`
|
|
26
|
+
* overrides it - including with `production`, which is how somebody turns the
|
|
27
|
+
* tag off without removing it.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_SCRIPT_ENVIRONMENT = "preview";
|
|
30
|
+
/** Where an App Router root layout lives, most conventional first. */
|
|
31
|
+
const APP_ROUTER_LAYOUTS = [
|
|
32
|
+
"app/layout.tsx",
|
|
33
|
+
"src/app/layout.tsx",
|
|
34
|
+
"app/layout.jsx",
|
|
35
|
+
"src/app/layout.jsx",
|
|
36
|
+
];
|
|
37
|
+
export const nextAppRouterStrategy = {
|
|
38
|
+
id: "next-app-router",
|
|
39
|
+
label: "Next.js App Router",
|
|
40
|
+
delivery: "npm-package",
|
|
41
|
+
detect(fs, root) {
|
|
42
|
+
for (const candidate of APP_ROUTER_LAYOUTS) {
|
|
43
|
+
const path = joinPath(root, candidate);
|
|
44
|
+
if (fs.exists(path)) {
|
|
45
|
+
return { path, description: `root layout at ${candidate}` };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
},
|
|
50
|
+
isInstalled(source) {
|
|
51
|
+
// Both halves, because a half-installed file is what a failed earlier run
|
|
52
|
+
// would leave and re-running should finish it rather than skip it.
|
|
53
|
+
return (source.includes(PACKAGE_NAME) && source.includes(`<${COMPONENT_NAME}`));
|
|
54
|
+
},
|
|
55
|
+
inject(source, context) {
|
|
56
|
+
let out = source;
|
|
57
|
+
if (!out.includes(PACKAGE_NAME)) {
|
|
58
|
+
out = addImport(out);
|
|
59
|
+
}
|
|
60
|
+
if (!out.includes(`<${COMPONENT_NAME}`)) {
|
|
61
|
+
// The API URL goes in the MARKUP, not only into an env file, and that is
|
|
62
|
+
// the difference between an install that works after a deploy and one
|
|
63
|
+
// that silently does nothing.
|
|
64
|
+
//
|
|
65
|
+
// `init` writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local`,
|
|
66
|
+
// which create-next-app gitignores - so on the host's next deploy the
|
|
67
|
+
// variable does not exist, `resolveConfig` leaves `enabled` false, and
|
|
68
|
+
// the deployed toolbar renders nothing. The developer sees no toolbar and
|
|
69
|
+
// no error, and nothing in the install told them to add the variable to
|
|
70
|
+
// their hosting provider by hand.
|
|
71
|
+
//
|
|
72
|
+
// The value is not a secret - it is a `NEXT_PUBLIC_` URL, and the
|
|
73
|
+
// script-tag route already writes the same value into a page - so
|
|
74
|
+
// committing it is exactly right. `resolveConfig` lets a prop beat the
|
|
75
|
+
// environment, which is what makes this deterministic; a developer who
|
|
76
|
+
// wants a different backend per environment deletes the prop and sets
|
|
77
|
+
// the variable in their hosting provider instead.
|
|
78
|
+
out = insertBeforeClosingBody(out, `<${COMPONENT_NAME} apiUrl="${context.apiUrl}" />`);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
/** Where a plain HTML entry point lives, most conventional first. */
|
|
84
|
+
const HTML_ENTRY_FILES = ["index.html", "public/index.html", "src/index.html"];
|
|
85
|
+
/**
|
|
86
|
+
* The `<script src>` install, for an app with no build step to add a
|
|
87
|
+
* dependency to. Second in the list on purpose: an App Router app usually
|
|
88
|
+
* also has an index.html somewhere, and the npm install is the better one.
|
|
89
|
+
*
|
|
90
|
+
* Note what the injected tag does NOT carry: a deploy key. There is no
|
|
91
|
+
* supported way to put one in a page, and the bundle refuses to mount when it
|
|
92
|
+
* finds anything that looks like one - a key in a page is a key in every
|
|
93
|
+
* visitor's hands, and its whole power is creating projects.
|
|
94
|
+
*
|
|
95
|
+
* It does carry an environment, and it has to. A static file cannot know which
|
|
96
|
+
* deployment served it and the gate treats absent as off, so a tag without one
|
|
97
|
+
* mounts nothing at all - which is what #17's "mounts a working toolbar on a
|
|
98
|
+
* plain HTML page" cannot mean. The value is written, printed as a change, and
|
|
99
|
+
* called out as a note; `--environment production` is how a developer turns
|
|
100
|
+
* the tag off without deleting it. See DEFAULT_SCRIPT_ENVIRONMENT.
|
|
101
|
+
*/
|
|
102
|
+
export const htmlScriptTagStrategy = {
|
|
103
|
+
id: "html-script-tag",
|
|
104
|
+
label: "plain HTML page",
|
|
105
|
+
delivery: "script-tag",
|
|
106
|
+
detect(fs, root) {
|
|
107
|
+
for (const candidate of HTML_ENTRY_FILES) {
|
|
108
|
+
const path = joinPath(root, candidate);
|
|
109
|
+
if (fs.exists(path)) {
|
|
110
|
+
return { path, description: `HTML page at ${candidate}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
},
|
|
115
|
+
isInstalled(source) {
|
|
116
|
+
return source.includes(SCRIPT_BUNDLE_PATH);
|
|
117
|
+
},
|
|
118
|
+
inject(source, context) {
|
|
119
|
+
if (!context.projectUrl) {
|
|
120
|
+
// Unlike the npm route, this one cannot fall back to reading the URL
|
|
121
|
+
// from the environment at runtime: a static page has no environment.
|
|
122
|
+
// Without it the toolbar would ask the backend about a project keyed on
|
|
123
|
+
// the empty string, so refuse rather than install something inert.
|
|
124
|
+
throw new InjectionError("a script-tag install needs the project's URL, and nothing here reports one.\n" +
|
|
125
|
+
"Re-run with --project-url https://myapp.com.");
|
|
126
|
+
}
|
|
127
|
+
const environment = context.environment ?? DEFAULT_SCRIPT_ENVIRONMENT;
|
|
128
|
+
return insertBeforeClosingBody(source, `<script src="${context.apiUrl}${SCRIPT_BUNDLE_PATH}" ` +
|
|
129
|
+
`data-api-url="${context.apiUrl}" ` +
|
|
130
|
+
`data-project-url="${context.projectUrl}" ` +
|
|
131
|
+
`data-environment="${environment}" defer></script>`);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
function addImport(source) {
|
|
135
|
+
const importLine = `import { ${COMPONENT_NAME} } from "${PACKAGE_NAME}";\n`;
|
|
136
|
+
// After the last top-level import, so the file keeps its import block
|
|
137
|
+
// together rather than growing a stray line above a directive.
|
|
138
|
+
const importRe = /^import[\s\S]*?;[ \t]*$/gm;
|
|
139
|
+
let lastEnd = -1;
|
|
140
|
+
for (const match of source.matchAll(importRe)) {
|
|
141
|
+
lastEnd = match.index + match[0].length;
|
|
142
|
+
}
|
|
143
|
+
if (lastEnd >= 0) {
|
|
144
|
+
return `${source.slice(0, lastEnd)}\n${importLine.trimEnd()}${source.slice(lastEnd)}`;
|
|
145
|
+
}
|
|
146
|
+
// No imports at all: go after a leading "use client" directive if present,
|
|
147
|
+
// since nothing may precede one.
|
|
148
|
+
const directive = /^\s*("use client"|'use client');?\s*\n/.exec(source);
|
|
149
|
+
if (directive) {
|
|
150
|
+
const end = directive.index + directive[0].length;
|
|
151
|
+
return `${source.slice(0, end)}\n${importLine}${source.slice(end)}`;
|
|
152
|
+
}
|
|
153
|
+
return `${importLine}\n${source}`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Put `markup` immediately inside the closing `</body>`.
|
|
157
|
+
*
|
|
158
|
+
* Shared by both strategies because the placement problem is the same one in
|
|
159
|
+
* JSX and in HTML, and the interesting case is the same too: the one-line
|
|
160
|
+
* `<body>…</body>` that Next's template and hand-written HTML pages both use.
|
|
161
|
+
*/
|
|
162
|
+
function insertBeforeClosingBody(source, markup) {
|
|
163
|
+
const closingBody = source.lastIndexOf("</body>");
|
|
164
|
+
if (closingBody === -1) {
|
|
165
|
+
// Refusing beats guessing. A layout with no <body> is not an App Router
|
|
166
|
+
// root layout, and injecting somewhere plausible would produce a build
|
|
167
|
+
// failure the developer has to reverse-engineer.
|
|
168
|
+
throw new InjectionError("found a layout file but no <body> tag to place the toolbar in");
|
|
169
|
+
}
|
|
170
|
+
// Immediately before `</body>`, never at the start of its line: Next's own
|
|
171
|
+
// template ships `<body>{children}</body>` on one line, and inserting at the
|
|
172
|
+
// line start would put the toolbar between <html> and <body>, which is a
|
|
173
|
+
// broken layout rather than an install.
|
|
174
|
+
const lineStart = source.lastIndexOf("\n", closingBody) + 1;
|
|
175
|
+
const beforeOnLine = source.slice(lineStart, closingBody);
|
|
176
|
+
const openingBodyOnSameLine = beforeOnLine.includes("<body");
|
|
177
|
+
if (openingBodyOnSameLine) {
|
|
178
|
+
// Everything is on one line, so splice in place rather than reformat.
|
|
179
|
+
return `${source.slice(0, closingBody)}${markup}${source.slice(closingBody)}`;
|
|
180
|
+
}
|
|
181
|
+
// `</body>` is on its own line: take its indentation plus one step, so the
|
|
182
|
+
// diff reads as if a person wrote it.
|
|
183
|
+
const indent = beforeOnLine.match(/^\s*/)?.[0] ?? "";
|
|
184
|
+
const line = `${indent} ${markup}\n`;
|
|
185
|
+
return `${source.slice(0, lineStart)}${line}${source.slice(lineStart)}`;
|
|
186
|
+
}
|
|
187
|
+
/** A failure the developer can act on, as opposed to an internal error. */
|
|
188
|
+
export class InjectionError extends Error {
|
|
189
|
+
constructor(message) {
|
|
190
|
+
super(message);
|
|
191
|
+
this.name = "InjectionError";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export const STRATEGIES = [
|
|
195
|
+
nextAppRouterStrategy,
|
|
196
|
+
htmlScriptTagStrategy,
|
|
197
|
+
];
|
|
198
|
+
//# sourceMappingURL=strategies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategies.js","sourceRoot":"","sources":["../../src/cli/strategies.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { FeedbackCreateInput, FeedbackItemDTO, FeedbackStatus, ToolbarSessionState } from "./vendor/types/index.js";
|
|
2
|
+
export interface ToolbarSession {
|
|
3
|
+
state: ToolbarSessionState;
|
|
4
|
+
user?: {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
name: string;
|
|
8
|
+
};
|
|
9
|
+
project?: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
role?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A failed call, carrying the API's machine-readable code where there is one. */
|
|
16
|
+
export declare class ToolbarApiError extends Error {
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly status: number;
|
|
19
|
+
constructor(code: string, status: number, message?: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function createToolbarClient(apiUrl: string): {
|
|
22
|
+
/** Who the caller is, and whether they can act on this project. */
|
|
23
|
+
session(projectUrl: string): Promise<ToolbarSession>;
|
|
24
|
+
/** Ask for a sign-in link. Always resolves, whoever the address belongs to. */
|
|
25
|
+
requestMagicLink(email: string, callbackUrl?: string): Promise<{
|
|
26
|
+
sent: true;
|
|
27
|
+
}>;
|
|
28
|
+
signOut(): Promise<{
|
|
29
|
+
ok: true;
|
|
30
|
+
}>;
|
|
31
|
+
submit(input: FeedbackCreateInput): Promise<FeedbackItemDTO>;
|
|
32
|
+
/** Feedback on one page, which is how the toolbar finds this page's pins. */
|
|
33
|
+
listForPage(organizationId: string, pageUrl: string, status?: FeedbackStatus): Promise<FeedbackItemDTO[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Every item in the project, whatever page it was filed on, newest first.
|
|
36
|
+
* The inbox sheet widens to this: a reviewer working through a round of
|
|
37
|
+
* feedback needs to see the items waiting on the pages they have not
|
|
38
|
+
* opened yet, and the same route answers both because `pageUrl` is an
|
|
39
|
+
* optional filter rather than a separate endpoint.
|
|
40
|
+
*
|
|
41
|
+
* `limit` is passed explicitly rather than left to the server's default
|
|
42
|
+
* because this is the one unfiltered read the toolbar makes, against the
|
|
43
|
+
* one table that grows for the life of a project. Stating the bound here
|
|
44
|
+
* is what lets the sheet say "the newest N" instead of quietly showing a
|
|
45
|
+
* truncated list as if it were the whole thing.
|
|
46
|
+
*/
|
|
47
|
+
listForProject(organizationId: string, limit: number, status?: FeedbackStatus): Promise<FeedbackItemDTO[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Move an item between lanes. The mover is never on the wire: the API
|
|
50
|
+
* takes it from the session cookie and records it alongside the new
|
|
51
|
+
* status, so a reviewer cannot attribute their own triage to someone
|
|
52
|
+
* else. The re-read item comes back carrying that attribution, which is
|
|
53
|
+
* why the thread can render "resolved by X" straight from the response.
|
|
54
|
+
*/
|
|
55
|
+
setStatus(organizationId: string, itemId: string, status: FeedbackStatus): Promise<FeedbackItemDTO>;
|
|
56
|
+
reply(organizationId: string, itemId: string, body: string, screenshot?: string): Promise<FeedbackItemDTO>;
|
|
57
|
+
};
|
|
58
|
+
export type ToolbarClient = ReturnType<typeof createToolbarClient>;
|
|
59
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,mBAAmB,EACpB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAM3D;AA8BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM;IAE9C,mEAAmE;wBAC/C,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAOpD,+EAA+E;4BAEtE,MAAM,gBACC,MAAM,GACnB,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;eAOf,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;kBAMlB,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAO5D,6EAA6E;gCAE3D,MAAM,WACb,MAAM,WACN,cAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC;IAM7B;;;;;;;;;;;;OAYG;mCAEe,MAAM,SACf,MAAM,WACJ,cAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC;IAS7B;;;;;;OAMG;8BAEe,MAAM,UACd,MAAM,UACN,cAAc,GACrB,OAAO,CAAC,eAAe,CAAC;0BAST,MAAM,UACd,MAAM,QACR,MAAM,eAOC,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC;EAW9B;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** A failed call, carrying the API's machine-readable code where there is one. */
|
|
2
|
+
export class ToolbarApiError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
constructor(code, status, message) {
|
|
6
|
+
super(message ?? code);
|
|
7
|
+
this.name = "ToolbarApiError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function request(apiUrl, path, init = {}) {
|
|
13
|
+
let res;
|
|
14
|
+
try {
|
|
15
|
+
res = await fetch(`${apiUrl}${path}`, {
|
|
16
|
+
...init,
|
|
17
|
+
credentials: "include",
|
|
18
|
+
headers: {
|
|
19
|
+
...(init.body ? { "content-type": "application/json" } : {}),
|
|
20
|
+
...init.headers,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (cause) {
|
|
25
|
+
// A network failure and a rejected CORS preflight look the same from here.
|
|
26
|
+
// Both mean the same thing to a reviewer: it did not send, try again.
|
|
27
|
+
throw new ToolbarApiError("network_error", 0, String(cause));
|
|
28
|
+
}
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
const body = (await res.json().catch(() => ({})));
|
|
31
|
+
throw new ToolbarApiError(body.error ?? "request_failed", res.status);
|
|
32
|
+
}
|
|
33
|
+
return (await res.json());
|
|
34
|
+
}
|
|
35
|
+
export function createToolbarClient(apiUrl) {
|
|
36
|
+
return {
|
|
37
|
+
/** Who the caller is, and whether they can act on this project. */
|
|
38
|
+
session(projectUrl) {
|
|
39
|
+
return request(apiUrl, `/api/v1/toolbar-auth/session?projectUrl=${encodeURIComponent(projectUrl)}`);
|
|
40
|
+
},
|
|
41
|
+
/** Ask for a sign-in link. Always resolves, whoever the address belongs to. */
|
|
42
|
+
requestMagicLink(email, callbackUrl) {
|
|
43
|
+
return request(apiUrl, "/api/v1/toolbar-auth/magic-link", {
|
|
44
|
+
method: "POST",
|
|
45
|
+
body: JSON.stringify({ email, callbackUrl }),
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
signOut() {
|
|
49
|
+
return request(apiUrl, "/api/v1/toolbar-auth/sign-out", {
|
|
50
|
+
method: "POST",
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
submit(input) {
|
|
54
|
+
return request(apiUrl, "/api/v1/feedback", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: JSON.stringify(input),
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
/** Feedback on one page, which is how the toolbar finds this page's pins. */
|
|
60
|
+
listForPage(organizationId, pageUrl, status) {
|
|
61
|
+
const params = new URLSearchParams({ organizationId, pageUrl });
|
|
62
|
+
if (status)
|
|
63
|
+
params.set("status", status);
|
|
64
|
+
return request(apiUrl, `/api/v1/feedback?${params.toString()}`);
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* Every item in the project, whatever page it was filed on, newest first.
|
|
68
|
+
* The inbox sheet widens to this: a reviewer working through a round of
|
|
69
|
+
* feedback needs to see the items waiting on the pages they have not
|
|
70
|
+
* opened yet, and the same route answers both because `pageUrl` is an
|
|
71
|
+
* optional filter rather than a separate endpoint.
|
|
72
|
+
*
|
|
73
|
+
* `limit` is passed explicitly rather than left to the server's default
|
|
74
|
+
* because this is the one unfiltered read the toolbar makes, against the
|
|
75
|
+
* one table that grows for the life of a project. Stating the bound here
|
|
76
|
+
* is what lets the sheet say "the newest N" instead of quietly showing a
|
|
77
|
+
* truncated list as if it were the whole thing.
|
|
78
|
+
*/
|
|
79
|
+
listForProject(organizationId, limit, status) {
|
|
80
|
+
const params = new URLSearchParams({
|
|
81
|
+
organizationId,
|
|
82
|
+
limit: String(limit),
|
|
83
|
+
});
|
|
84
|
+
if (status)
|
|
85
|
+
params.set("status", status);
|
|
86
|
+
return request(apiUrl, `/api/v1/feedback?${params.toString()}`);
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Move an item between lanes. The mover is never on the wire: the API
|
|
90
|
+
* takes it from the session cookie and records it alongside the new
|
|
91
|
+
* status, so a reviewer cannot attribute their own triage to someone
|
|
92
|
+
* else. The re-read item comes back carrying that attribution, which is
|
|
93
|
+
* why the thread can render "resolved by X" straight from the response.
|
|
94
|
+
*/
|
|
95
|
+
setStatus(organizationId, itemId, status) {
|
|
96
|
+
return request(apiUrl, `/api/v1/feedback/${itemId}/status?organizationId=${encodeURIComponent(organizationId)}`, { method: "PATCH", body: JSON.stringify({ status }) });
|
|
97
|
+
},
|
|
98
|
+
reply(organizationId, itemId, body,
|
|
99
|
+
/**
|
|
100
|
+
* A data URL of one element the reviewer pointed at, or undefined.
|
|
101
|
+
* Omitted from the payload entirely when absent rather than sent as
|
|
102
|
+
* null: the schema makes it optional, and an explicit null would be a
|
|
103
|
+
* different shape for the same fact.
|
|
104
|
+
*/
|
|
105
|
+
screenshot) {
|
|
106
|
+
return request(apiUrl, `/api/v1/feedback/${itemId}/replies?organizationId=${encodeURIComponent(organizationId)}`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: JSON.stringify(screenshot ? { body, screenshot } : { body }),
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type PinShortcutSetting } from "./shortcut.js";
|
|
2
|
+
export interface ToolbarConfig {
|
|
3
|
+
/** Base URL of the BitVea Feedback backend, no trailing slash. */
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
/**
|
|
6
|
+
* Where the toolbar's own static assets - the two font files - are served
|
|
7
|
+
* from, no trailing slash. Defaults to `apiUrl`, which is right for every
|
|
8
|
+
* real install: the backend that serves the API serves `/fonts` too.
|
|
9
|
+
*
|
|
10
|
+
* It is a separate field because the two are not the same question. A
|
|
11
|
+
* harness can legitimately point the API at a host that does not exist and
|
|
12
|
+
* answer every call locally, which is exactly what the preview route does -
|
|
13
|
+
* and welding the font URL to `apiUrl` made the fonts 404 there, so the one
|
|
14
|
+
* page built to review the toolbar was the one page that rendered it in the
|
|
15
|
+
* wrong typeface.
|
|
16
|
+
*/
|
|
17
|
+
assetOrigin: string;
|
|
18
|
+
/** The project this deployment reports against, its normalised origin. */
|
|
19
|
+
projectUrl: string;
|
|
20
|
+
/** "preview", "development" or "production". */
|
|
21
|
+
environment: string;
|
|
22
|
+
/** Git branch, when the build knows it. */
|
|
23
|
+
gitBranch?: string | undefined;
|
|
24
|
+
/** Git commit SHA, when the build knows it. */
|
|
25
|
+
gitCommit?: string | undefined;
|
|
26
|
+
/** Whether the toolbar renders and talks to the network at all. */
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The chord that arms pinning, or `null` to claim no key at all.
|
|
30
|
+
*
|
|
31
|
+
* THIS IS THE HOST'S REMEDY, not a preference. The toolbar listens for the
|
|
32
|
+
* chord in the capture phase and swallows it when it acts, so a host
|
|
33
|
+
* application that binds the same keys loses its own binding silently - and
|
|
34
|
+
* the default chord is known to collide (`Cmd+Space` is Spotlight on macOS;
|
|
35
|
+
* `Ctrl+Space` is commonly an input method's). Claiming a key inside
|
|
36
|
+
* somebody else's application is only defensible while there is a way out
|
|
37
|
+
* of it, so this field is that way out, and `null` is honoured by
|
|
38
|
+
* registering no listener rather than by declining inside one.
|
|
39
|
+
*/
|
|
40
|
+
pinShortcut: PinShortcutSetting;
|
|
41
|
+
}
|
|
42
|
+
/** Props a host app may pass to override what the environment reports. */
|
|
43
|
+
export interface ToolbarConfigOverrides {
|
|
44
|
+
apiUrl?: string;
|
|
45
|
+
assetOrigin?: string;
|
|
46
|
+
projectUrl?: string;
|
|
47
|
+
environment?: string;
|
|
48
|
+
gitBranch?: string;
|
|
49
|
+
gitCommit?: string;
|
|
50
|
+
/** Force the gate one way for a test harness. Not for production use. */
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Replace the pinning chord, or pass `null` to switch it off.
|
|
54
|
+
*
|
|
55
|
+
* Omitted means the default. `null` is a deliberate value rather than a
|
|
56
|
+
* missing one, which is why this is not `ShortcutChord | undefined`: a host
|
|
57
|
+
* has to be able to say "no key" and be heard.
|
|
58
|
+
*/
|
|
59
|
+
pinShortcut?: PinShortcutSetting;
|
|
60
|
+
}
|
|
61
|
+
export declare const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
|
|
62
|
+
/**
|
|
63
|
+
* `NEXT_PUBLIC_` env names Vercel populates in the client bundle. They are
|
|
64
|
+
* read through a bag rather than off `process.env` directly so this stays a
|
|
65
|
+
* pure function, and because a bundler needs literal member access to inline
|
|
66
|
+
* them, which the caller does.
|
|
67
|
+
*/
|
|
68
|
+
export declare const VERCEL_ENV_VARS: {
|
|
69
|
+
readonly env: "NEXT_PUBLIC_VERCEL_ENV";
|
|
70
|
+
readonly branch: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF";
|
|
71
|
+
readonly commit: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA";
|
|
72
|
+
/**
|
|
73
|
+
* The project's stable production hostname, and the identity a Project row
|
|
74
|
+
* is keyed by. Preferred over the per-deployment URL below because a
|
|
75
|
+
* preview URL changes on every commit, and keying on it would create a new
|
|
76
|
+
* project per deploy instead of collecting a project's feedback in one
|
|
77
|
+
* place. The installer registers the same value, so the two agree.
|
|
78
|
+
*/
|
|
79
|
+
readonly productionUrl: "NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL";
|
|
80
|
+
/** This deployment's own URL. The fallback, and what a local build has. */
|
|
81
|
+
readonly url: "NEXT_PUBLIC_VERCEL_URL";
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the running configuration. Overrides win over the environment, and
|
|
85
|
+
* the environment wins over nothing.
|
|
86
|
+
*
|
|
87
|
+
* `enabled` folds in one extra condition beyond the environment gate: without
|
|
88
|
+
* an API URL the toolbar has nowhere to send anything, so it stays dark rather
|
|
89
|
+
* than rendering a button that fails on click.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveConfig(env: Record<string, string | undefined>, overrides?: ToolbarConfigOverrides): ToolbarConfig;
|
|
92
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,eAAO,MAAM,eAAe,yCAAyC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;IAI1B;;;;;;OAMG;;IAEH,2EAA2E;;CAEnE,CAAC;AAMX;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,SAAS,GAAE,sBAA2B,GACrC,aAAa,CA8Bf"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The toolbar's configuration, and where each field comes from.
|
|
2
|
+
//
|
|
3
|
+
// Defaults are read from the environment Vercel already populates, so a
|
|
4
|
+
// correct install needs no config object at all; every field can still be
|
|
5
|
+
// overridden by a prop for the cases the environment cannot describe (a local
|
|
6
|
+
// harness, a preview pointed at a different backend).
|
|
7
|
+
import { isToolbarEnabledFromEnv } from "./gating.js";
|
|
8
|
+
import { DEFAULT_PIN_SHORTCUT } from "./shortcut.js";
|
|
9
|
+
export const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
|
|
10
|
+
/**
|
|
11
|
+
* `NEXT_PUBLIC_` env names Vercel populates in the client bundle. They are
|
|
12
|
+
* read through a bag rather than off `process.env` directly so this stays a
|
|
13
|
+
* pure function, and because a bundler needs literal member access to inline
|
|
14
|
+
* them, which the caller does.
|
|
15
|
+
*/
|
|
16
|
+
export const VERCEL_ENV_VARS = {
|
|
17
|
+
env: "NEXT_PUBLIC_VERCEL_ENV",
|
|
18
|
+
branch: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF",
|
|
19
|
+
commit: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA",
|
|
20
|
+
/**
|
|
21
|
+
* The project's stable production hostname, and the identity a Project row
|
|
22
|
+
* is keyed by. Preferred over the per-deployment URL below because a
|
|
23
|
+
* preview URL changes on every commit, and keying on it would create a new
|
|
24
|
+
* project per deploy instead of collecting a project's feedback in one
|
|
25
|
+
* place. The installer registers the same value, so the two agree.
|
|
26
|
+
*/
|
|
27
|
+
productionUrl: "NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL",
|
|
28
|
+
/** This deployment's own URL. The fallback, and what a local build has. */
|
|
29
|
+
url: "NEXT_PUBLIC_VERCEL_URL",
|
|
30
|
+
};
|
|
31
|
+
function trimSlashes(value) {
|
|
32
|
+
return value.replace(/\/+$/, "");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the running configuration. Overrides win over the environment, and
|
|
36
|
+
* the environment wins over nothing.
|
|
37
|
+
*
|
|
38
|
+
* `enabled` folds in one extra condition beyond the environment gate: without
|
|
39
|
+
* an API URL the toolbar has nowhere to send anything, so it stays dark rather
|
|
40
|
+
* than rendering a button that fails on click.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveConfig(env, overrides = {}) {
|
|
43
|
+
const apiUrl = trimSlashes(overrides.apiUrl ?? env[API_URL_ENV_VAR] ?? "");
|
|
44
|
+
const assetOrigin = trimSlashes(overrides.assetOrigin ?? apiUrl);
|
|
45
|
+
const vercelHost = env[VERCEL_ENV_VARS.productionUrl] ?? env[VERCEL_ENV_VARS.url];
|
|
46
|
+
const projectUrl = trimSlashes(overrides.projectUrl ?? (vercelHost ? `https://${vercelHost}` : ""));
|
|
47
|
+
const environment = overrides.environment ?? env[VERCEL_ENV_VARS.env] ?? "unknown";
|
|
48
|
+
const gatePasses = isToolbarEnabledFromEnv(env);
|
|
49
|
+
const enabled = (overrides.enabled ?? gatePasses) && apiUrl.length > 0;
|
|
50
|
+
return {
|
|
51
|
+
apiUrl,
|
|
52
|
+
assetOrigin,
|
|
53
|
+
projectUrl,
|
|
54
|
+
environment,
|
|
55
|
+
gitBranch: overrides.gitBranch ?? env[VERCEL_ENV_VARS.branch],
|
|
56
|
+
gitCommit: overrides.gitCommit ?? env[VERCEL_ENV_VARS.commit],
|
|
57
|
+
enabled,
|
|
58
|
+
// `??` and not `||`, because `null` is the host saying "no key" and must
|
|
59
|
+
// survive; only an omitted override falls back to the default.
|
|
60
|
+
pinShortcut: overrides.pinShortcut === undefined
|
|
61
|
+
? DEFAULT_PIN_SHORTCUT
|
|
62
|
+
: overrides.pinShortcut,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,8EAA8E;AAC9E,sDAAsD;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAA2B,MAAM,YAAY,CAAC;AA+D3E,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,wBAAwB;IAC7B,MAAM,EAAE,mCAAmC;IAC3C,MAAM,EAAE,mCAAmC;IAC3C;;;;;;OAMG;IACH,aAAa,EAAE,2CAA2C;IAC1D,2EAA2E;IAC3E,GAAG,EAAE,wBAAwB;CACrB,CAAC;AAEX,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAuC,EACvC,YAAoC,EAAE;IAEtC,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3E,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC;IACjE,MAAM,UAAU,GACd,GAAG,CAAC,eAAe,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,WAAW,CAC5B,SAAS,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;IACF,MAAM,WAAW,GACf,SAAS,CAAC,WAAW,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAEjE,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,OAAO,GACX,CAAC,SAAS,CAAC,OAAO,IAAI,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzD,OAAO;QACL,MAAM;QACN,WAAW;QACX,UAAU;QACV,WAAW;QACX,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;QAC7D,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;QAC7D,OAAO;QACP,yEAAyE;QACzE,+DAA+D;QAC/D,WAAW,EACT,SAAS,CAAC,WAAW,KAAK,SAAS;YACjC,CAAC,CAAC,oBAAoB;YACtB,CAAC,CAAC,SAAS,CAAC,WAAW;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** The whole namespace. Nothing else in a hash is ours. */
|
|
2
|
+
export declare const DEEP_LINK_PREFIX = "#bitvea-feedback=";
|
|
3
|
+
/**
|
|
4
|
+
* The feedback id a hash names, or null for every other hash there is -
|
|
5
|
+
* including `#pricing`, `#/route/23` and a bare `#`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseDeepLink(hash: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* The URL that opens one item's thread on the page it was filed on. Used by
|
|
10
|
+
* the inbox sheet for an item on another page of the same site.
|
|
11
|
+
*
|
|
12
|
+
* Any existing fragment is dropped, because a URL has one fragment and this
|
|
13
|
+
* one is ours. That is safe precisely because page identity drops the fragment
|
|
14
|
+
* too - see the note at the top of this file - so the URL this returns and the
|
|
15
|
+
* `pageUrl` the item was stored under reduce to the same identity.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildDeepLink(pageUrl: string, feedbackItemId: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Read the deep link, once. A pure read: nothing about the document changes.
|
|
20
|
+
*
|
|
21
|
+
* It used to strip the fragment here as well, which was a `replaceState` from
|
|
22
|
+
* inside a render pass - a side effect on the HOST's document, in a phase
|
|
23
|
+
* React may run twice and may throw away. The clear is now the shell's job
|
|
24
|
+
* (`clearDeepLinkHash` from an effect), and this function only answers.
|
|
25
|
+
*
|
|
26
|
+
* Nothing downstream may assume the clear stuck in any case: a render-phase
|
|
27
|
+
* clear was overwritten by Next's App Router a moment later, which is why the
|
|
28
|
+
* pin layer's idea of "this page" comes from `currentPageUrl` rather than from
|
|
29
|
+
* whatever the address bar ended up saying.
|
|
30
|
+
*/
|
|
31
|
+
export declare function captureDeepLink(): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The identity of the page the reviewer is on: its URL with no fragment.
|
|
34
|
+
*
|
|
35
|
+
* This is what "which page is this" means everywhere the toolbar asks - the
|
|
36
|
+
* pin layer's page-scoped query, the inbox sheet's scope, and the metadata
|
|
37
|
+
* attached to a new report - and it is deliberately not `location.href`.
|
|
38
|
+
*
|
|
39
|
+
* Two reasons, and both are about the address bar not being ours to trust. A
|
|
40
|
+
* host framework can put our own fragment back after we have removed it
|
|
41
|
+
* (Next's App Router does, see `clearDeepLinkHash`), and a `pageUrl` carrying
|
|
42
|
+
* `#bitvea-feedback=...` matches no stored row. And a deep link has to replace
|
|
43
|
+
* the host's fragment to carry the id at all, so a landing URL and the URL a
|
|
44
|
+
* report was filed from differ by the fragment as a matter of course - see the
|
|
45
|
+
* note at the top of this file for why identity gives the fragment up rather
|
|
46
|
+
* than the other way round.
|
|
47
|
+
*
|
|
48
|
+
* Deriving it instead of trusting the URL makes that class of bug impossible
|
|
49
|
+
* rather than merely unlikely: it is right on the first render, whatever the
|
|
50
|
+
* host does to the address bar afterwards.
|
|
51
|
+
*/
|
|
52
|
+
export declare function currentPageUrl(): string;
|
|
53
|
+
/**
|
|
54
|
+
* `href` reduced to a page identity: origin, path and query, no fragment.
|
|
55
|
+
*
|
|
56
|
+
* Every fragment, not only ours. See the note at the top of this file: a URL
|
|
57
|
+
* has one fragment and a deep link needs it, so an identity that keeps the
|
|
58
|
+
* host's cannot be produced by a link we build. Query strings are kept,
|
|
59
|
+
* because they do distinguish pages on a server-rendered host.
|
|
60
|
+
*/
|
|
61
|
+
export declare function pageIdentity(href: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Strip our fragment, leaving the rest of the URL byte-identical. Re-checks
|
|
64
|
+
* the prefix so this can never be the thing that eats a host's own hash, and
|
|
65
|
+
* uses `replaceState` so it adds no history entry the back button has to walk
|
|
66
|
+
* back through.
|
|
67
|
+
*
|
|
68
|
+
* Idempotent, and called more than once on purpose. A render-phase clear is
|
|
69
|
+
* not the last word on the address bar: Next's App Router restores the URL it
|
|
70
|
+
* considered canonical at load - fragment included - from a commit-phase
|
|
71
|
+
* effect, and because a parent's effects run after its children's there is no
|
|
72
|
+
* effect the toolbar can register that is guaranteed to run later. So the
|
|
73
|
+
* shell schedules one more clear onto the next macrotask, which is after the
|
|
74
|
+
* whole commit; see `EnabledToolbar`. Re-checking the prefix is what makes
|
|
75
|
+
* that safe: by then the host may have navigated, and a second clear must
|
|
76
|
+
* never eat a fragment that is not ours.
|
|
77
|
+
*/
|
|
78
|
+
export declare function clearDeepLinkHash(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Forget the capture. Only tests need this - a module-scope one-shot is
|
|
81
|
+
* exactly right in a browser, where there is one page load per module, and
|
|
82
|
+
* exactly wrong in a runner where there are many renders per module. It is
|
|
83
|
+
* deliberately not re-exported from the package barrel.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resetDeepLinkCapture(): void;
|
|
86
|
+
//# sourceMappingURL=deepLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepLink.d.ts","sourceRoot":"","sources":["../src/deepLink.ts"],"names":[],"mappings":"AA0BA,2DAA2D;AAC3D,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAWpD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAI7E;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAQ/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAYxC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|