tdecollab 0.3.4 → 0.3.6

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/tui/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getRuntimeConfigDiagnostics
4
+ } from "../chunk-ZTJYFJQG.js";
2
5
  import {
3
6
  ImageDownloader
4
7
  } from "../chunk-JBDK5WP3.js";
@@ -19,7 +22,7 @@ import {
19
22
  loadGitlabConfig,
20
23
  loadJiraConfig,
21
24
  tryBuildJiraIssueMap
22
- } from "../chunk-5OB3KU5D.js";
25
+ } from "../chunk-SIKUIQKX.js";
23
26
  import "../chunk-IFYMZLQI.js";
24
27
 
25
28
  // tools/tui/index.tsx
@@ -857,8 +860,8 @@ function toRelativeIfPossible(absPath) {
857
860
 
858
861
  // tools/tui/components/FormField.tsx
859
862
  import { Box as Box9, Text as Text8 } from "ink";
860
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
861
- function FormField({ field, value, focused, error, accent = T.pink }) {
863
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
864
+ function FormField({ field, value, focused, cursor, error, accent = T.pink }) {
862
865
  const labelText = field.label.padEnd(28);
863
866
  if (field.type === "bool") {
864
867
  const checked = value === true;
@@ -898,6 +901,10 @@ function FormField({ field, value, focused, error, accent = T.pink }) {
898
901
  const borderColor = error ? T.red : focused ? accent : T.border;
899
902
  const valStr = String(value ?? "");
900
903
  const isPath = !!field.pathType;
904
+ const cursorIndex = Math.max(0, Math.min(cursor ?? valStr.length, valStr.length));
905
+ const beforeCursor = valStr.slice(0, cursorIndex);
906
+ const cursorChar = valStr[cursorIndex];
907
+ const afterCursor = valStr.slice(cursorIndex + (cursorChar ? 1 : 0));
901
908
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 0, children: [
902
909
  /* @__PURE__ */ jsxs9(Box9, { gap: 1, children: [
903
910
  /* @__PURE__ */ jsx9(Text8, { color: focused ? accent : T.fgDim, bold: focused, children: labelText }),
@@ -914,19 +921,27 @@ function FormField({ field, value, focused, error, accent = T.pink }) {
914
921
  field.prefix,
915
922
  " "
916
923
  ] }),
917
- valStr ? /* @__PURE__ */ jsx9(Text8, { color: T.fg, children: valStr }) : /* @__PURE__ */ jsx9(Text8, { color: T.fgFaint, children: field.hint ?? "" }),
918
- focused && /* @__PURE__ */ jsx9(Text8, { backgroundColor: accent, color: T.bg, children: " " })
924
+ valStr && focused ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
925
+ /* @__PURE__ */ jsx9(Text8, { color: T.fg, children: beforeCursor }),
926
+ /* @__PURE__ */ jsx9(Text8, { backgroundColor: accent, color: T.bg, children: cursorChar || " " }),
927
+ /* @__PURE__ */ jsx9(Text8, { color: T.fg, children: afterCursor })
928
+ ] }) : valStr ? /* @__PURE__ */ jsx9(Text8, { color: T.fg, children: valStr }) : /* @__PURE__ */ jsx9(Text8, { color: T.fgFaint, children: field.hint ?? "" }),
929
+ focused && !valStr && /* @__PURE__ */ jsx9(Text8, { backgroundColor: accent, color: T.bg, children: " " })
919
930
  ]
920
931
  }
921
932
  )
922
933
  ] }),
923
934
  focused && /* @__PURE__ */ jsx9(Box9, { marginLeft: 30, children: error ? /* @__PURE__ */ jsx9(Text8, { color: T.red, children: error }) : isPath ? /* @__PURE__ */ jsxs9(Text8, { color: T.fgFaint, children: [
935
+ "\u2190/\u2192 \uCEE4\uC11C \uC774\uB3D9 \xB7 ",
924
936
  /* @__PURE__ */ jsx9(Text8, { color: T.amber, children: "\u21B5" }),
925
937
  " ",
926
938
  field.pathType === "file" ? "\uD30C\uC77C" : "\uB514\uB809\uD1A0\uB9AC",
927
939
  " \uC120\uD0DD\uCC3D \uC5F4\uAE30",
928
940
  field.hint ? ` \xB7 ${field.hint}` : ""
929
- ] }) : field.hint ? /* @__PURE__ */ jsx9(Text8, { color: T.fgFaint, children: field.hint }) : null })
941
+ ] }) : field.hint ? /* @__PURE__ */ jsxs9(Text8, { color: T.fgFaint, children: [
942
+ "\u2190/\u2192 \uCEE4\uC11C \uC774\uB3D9 \xB7 ",
943
+ field.hint
944
+ ] }) : null })
930
945
  ] });
931
946
  }
932
947
 
@@ -935,10 +950,13 @@ function parseConfluenceUrl(input) {
935
950
  const url = input.trim();
936
951
  if (!url) return {};
937
952
  const result = {};
938
- const spacesMatch = url.match(/\/spaces\/([^/]+)\/pages\/(\d+)/);
953
+ const spacesMatch = url.match(/\/spaces\/([^/]+)\/pages\/(\d+)(?:\/([^?#]+))?/);
939
954
  if (spacesMatch) {
940
955
  result.space = decodeURIComponent(spacesMatch[1]);
941
956
  result.pageId = spacesMatch[2];
957
+ if (spacesMatch[3]) {
958
+ result.title = decodeConfluenceTitle(spacesMatch[3]);
959
+ }
942
960
  return result;
943
961
  }
944
962
  const pageIdQuery = url.match(/[?&]pageId=(\d+)/);
@@ -960,12 +978,30 @@ function applyUrlFill(commandKey, parsed, current) {
960
978
  if (commandKey === "confluence:page:get" || commandKey === "confluence:page:update") {
961
979
  if (parsed.space) result["space"] = parsed.space;
962
980
  if (parsed.pageId) result["pageId"] = parsed.pageId;
981
+ if (commandKey === "confluence:page:get" && parsed.title) {
982
+ result["output"] = buildOutputPathWithTitle(current["output"], parsed.title);
983
+ }
963
984
  } else if (commandKey === "confluence:page:create") {
964
985
  if (parsed.space) result["space"] = parsed.space;
965
986
  if (parsed.pageId) result["parent"] = parsed.pageId;
966
987
  }
967
988
  return result;
968
989
  }
990
+ function decodeConfluenceTitle(value) {
991
+ return decodeURIComponent(value).replace(/\+/g, " ");
992
+ }
993
+ function sanitizeFilename(value) {
994
+ return value.replace(/[\\/:*?"<>|]/g, "-").trim() || "confluence-page";
995
+ }
996
+ function buildOutputPathWithTitle(currentOutput, title) {
997
+ const filename = `${sanitizeFilename(title)}.md`;
998
+ const current = typeof currentOutput === "string" ? currentOutput.trim() : "";
999
+ if (!current) return filename;
1000
+ if (current.endsWith("/")) return `${current}${filename}`;
1001
+ const slashIndex = current.lastIndexOf("/");
1002
+ if (slashIndex < 0) return filename;
1003
+ return `${current.slice(0, slashIndex + 1)}${filename}`;
1004
+ }
969
1005
  function supportsUrlFill(commandKey) {
970
1006
  return [
971
1007
  "confluence:page:get",
@@ -975,10 +1011,46 @@ function supportsUrlFill(commandKey) {
975
1011
  }
976
1012
  function getUrlFillTargets(commandKey) {
977
1013
  if (commandKey === "confluence:page:create") return ["space", "parent"];
978
- if (commandKey === "confluence:page:get" || commandKey === "confluence:page:update") return ["space", "pageId"];
1014
+ if (commandKey === "confluence:page:get") return ["space", "pageId", "output"];
1015
+ if (commandKey === "confluence:page:update") return ["space", "pageId"];
979
1016
  return [];
980
1017
  }
981
1018
 
1019
+ // tools/tui/text-edit.ts
1020
+ function clampCursor(cursor, value) {
1021
+ return Math.max(0, Math.min(cursor, value.length));
1022
+ }
1023
+ function applyTextEdit(state, action) {
1024
+ const cursor = clampCursor(state.cursor, state.value);
1025
+ if (action.type === "moveLeft") {
1026
+ return { value: state.value, cursor: clampCursor(cursor - 1, state.value) };
1027
+ }
1028
+ if (action.type === "moveRight") {
1029
+ return { value: state.value, cursor: clampCursor(cursor + 1, state.value) };
1030
+ }
1031
+ if (action.type === "moveHome") {
1032
+ return { value: state.value, cursor: 0 };
1033
+ }
1034
+ if (action.type === "moveEnd") {
1035
+ return { value: state.value, cursor: state.value.length };
1036
+ }
1037
+ if (action.type === "insert") {
1038
+ const value = `${state.value.slice(0, cursor)}${action.text}${state.value.slice(cursor)}`;
1039
+ return { value, cursor: cursor + action.text.length };
1040
+ }
1041
+ if (action.type === "backspace") {
1042
+ if (cursor === 0) return { value: state.value, cursor };
1043
+ const value = `${state.value.slice(0, cursor - 1)}${state.value.slice(cursor)}`;
1044
+ return { value, cursor: cursor - 1 };
1045
+ }
1046
+ if (action.type === "delete") {
1047
+ if (cursor >= state.value.length) return { value: state.value, cursor };
1048
+ const value = `${state.value.slice(0, cursor)}${state.value.slice(cursor + 1)}`;
1049
+ return { value, cursor };
1050
+ }
1051
+ return { value: state.value, cursor };
1052
+ }
1053
+
982
1054
  // tools/tui/screens/FormScreen.tsx
983
1055
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
984
1056
  var URL_FILL_COLOR = T.blue;
@@ -989,6 +1061,7 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
989
1061
  const [focusIdx, setFocusIdx] = useState2(state.focusedField);
990
1062
  const [pickerOpen, setPickerOpen] = useState2(false);
991
1063
  const [urlValue, setUrlValue] = useState2("");
1064
+ const [cursorByField, setCursorByField] = useState2({});
992
1065
  if (!def) return /* @__PURE__ */ jsxs10(Text9, { color: T.red, children: [
993
1066
  "\uCEE4\uB9E8\uB4DC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ",
994
1067
  state.commandKey
@@ -999,11 +1072,19 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
999
1072
  const onUrlField = hasUrlFill && focusIdx === -1;
1000
1073
  const currentField = onUrlField ? void 0 : fields[focusIdx];
1001
1074
  const preview = buildPreview(def, values);
1075
+ function getFieldCursor(fieldKey, fieldValue) {
1076
+ const value = String(fieldValue ?? "");
1077
+ return clampCursor(cursorByField[fieldKey] ?? value.length, value);
1078
+ }
1002
1079
  function updateUrl(next) {
1003
1080
  setUrlValue(next);
1004
1081
  const parsed = parseConfluenceUrl(next);
1005
1082
  if (parsed.space || parsed.pageId) {
1006
- setValues((v) => applyUrlFill(state.commandKey, parsed, v));
1083
+ const filled = applyUrlFill(state.commandKey, parsed, values);
1084
+ setValues(filled);
1085
+ if (typeof filled.output === "string") {
1086
+ setCursorByField((c) => ({ ...c, output: String(filled.output).length }));
1087
+ }
1007
1088
  setErrors({});
1008
1089
  }
1009
1090
  }
@@ -1071,6 +1152,17 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1071
1152
  nextField();
1072
1153
  return;
1073
1154
  }
1155
+ if (currentField?.type === "text" && (key.leftArrow || key.rightArrow)) {
1156
+ const keyName = currentField.key;
1157
+ const value = String(values[keyName] ?? "");
1158
+ const cursor = getFieldCursor(keyName, value);
1159
+ const next = applyTextEdit(
1160
+ { value, cursor },
1161
+ { type: key.leftArrow ? "moveLeft" : "moveRight" }
1162
+ );
1163
+ setCursorByField((c) => ({ ...c, [keyName]: next.cursor }));
1164
+ return;
1165
+ }
1074
1166
  if (onUrlField) {
1075
1167
  if (key.backspace || key.delete) {
1076
1168
  updateUrl(urlValue.slice(0, -1));
@@ -1111,10 +1203,15 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1111
1203
  return;
1112
1204
  }
1113
1205
  if (key.backspace || key.delete) {
1114
- setValues((v) => ({
1115
- ...v,
1116
- [currentField.key]: String(v[currentField.key] ?? "").slice(0, -1)
1117
- }));
1206
+ const keyName = currentField.key;
1207
+ const value = String(values[keyName] ?? "");
1208
+ const cursor = getFieldCursor(keyName, value);
1209
+ const next = applyTextEdit(
1210
+ { value, cursor },
1211
+ { type: key.backspace ? "backspace" : "delete" }
1212
+ );
1213
+ setValues((v) => ({ ...v, [keyName]: next.value }));
1214
+ setCursorByField((c) => ({ ...c, [keyName]: next.cursor }));
1118
1215
  setErrors((e) => {
1119
1216
  const n = { ...e };
1120
1217
  delete n[currentField.key];
@@ -1123,10 +1220,12 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1123
1220
  return;
1124
1221
  }
1125
1222
  if (input && !key.ctrl && !key.meta) {
1126
- setValues((v) => ({
1127
- ...v,
1128
- [currentField.key]: String(v[currentField.key] ?? "") + input
1129
- }));
1223
+ const keyName = currentField.key;
1224
+ const value = String(values[keyName] ?? "");
1225
+ const cursor = getFieldCursor(keyName, value);
1226
+ const next = applyTextEdit({ value, cursor }, { type: "insert", text: input });
1227
+ setValues((v) => ({ ...v, [keyName]: next.value }));
1228
+ setCursorByField((c) => ({ ...c, [keyName]: next.cursor }));
1130
1229
  setErrors((e) => {
1131
1230
  const n = { ...e };
1132
1231
  delete n[currentField.key];
@@ -1165,6 +1264,7 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1165
1264
  accent,
1166
1265
  onSelect: (selected) => {
1167
1266
  setValues((v) => ({ ...v, [currentField.key]: selected }));
1267
+ setCursorByField((c) => ({ ...c, [currentField.key]: selected.length }));
1168
1268
  setErrors((e) => {
1169
1269
  const n = { ...e };
1170
1270
  delete n[currentField.key];
@@ -1206,7 +1306,7 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1206
1306
  ] })
1207
1307
  ] }),
1208
1308
  /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
1209
- urlValue ? /* @__PURE__ */ jsx10(Text9, { color: T.fg, children: urlValue }) : /* @__PURE__ */ jsx10(Text9, { color: T.fgFaint, children: "https://confluence.tde.sktelecom.com/spaces/TDE/pages/123456/..." }),
1309
+ urlValue ? /* @__PURE__ */ jsx10(Text9, { color: T.fg, children: urlValue }) : /* @__PURE__ */ jsx10(Text9, { color: T.fgFaint, children: "https://confluence.tde.example.com/spaces/TDE/pages/123456/..." }),
1210
1310
  onUrlField && /* @__PURE__ */ jsx10(Text9, { backgroundColor: URL_FILL_COLOR, color: T.bg, children: " " })
1211
1311
  ] })
1212
1312
  ]
@@ -1218,6 +1318,7 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1218
1318
  field: f,
1219
1319
  value: values[f.key] ?? "",
1220
1320
  focused: i === focusIdx,
1321
+ cursor: getFieldCursor(f.key, values[f.key]),
1221
1322
  error: errors[f.key],
1222
1323
  accent
1223
1324
  },
@@ -1262,6 +1363,7 @@ function FormScreen({ state, onRun, onBack, onSavePreset, accent = DEFAULT_ACCEN
1262
1363
  ] }) : /* @__PURE__ */ jsx10(Keymap, { accent, keys: [
1263
1364
  { key: "Tab", label: "next field" },
1264
1365
  { key: "\u21E7Tab", label: "prev" },
1366
+ { key: "\u2190/\u2192", label: "cursor" },
1265
1367
  { key: "A-Z", label: "\uBC14\uB85C \uC785\uB825" },
1266
1368
  { key: "\u21B5", label: currentField?.pathType ? "\uACBD\uB85C \uC120\uD0DD\uCC3D" : currentField?.type === "bool" ? "toggle" : "\u2014" },
1267
1369
  { key: "Ctrl+R", label: "Run" },
@@ -1598,9 +1700,11 @@ function MenuScreen({
1598
1700
  );
1599
1701
  }
1600
1702
  function WelcomeView({ accent }) {
1703
+ const diagnostics = getRuntimeConfigDiagnostics();
1601
1704
  return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", gap: 1, paddingY: 1, children: [
1602
1705
  /* @__PURE__ */ jsx13(Text12, { color: accent, bold: true, children: "tdecollab TUI" }),
1603
1706
  /* @__PURE__ */ jsx13(Text12, { color: T.fgDim, children: "\uC88C\uCE21 \uBA54\uB274\uC5D0\uC11C \uBA85\uB839\uC744 \uC120\uD0DD\uD558\uC138\uC694." }),
1707
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, flexDirection: "column", gap: 0, children: diagnostics.map((line, index) => /* @__PURE__ */ jsx13(Text12, { color: index === 0 ? accent : T.fgDim, children: line }, index)) }),
1604
1708
  /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", gap: 0, children: [
1605
1709
  /* @__PURE__ */ jsx13(Text12, { color: T.fgFaint, children: " Confluence \u25C6 \uD398\uC774\uC9C0/\uC2A4\uD398\uC774\uC2A4/\uAC80\uC0C9" }),
1606
1710
  /* @__PURE__ */ jsx13(Text12, { color: T.fgFaint, children: " JIRA \u25C6 \uC774\uC288/\uAC80\uC0C9" }),
@@ -1941,6 +2045,13 @@ function appendHistory(entry) {
1941
2045
  // tools/tui/executor/confluence.ts
1942
2046
  import fs4 from "fs";
1943
2047
  import path4 from "path";
2048
+
2049
+ // tools/confluence/utils/version.ts
2050
+ function resolveCurrentVersionForUpdate(version) {
2051
+ return version ?? 1;
2052
+ }
2053
+
2054
+ // tools/tui/executor/confluence.ts
1944
2055
  function makeStep(id, title, detail = "") {
1945
2056
  return { id, title, detail, state: "pending" };
1946
2057
  }
@@ -2180,7 +2291,7 @@ async function executePageUpdate(values, onSteps, onLog) {
2180
2291
  id: String(values["pageId"]),
2181
2292
  title: values["title"] ? String(values["title"]) : current.title,
2182
2293
  body: storageXml,
2183
- version: (current.version?.number ?? 1) + 1
2294
+ version: resolveCurrentVersionForUpdate(current.version?.number)
2184
2295
  });
2185
2296
  addLog(makeLog("ok", `\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC: "${updated.title}" v${updated.version?.number}`));
2186
2297
  steps[3] = { ...steps[3], state: "done", detail: `v${updated.version?.number}` };
@@ -2501,7 +2612,10 @@ var INITIAL_STATE = {
2501
2612
  resultList: [],
2502
2613
  resultListCols: [],
2503
2614
  resultListSelected: 0,
2504
- logs: [makeLog("info", "session \uC2DC\uC791 \xB7 tdecollab TUI v0.2.3")],
2615
+ logs: [
2616
+ makeLog("info", "session \uC2DC\uC791 \xB7 tdecollab TUI v0.2.3"),
2617
+ ...getRuntimeConfigDiagnostics().map((line) => makeLog("dim", line))
2618
+ ],
2505
2619
  history: loadHistory(),
2506
2620
  historySelected: 0
2507
2621
  };