@kitlangton/ghui 0.1.21 → 0.2.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/src/ui/modals.tsx CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Data } from "effect"
2
- import { formatShortDate, formatTimestamp } from "../date.js"
3
2
  import type { PullRequestLabel, PullRequestMergeInfo, PullRequestReviewComment } from "../domain.js"
4
3
  import { availableMergeActions } from "../mergeActions.js"
5
4
  import { clampCursor, commentEditorLines, cursorLineIndexForLines } from "./commentEditor.js"
6
5
  import { colors, filterThemeDefinitions, themeDefinitions, type ThemeId } from "./colors.js"
6
+ import { commentDisplayRows, CommentSegmentsLine, type CommentDisplayLine } from "./comments.js"
7
7
  import { centerCell, Filler, fitCell, HintRow, PlainLine, StandardModal, standardModalDims, TextLine } from "./primitives.js"
8
8
  import { labelColor, shortRepoName } from "./pullRequests.js"
9
9
 
@@ -484,41 +484,8 @@ export const CommentModal = ({
484
484
  )
485
485
  }
486
486
 
487
- type CommentThreadRow = {
488
- readonly key: string
489
- readonly text: string
490
- readonly fg: string
491
- readonly bold?: boolean
492
- }
493
-
494
- const wrapCommentBody = (body: string, width: number) => {
495
- const lines = body.length === 0 ? [""] : body.split("\n")
496
- return lines.flatMap((line) => {
497
- if (line.length === 0) return [""]
498
- const wrapped: string[] = []
499
- for (let index = 0; index < line.length; index += width) {
500
- wrapped.push(line.slice(index, index + width))
501
- }
502
- return wrapped
503
- })
504
- }
505
-
506
- const formatCommentDate = (date: Date | null) => date ? `${formatShortDate(date)} ${formatTimestamp(date)}` : ""
507
-
508
- const commentThreadRows = (comments: readonly PullRequestReviewComment[], width: number): readonly CommentThreadRow[] =>
509
- comments.flatMap((comment, commentIndex) => {
510
- const timestamp = formatCommentDate(comment.createdAt)
511
- const header = timestamp ? `${comment.author} ${timestamp}` : comment.author
512
- return [
513
- { key: `${comment.id}:header`, text: header, fg: colors.count, bold: true },
514
- ...wrapCommentBody(comment.body, width).map((line, lineIndex) => ({
515
- key: `${comment.id}:body:${lineIndex}`,
516
- text: line,
517
- fg: colors.text,
518
- })),
519
- ...(commentIndex < comments.length - 1 ? [{ key: `${comment.id}:gap`, text: "", fg: colors.muted }] : []),
520
- ]
521
- })
487
+ const commentThreadRows = (comments: readonly PullRequestReviewComment[], width: number): readonly CommentDisplayLine[] =>
488
+ comments.flatMap((comment) => commentDisplayRows({ item: comment, width }))
522
489
 
523
490
  export const CommentThreadModal = ({
524
491
  state,
@@ -559,9 +526,7 @@ export const CommentThreadModal = ({
559
526
  >
560
527
  {visibleRows.length === 0 ? (
561
528
  <PlainLine text={fitCell("No comments on this line.", contentWidth)} fg={colors.muted} />
562
- ) : visibleRows.map((row) => (
563
- <PlainLine key={row.key} text={fitCell(row.text, contentWidth)} fg={row.fg} bold={row.bold ?? false} />
564
- ))}
529
+ ) : visibleRows.map((row) => <CommentSegmentsLine key={row.key} segments={row.segments} />)}
565
530
  </StandardModal>
566
531
  )
567
532
  }
@@ -167,6 +167,7 @@ export const ModalFrame = ({
167
167
  width,
168
168
  height,
169
169
  junctionRows = [],
170
+ topJunctionColumns = [],
170
171
  backgroundColor = colors.modalBackground,
171
172
  }: {
172
173
  children: React.ReactNode
@@ -175,15 +176,18 @@ export const ModalFrame = ({
175
176
  width: number
176
177
  height: number
177
178
  junctionRows?: readonly number[]
179
+ topJunctionColumns?: readonly number[]
178
180
  backgroundColor?: string
179
181
  }) => {
180
182
  const innerWidth = Math.max(1, width - 2)
181
183
  const innerHeight = Math.max(1, height - 2)
182
184
  const junctions = new Set(junctionRows)
185
+ const topJunctions = new Set(topJunctionColumns)
186
+ const topBorder = Array.from({ length: innerWidth }, (_, index) => topJunctions.has(index) ? "┬" : "─").join("")
183
187
 
184
188
  return (
185
189
  <box position="absolute" left={left} top={top} width={width} height={height} flexDirection="column" backgroundColor={backgroundColor}>
186
- <PlainLine text={`┌${"─".repeat(innerWidth)}┐`} fg={colors.separator} />
190
+ <PlainLine text={`┌${topBorder}┐`} fg={colors.separator} />
187
191
  <box height={innerHeight} flexDirection="row">
188
192
  <box width={1} height={innerHeight} flexDirection="column">
189
193
  {Array.from({ length: innerHeight }, (_, index) => <PlainLine key={index} text={junctions.has(index) ? "├" : "│"} fg={colors.separator} />)}
@@ -11,7 +11,8 @@ export const singleLineText = (text: string) => text.replace(/[\r\n]+/g, " ")
11
11
 
12
12
  export const printableKeyText = (key: Pick<SingleLineInputKey, "ctrl" | "meta" | "sequence">) => {
13
13
  if (key.ctrl || key.meta || key.sequence.length === 0) return null
14
- return /^[^\x00-\x1f\x7f]+$/.test(key.sequence) ? key.sequence : null
14
+ // oxlint-disable-next-line no-control-regex -- intentional: skip control characters
15
+ return /^[^\u0000-\u001f\u007f]+$/.test(key.sequence) ? key.sequence : null
15
16
  }
16
17
 
17
18
  export const editSingleLineInput = (value: string, key: SingleLineInputKey) => {
@@ -0,0 +1 @@
1
+ export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;