mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(app): prevent comment action clipping
This commit is contained in:
@@ -64,7 +64,7 @@ for (const direction of ["ltr", "rtl"]) {
|
||||
const box = await action.boundingBox()
|
||||
const code = root.locator("[data-code]").first()
|
||||
const gutterRight = await code.evaluate((element) => element.firstElementChild?.getBoundingClientRect().right)
|
||||
expect((box?.x ?? 0) - (gutterRight ?? 0)).toBe(8)
|
||||
expect((box?.x ?? 0) - (gutterRight ?? 0)).toBeCloseTo(8, 0)
|
||||
expect(box?.x).toBeGreaterThanOrEqual(0)
|
||||
expect((box?.x ?? 0) + (box?.width ?? 0)).toBeLessThanOrEqual(
|
||||
await page.evaluate(() => document.documentElement.clientWidth),
|
||||
@@ -115,7 +115,7 @@ for (const direction of ["up", "down"] as const) {
|
||||
await expect
|
||||
.poll(() =>
|
||||
action.evaluate((button) => {
|
||||
const host = button.closest('[data-component="file"]')?.querySelector("diffs-container")
|
||||
const host = document.querySelector('[data-component="file"]')?.querySelector("diffs-container")
|
||||
const root = host?.shadowRoot
|
||||
if (!root) return NaN
|
||||
const selection =
|
||||
@@ -149,6 +149,24 @@ story("leaves a review code click as regular text interaction", async ({ mount }
|
||||
await expect(root.getByRole("textbox")).not.toBeVisible()
|
||||
})
|
||||
|
||||
story("keeps the text selection action visible past the end of a file", async ({ mount, page }) => {
|
||||
const root = await mount("components-session-review--interactive-file-comments")
|
||||
const action = page.getByRole("button", { name: "Add comment", exact: true })
|
||||
await expect(async () => {
|
||||
await root.getByText("export const last = 2", { exact: true }).selectText()
|
||||
await expect(action).toBeVisible()
|
||||
}).toPass()
|
||||
|
||||
const file = root.locator('[data-component="file"]')
|
||||
await expect(action).toHaveCSS("position", "fixed")
|
||||
expect(await action.evaluate((element) => element.closest('[data-component="file"]'))).toBeNull()
|
||||
const actionBox = await action.boundingBox()
|
||||
const fileBox = await file.boundingBox()
|
||||
expect((actionBox?.y ?? 0) + (actionBox?.height ?? 0)).toBeGreaterThan(
|
||||
(fileBox?.y ?? 0) + (fileBox?.height ?? 0),
|
||||
)
|
||||
})
|
||||
|
||||
story("keeps direct line-number range comments in the review panel", async ({ mount }) => {
|
||||
const root = await mount("components-session-review--interactive-comments-panel")
|
||||
await root.locator('[data-column-number="1"]').dragTo(root.locator('[data-column-number="3"]'))
|
||||
|
||||
@@ -21,6 +21,7 @@ import { type PreloadFileDiffResult, type PreloadMultiFileDiffResult } from "@pi
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||
import { Portal } from "solid-js/web"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { createDefaultOptions, styleVariables } from "../pierre"
|
||||
import { markCommentedDiffLines, markCommentedFileLines } from "../pierre/commented-lines"
|
||||
@@ -252,8 +253,7 @@ function useFileViewer(config: ViewerConfig) {
|
||||
const roomAbove = rect.top - TEXT_SELECTION_ACTION_HEIGHT - TEXT_SELECTION_ACTION_GAP >= 0
|
||||
const preferBelow = selected.direction !== "up"
|
||||
const below = preferBelow ? roomBelow || !roomAbove : !roomAbove && roomBelow
|
||||
const gutterEdge =
|
||||
(selected.gutterRight ?? wrapper.getBoundingClientRect().left) - wrapper.getBoundingClientRect().left
|
||||
const gutterEdge = selected.gutterRight ?? wrapper.getBoundingClientRect().left
|
||||
setTextSelection({ range: selected.range, rect, label: action.label, below, gutterEdge })
|
||||
}
|
||||
|
||||
@@ -839,36 +839,34 @@ function ViewerShell(props: {
|
||||
<div ref={(el) => (props.viewer.overlay = el)} class="pointer-events-none absolute inset-0 z-0" />
|
||||
<Show when={props.viewer.textSelection()}>
|
||||
{(selection) => (
|
||||
<Button
|
||||
data-slot="file-text-selection-action"
|
||||
data-placement={selection().below ? "bottom" : "top"}
|
||||
size="small"
|
||||
variant="submit"
|
||||
class="z-[110] whitespace-nowrap motion-safe:transition-transform duration-100 ease-out motion-reduce:transition-none"
|
||||
style={{
|
||||
position: "absolute",
|
||||
"--line-comment-gutter-edge": `${selection().gutterEdge}px`,
|
||||
left: `calc(var(--line-comment-gutter-edge) + ${LINE_COMMENT_ACTION_GAP}px)`,
|
||||
top: `${
|
||||
(selection().below ? selection().rect.bottom : selection().rect.top) -
|
||||
props.viewer.wrapper.getBoundingClientRect().top
|
||||
}px`,
|
||||
transform: selection().below
|
||||
? `translateY(${TEXT_SELECTION_ACTION_GAP}px)`
|
||||
: `translateY(calc(-100% - ${TEXT_SELECTION_ACTION_GAP}px))`,
|
||||
}}
|
||||
onPointerDown={(event: PointerEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onMouseDown={(event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={props.viewer.activateTextSelection}
|
||||
>
|
||||
{selection().label}
|
||||
</Button>
|
||||
<Portal>
|
||||
<Button
|
||||
data-slot="file-text-selection-action"
|
||||
data-placement={selection().below ? "bottom" : "top"}
|
||||
size="small"
|
||||
variant="submit"
|
||||
class="z-[110] whitespace-nowrap motion-safe:transition-transform duration-100 ease-out motion-reduce:transition-none"
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: `${selection().gutterEdge + LINE_COMMENT_ACTION_GAP}px`,
|
||||
top: `${selection().below ? selection().rect.bottom : selection().rect.top}px`,
|
||||
transform: selection().below
|
||||
? `translateY(${TEXT_SELECTION_ACTION_GAP}px)`
|
||||
: `translateY(calc(-100% - ${TEXT_SELECTION_ACTION_GAP}px))`,
|
||||
}}
|
||||
onPointerDown={(event: PointerEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onMouseDown={(event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={props.viewer.activateTextSelection}
|
||||
>
|
||||
{selection().label}
|
||||
</Button>
|
||||
</Portal>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -111,6 +111,23 @@ function InteractiveCommentsV2Story() {
|
||||
|
||||
export const InteractiveCommentsPanel = { render: () => <InteractiveCommentsV2Story /> }
|
||||
|
||||
export const InteractiveFileComments = {
|
||||
render: () => (
|
||||
<CurrentSessionProviders document={editThenTestDocument}>
|
||||
<div class="h-screen overflow-auto bg-background-base">
|
||||
<div class="relative overflow-hidden pb-40">
|
||||
<File
|
||||
mode="text"
|
||||
file={{ name: "review.ts", contents: "export const first = 1\nexport const last = 2" }}
|
||||
enableLineSelection
|
||||
textSelectionAction={{ label: "Add comment" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CurrentSessionProviders>
|
||||
),
|
||||
}
|
||||
|
||||
const gitDiffs = [
|
||||
{
|
||||
// OpenCode 93e1f383dd79683af4fc5ad139cea0516603c838, unchanged git-show output.
|
||||
|
||||
Reference in New Issue
Block a user