@solhun/feedback-kit-web 0.10.0 → 0.12.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/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
1
+ import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackAdapter, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
2
2
  export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
3
3
  import * as react from 'react';
4
4
 
@@ -391,6 +391,11 @@ interface WebWidget {
391
391
  widget: WidgetController;
392
392
  picking: ElementPickingController;
393
393
  store: MarkerStore;
394
+ /**
395
+ * "이 화면의 제보가 어디로 가나" 조회. 어댑터가 지원하지 않으면 `null` 이고,
396
+ * 그러면 설정 화면이 "확인 못 했습니다"를 보여준다. 표시용이라 없어도 제보에는 영향이 없다.
397
+ */
398
+ fetchConnection: NonNullable<FeedbackAdapter["fetchConnection"]> | null;
394
399
  dispose(): void;
395
400
  }
396
401
  declare function createWebWidget(opts: WebWidgetOpts): WebWidget;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
1
+ import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, RouteWatcher, WidgetControllerOpts, HintProvider, WidgetController, FeedbackAdapter, FeedbackStorage, ContextProviders, DiagnosticsInstallOpts, RouteTrailStore } from '@solhun/feedback-kit-core';
2
2
  export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
3
3
  import * as react from 'react';
4
4
 
@@ -391,6 +391,11 @@ interface WebWidget {
391
391
  widget: WidgetController;
392
392
  picking: ElementPickingController;
393
393
  store: MarkerStore;
394
+ /**
395
+ * "이 화면의 제보가 어디로 가나" 조회. 어댑터가 지원하지 않으면 `null` 이고,
396
+ * 그러면 설정 화면이 "확인 못 했습니다"를 보여준다. 표시용이라 없어도 제보에는 영향이 없다.
397
+ */
398
+ fetchConnection: NonNullable<FeedbackAdapter["fetchConnection"]> | null;
394
399
  dispose(): void;
395
400
  }
396
401
  declare function createWebWidget(opts: WebWidgetOpts): WebWidget;
package/dist/index.js CHANGED
@@ -1062,6 +1062,11 @@ function hintSourceFrom(queue) {
1062
1062
  const cacheKey = adapter.hintsCacheKey ?? "default";
1063
1063
  return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
1064
1064
  }
1065
+ function connectionSourceFrom(queue) {
1066
+ const adapter = queue.adapter;
1067
+ if (!adapter || typeof adapter.fetchConnection !== "function") return null;
1068
+ return adapter.fetchConnection.bind(adapter);
1069
+ }
1065
1070
  function resolveHintProvider(opts) {
1066
1071
  if (opts.hintProvider) return opts.hintProvider;
1067
1072
  if (opts.hints) {
@@ -1147,6 +1152,7 @@ function createWebWidget(opts) {
1147
1152
  widget,
1148
1153
  picking,
1149
1154
  store,
1155
+ fetchConnection: connectionSourceFrom(opts.queue),
1150
1156
  dispose() {
1151
1157
  picking.dispose();
1152
1158
  widget.dispose();
@@ -1174,7 +1180,7 @@ import {
1174
1180
  } from "react";
1175
1181
 
1176
1182
  // src/version.ts
1177
- var VERSION = true ? "0.10.0" : "dev";
1183
+ var VERSION = true ? "0.12.0" : "dev";
1178
1184
 
1179
1185
  // src/dev-clipboard.ts
1180
1186
  import { sourceFromElement } from "@solhun/feedback-kit-core";
@@ -1252,6 +1258,36 @@ function resolveDevMode(propValue, stored) {
1252
1258
  if (stored !== null) return stored;
1253
1259
  return propValue === true;
1254
1260
  }
1261
+ var SETTINGS_FLAG = "fk-settings";
1262
+ var SESSION_KEY = "feedback-kit:settings-visible";
1263
+ function isSettingsRequested(search, session) {
1264
+ const store = session === void 0 ? sessionStorageOrNull() : session;
1265
+ let requested = false;
1266
+ try {
1267
+ requested = new URLSearchParams(search).get(SETTINGS_FLAG) === "1";
1268
+ } catch {
1269
+ requested = false;
1270
+ }
1271
+ if (requested) {
1272
+ try {
1273
+ store?.setItem(SESSION_KEY, "1");
1274
+ } catch {
1275
+ }
1276
+ return true;
1277
+ }
1278
+ try {
1279
+ return store?.getItem(SESSION_KEY) === "1";
1280
+ } catch {
1281
+ return false;
1282
+ }
1283
+ }
1284
+ function sessionStorageOrNull() {
1285
+ try {
1286
+ return globalThis.sessionStorage ?? null;
1287
+ } catch {
1288
+ return null;
1289
+ }
1290
+ }
1255
1291
 
1256
1292
  // src/feedback-kit.tsx
1257
1293
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1749,10 +1785,15 @@ function FeedbackKit(props) {
1749
1785
  const [pickingState, setPickingState] = useState(INITIAL_PICKING_STATE);
1750
1786
  const [announcement, setAnnouncement] = useState(null);
1751
1787
  const [devMode, setDevMode] = useState(() => resolveDevMode(props.devMode, readDevPreference()));
1752
- const [devSettingsAvailable] = useState(
1788
+ const [devToggleAvailable] = useState(
1753
1789
  () => props.devMode === true || isDevSettingsHost(globalThis.location?.hostname ?? "")
1754
1790
  );
1791
+ const [devSettingsAvailable] = useState(
1792
+ () => props.devMode === true || isDevSettingsHost(globalThis.location?.hostname ?? "") || isSettingsRequested(globalThis.location?.search ?? "")
1793
+ );
1755
1794
  const [devSettingsOpen, setDevSettingsOpen] = useState(false);
1795
+ const [connection, setConnection] = useState(null);
1796
+ const [connectionState, setConnectionState] = useState("idle");
1756
1797
  const devClipsRef = useRef([]);
1757
1798
  const [devClipCount, setDevClipCount] = useState(0);
1758
1799
  const [devFallbackText, setDevFallbackText] = useState(null);
@@ -1912,6 +1953,20 @@ function FeedbackKit(props) {
1912
1953
  if (devClipsRef.current.length === 0) return;
1913
1954
  await putOnClipboard(renderDevClipList(devClipsRef.current));
1914
1955
  }
1956
+ function openDevSettings() {
1957
+ setDevSettingsOpen(true);
1958
+ if (connectionState !== "idle") return;
1959
+ const fetchConnection = activeKit.fetchConnection;
1960
+ if (!fetchConnection) {
1961
+ setConnectionState("fail");
1962
+ return;
1963
+ }
1964
+ setConnectionState("loading");
1965
+ void fetchConnection().then((value) => {
1966
+ setConnection(value);
1967
+ setConnectionState(value ? "done" : "fail");
1968
+ }).catch(() => setConnectionState("fail"));
1969
+ }
1915
1970
  function toggleDevMode(next) {
1916
1971
  writeDevPreference(next);
1917
1972
  setDevMode(next);
@@ -2033,7 +2088,7 @@ function FeedbackKit(props) {
2033
2088
  "data-fk-dev-settings-toggle": "",
2034
2089
  "aria-label": "\uD53C\uB4DC\uBC31 \uC124\uC815",
2035
2090
  "aria-expanded": devSettingsOpen,
2036
- onClick: () => setDevSettingsOpen((open) => !open),
2091
+ onClick: () => devSettingsOpen ? setDevSettingsOpen(false) : openDevSettings(),
2037
2092
  style: {
2038
2093
  ...baseButton,
2039
2094
  width: 46,
@@ -2165,6 +2220,7 @@ function FeedbackKit(props) {
2165
2220
  {
2166
2221
  type: "checkbox",
2167
2222
  checked: devMode,
2223
+ disabled: !devToggleAvailable,
2168
2224
  onChange: (event) => toggleDevMode(event.currentTarget.checked),
2169
2225
  style: { marginTop: 3 }
2170
2226
  }
@@ -2178,11 +2234,61 @@ function FeedbackKit(props) {
2178
2234
  ] })
2179
2235
  ] })
2180
2236
  ] }),
2181
- /* @__PURE__ */ jsxs("p", { style: { margin: "12px 0 0", color: TOKENS.muted, fontSize: 12 }, children: [
2182
- "\uC774 \uC124\uC815\uC740 \uB85C\uCEEC(",
2237
+ /* @__PURE__ */ jsxs(
2238
+ "div",
2239
+ {
2240
+ "data-fk-connection": "",
2241
+ style: {
2242
+ marginTop: 12,
2243
+ paddingTop: 12,
2244
+ borderTop: `1px solid ${TOKENS.line}`,
2245
+ fontSize: 12.5
2246
+ },
2247
+ children: [
2248
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 700, marginBottom: 6 }, children: "\uBCF4\uB0B4\uB294 \uACF3" }),
2249
+ connectionState === "loading" ? /* @__PURE__ */ jsx("div", { style: { color: TOKENS.muted }, children: "\uD655\uC778\uD558\uB294 \uC911\u2026" }) : connectionState === "fail" || !connection ? (
2250
+ // 조용히 비우면 "연결이 없다"로 읽힌다. 못 물어봤다는 것과 없다는 것은 다르다.
2251
+ /* @__PURE__ */ jsx("div", { style: { color: TOKENS.muted }, children: "\uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(\uC218\uC9D1\uCC98\uAC00 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC74C)." })
2252
+ ) : /* @__PURE__ */ jsxs("ul", { style: { margin: 0, paddingLeft: 16, color: TOKENS.ink, lineHeight: 1.7 }, children: [
2253
+ /* @__PURE__ */ jsxs("li", { children: [
2254
+ "\uC218\uC9D1\uCC98 \xB7 ",
2255
+ connection.project.name ?? "\uC774\uB984 \uC5C6\uB294 \uD504\uB85C\uC81D\uD2B8"
2256
+ ] }),
2257
+ /* @__PURE__ */ jsxs("li", { children: [
2258
+ "Linear \xB7",
2259
+ " ",
2260
+ connection.linear.connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
2261
+ connection.linear.teamName ?? "\uC5F0\uACB0\uB41C \uD300",
2262
+ connection.linear.projectName ? ` / ${connection.linear.projectName}` : "",
2263
+ " \u2014 ",
2264
+ /* @__PURE__ */ jsx("b", { children: connection.linear.autoExport ? "\uC81C\uBCF4\uAC00 \uC790\uB3D9\uC73C\uB85C \uB098\uAC11\uB2C8\uB2E4" : "\uC790\uB3D9 \uBC1C\uC1A1 \uAEBC\uC9D0" })
2265
+ ] }) : "\uC5F0\uACB0 \uC5C6\uC74C"
2266
+ ] }),
2267
+ /* @__PURE__ */ jsxs("li", { children: [
2268
+ "Symphony \xB7",
2269
+ " ",
2270
+ connection.symphonySlug ? `${connection.symphonySlug} \uAC00 \uCC98\uB9AC` : "\uC790\uB3D9 \uCC98\uB9AC \uC548 \uD568"
2271
+ ] })
2272
+ ] }),
2273
+ devMode ? (
2274
+ // 개발용 모드가 켜져 있으면 위 목록은 "원래 가던 곳"일 뿐이다. 그대로 두면
2275
+ // 지금도 거기로 가는 줄 읽는다.
2276
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 8, color: TOKENS.warning, fontWeight: 700 }, children: "\uC9C0\uAE08\uC740 \uC5B4\uB514\uC5D0\uB3C4 \uC548 \uBCF4\uB0C5\uB2C8\uB2E4 \u2014 \uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uAC00 \uCF1C\uC838 \uC788\uC2B5\uB2C8\uB2E4." })
2277
+ ) : null
2278
+ ]
2279
+ }
2280
+ ),
2281
+ /* @__PURE__ */ jsx("p", { style: { margin: "12px 0 0", color: TOKENS.muted, fontSize: 12 }, children: devToggleAvailable ? /* @__PURE__ */ jsxs(Fragment, { children: [
2282
+ "\uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uB294 \uB85C\uCEEC(",
2183
2283
  /* @__PURE__ */ jsx("code", { children: "localhost" }),
2184
- ")\uC5D0\uC11C\uB9CC \uBCF4\uC785\uB2C8\uB2E4. \uBC30\uD3EC\uB41C \uD654\uBA74\uC5D0\uC11C\uB294 \uC2E4\uC0AC\uC6A9\uC790\uAC00 \uCF24 \uC218 \uC5C6\uC5B4\uC57C \uD574\uC11C \uC544\uC608 \uB744\uC6B0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
2185
- ] }),
2284
+ ")\uC5D0\uC11C\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uBC30\uD3EC\uB41C \uD654\uBA74\uC5D0\uC11C \uC2E4\uC0AC\uC6A9\uC790\uAC00 \uCF1C\uBA74 \uADF8 \uC0AC\uB78C \uC81C\uBCF4\uAC00 \uC5B4\uB514\uC5D0\uB3C4 \uC548 \uB0A8\uAE30 \uB54C\uBB38\uC785\uB2C8\uB2E4."
2285
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2286
+ "\uC5EC\uAE30\uC11C\uB294 ",
2287
+ /* @__PURE__ */ jsx("b", { children: "\uBCF4\uAE30\uB9CC" }),
2288
+ " \uB429\uB2C8\uB2E4. \uAC1C\uBC1C\uC6A9 \uBAA8\uB4DC\uB294 \uB85C\uCEEC(",
2289
+ /* @__PURE__ */ jsx("code", { children: "localhost" }),
2290
+ ")\uC5D0\uC11C\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC2E4\uC0AC\uC6A9\uC790\uAC00 \uCF1C\uBA74 \uADF8 \uC0AC\uB78C \uC81C\uBCF4\uAC00 \uC5B4\uB514\uC5D0\uB3C4 \uC548 \uB0A8\uAE30 \uB54C\uBB38\uC785\uB2C8\uB2E4."
2291
+ ] }) }),
2186
2292
  /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 12 }, children: /* @__PURE__ */ jsx("button", { type: "button", style: baseButton, onClick: () => setDevSettingsOpen(false), children: "\uB2EB\uAE30" }) })
2187
2293
  ]
2188
2294
  }