wip(app): line selection
This commit is contained in:
@@ -15,7 +15,7 @@ import {
|
|||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { createFocusSignal } from "@solid-primitives/active-element"
|
import { createFocusSignal } from "@solid-primitives/active-element"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
import { selectionFromLines, useFile, type FileSelection } from "@/context/file"
|
import { useFile, type FileSelection } from "@/context/file"
|
||||||
import {
|
import {
|
||||||
ContentPart,
|
ContentPart,
|
||||||
DEFAULT_PROMPT,
|
DEFAULT_PROMPT,
|
||||||
@@ -161,18 +161,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
const tabs = createMemo(() => layout.tabs(sessionKey()))
|
||||||
const view = createMemo(() => layout.view(sessionKey()))
|
const view = createMemo(() => layout.view(sessionKey()))
|
||||||
const activeFile = createMemo(() => {
|
const recent = createMemo(() => {
|
||||||
const tab = tabs().active()
|
const all = tabs().all()
|
||||||
if (!tab) return
|
const active = tabs().active()
|
||||||
return files.pathFromTab(tab)
|
const order = active ? [active, ...all.filter((x) => x !== active)] : all
|
||||||
})
|
const seen = new Set<string>()
|
||||||
|
const paths: string[] = []
|
||||||
|
|
||||||
const activeFileSelection = createMemo(() => {
|
for (const tab of order) {
|
||||||
const path = activeFile()
|
const path = files.pathFromTab(tab)
|
||||||
if (!path) return
|
if (!path) continue
|
||||||
const range = files.selectedLines(path)
|
if (seen.has(path)) continue
|
||||||
if (!range) return
|
seen.add(path)
|
||||||
return selectionFromLines(range)
|
paths.push(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
})
|
})
|
||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
||||||
const status = createMemo(
|
const status = createMemo(
|
||||||
@@ -393,7 +397,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
if (!isFocused()) setComposing(false)
|
if (!isFocused()) setComposing(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
type AtOption = { type: "agent"; name: string; display: string } | { type: "file"; path: string; display: string }
|
type AtOption =
|
||||||
|
| { type: "agent"; name: string; display: string }
|
||||||
|
| { type: "file"; path: string; display: string; recent?: boolean }
|
||||||
|
|
||||||
const agentList = createMemo(() =>
|
const agentList = createMemo(() =>
|
||||||
sync.data.agent
|
sync.data.agent
|
||||||
@@ -424,12 +430,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
} = useFilteredList<AtOption>({
|
} = useFilteredList<AtOption>({
|
||||||
items: async (query) => {
|
items: async (query) => {
|
||||||
const agents = agentList()
|
const agents = agentList()
|
||||||
|
const open = recent()
|
||||||
|
const seen = new Set(open)
|
||||||
|
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||||
const paths = await files.searchFilesAndDirectories(query)
|
const paths = await files.searchFilesAndDirectories(query)
|
||||||
const fileOptions: AtOption[] = paths.map((path) => ({ type: "file", path, display: path }))
|
const fileOptions: AtOption[] = paths
|
||||||
return [...agents, ...fileOptions]
|
.filter((path) => !seen.has(path))
|
||||||
|
.map((path) => ({ type: "file", path, display: path }))
|
||||||
|
return [...agents, ...pinned, ...fileOptions]
|
||||||
},
|
},
|
||||||
key: atKey,
|
key: atKey,
|
||||||
filterKeys: ["display"],
|
filterKeys: ["display"],
|
||||||
|
groupBy: (item) => {
|
||||||
|
if (item.type === "agent") return "agent"
|
||||||
|
if (item.recent) return "recent"
|
||||||
|
return "file"
|
||||||
|
},
|
||||||
|
sortGroupsBy: (a, b) => {
|
||||||
|
const rank = (category: string) => {
|
||||||
|
if (category === "agent") return 0
|
||||||
|
if (category === "recent") return 1
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return rank(a.category) - rank(b.category)
|
||||||
|
},
|
||||||
onSelect: handleAtSelect,
|
onSelect: handleAtSelect,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1242,37 +1266,67 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
const usedUrls = new Set(fileAttachmentParts.map((part) => part.url))
|
const usedUrls = new Set(fileAttachmentParts.map((part) => part.url))
|
||||||
|
|
||||||
const contextFileParts: Array<{
|
const context = prompt.context.items().slice()
|
||||||
id: string
|
|
||||||
type: "file"
|
|
||||||
mime: string
|
|
||||||
url: string
|
|
||||||
filename?: string
|
|
||||||
}> = []
|
|
||||||
|
|
||||||
const addContextFile = (path: string, selection?: FileSelection) => {
|
const commentItems = context.filter((item) => item.type === "file" && !!item.comment?.trim())
|
||||||
const absolute = toAbsolutePath(path)
|
|
||||||
const query = selection ? `?start=${selection.startLine}&end=${selection.endLine}` : ""
|
const contextParts: Array<
|
||||||
|
| {
|
||||||
|
id: string
|
||||||
|
type: "text"
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
id: string
|
||||||
|
type: "file"
|
||||||
|
mime: string
|
||||||
|
url: string
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
> = []
|
||||||
|
|
||||||
|
const commentNote = (path: string, selection: FileSelection | undefined, comment: string) => {
|
||||||
|
const start = selection ? Math.min(selection.startLine, selection.endLine) : undefined
|
||||||
|
const end = selection ? Math.max(selection.startLine, selection.endLine) : undefined
|
||||||
|
const range =
|
||||||
|
start === undefined || end === undefined
|
||||||
|
? "this file"
|
||||||
|
: start === end
|
||||||
|
? `line ${start}`
|
||||||
|
: `lines ${start} through ${end}`
|
||||||
|
|
||||||
|
return `The user made the following comment regarding ${range} of ${path}: ${comment}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const addContextFile = (input: { path: string; selection?: FileSelection; comment?: string }) => {
|
||||||
|
const absolute = toAbsolutePath(input.path)
|
||||||
|
const query = input.selection ? `?start=${input.selection.startLine}&end=${input.selection.endLine}` : ""
|
||||||
const url = `file://${absolute}${query}`
|
const url = `file://${absolute}${query}`
|
||||||
if (usedUrls.has(url)) return
|
|
||||||
|
const comment = input.comment?.trim()
|
||||||
|
if (!comment && usedUrls.has(url)) return
|
||||||
usedUrls.add(url)
|
usedUrls.add(url)
|
||||||
contextFileParts.push({
|
|
||||||
|
if (comment) {
|
||||||
|
contextParts.push({
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
type: "text",
|
||||||
|
text: commentNote(input.path, input.selection, comment),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
contextParts.push({
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
type: "file",
|
type: "file",
|
||||||
mime: "text/plain",
|
mime: "text/plain",
|
||||||
url,
|
url,
|
||||||
filename: getFilename(path),
|
filename: getFilename(input.path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const activePath = activeFile()
|
for (const item of context) {
|
||||||
if (activePath && prompt.context.activeTab()) {
|
|
||||||
addContextFile(activePath, activeFileSelection())
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of prompt.context.items()) {
|
|
||||||
if (item.type !== "file") continue
|
if (item.type !== "file") continue
|
||||||
addContextFile(item.path, item.selection)
|
addContextFile({ path: item.path, selection: item.selection, comment: item.comment })
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageAttachmentParts = images.map((attachment) => ({
|
const imageAttachmentParts = images.map((attachment) => ({
|
||||||
@@ -1292,7 +1346,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const requestParts = [
|
const requestParts = [
|
||||||
textPart,
|
textPart,
|
||||||
...fileAttachmentParts,
|
...fileAttachmentParts,
|
||||||
...contextFileParts,
|
...contextParts,
|
||||||
...agentAttachmentParts,
|
...agentAttachmentParts,
|
||||||
...imageAttachmentParts,
|
...imageAttachmentParts,
|
||||||
]
|
]
|
||||||
@@ -1345,6 +1399,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of commentItems) {
|
||||||
|
prompt.context.remove(item.key)
|
||||||
|
}
|
||||||
|
|
||||||
clearInput()
|
clearInput()
|
||||||
addOptimisticMessage()
|
addOptimisticMessage()
|
||||||
|
|
||||||
@@ -1363,6 +1421,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
description: errorMessage(err),
|
description: errorMessage(err),
|
||||||
})
|
})
|
||||||
removeOptimisticMessage()
|
removeOptimisticMessage()
|
||||||
|
for (const item of commentItems) {
|
||||||
|
prompt.context.add({
|
||||||
|
type: "file",
|
||||||
|
path: item.path,
|
||||||
|
selection: item.selection,
|
||||||
|
comment: item.comment,
|
||||||
|
commentID: item.commentID,
|
||||||
|
preview: item.preview,
|
||||||
|
})
|
||||||
|
}
|
||||||
restoreInput()
|
restoreInput()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1487,49 +1555,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={prompt.context.items().length > 0 || !!activeFile()}>
|
<Show when={prompt.context.items().length > 0}>
|
||||||
<div class="flex flex-nowrap items-start gap-1.5 px-3 pt-3 overflow-x-auto no-scrollbar">
|
<div class="flex flex-nowrap items-start gap-1.5 px-3 pt-3 overflow-x-auto no-scrollbar">
|
||||||
<Show when={prompt.context.activeTab() ? activeFile() : undefined}>
|
|
||||||
{(path) => (
|
|
||||||
<div class="shrink-0 flex flex-col gap-1 rounded-md bg-surface-base border border-border-base px-2 py-1 max-w-[320px]">
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<FileIcon node={{ path: path(), type: "file" }} class="shrink-0 size-3.5" />
|
|
||||||
<div class="flex items-center text-11-regular min-w-0">
|
|
||||||
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span>
|
|
||||||
<span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span>
|
|
||||||
<Show when={activeFileSelection()}>
|
|
||||||
{(sel) => (
|
|
||||||
<span class="text-text-weak whitespace-nowrap ml-1">
|
|
||||||
{sel().startLine === sel().endLine
|
|
||||||
? `:${sel().startLine}`
|
|
||||||
: `:${sel().startLine}-${sel().endLine}`}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span>
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
type="button"
|
|
||||||
icon="close"
|
|
||||||
variant="ghost"
|
|
||||||
class="h-5 w-5"
|
|
||||||
onClick={() => prompt.context.removeActive()}
|
|
||||||
aria-label={language.t("prompt.context.removeActiveFile")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<Show when={!prompt.context.activeTab() && !!activeFile()}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="shrink-0 flex items-center gap-1.5 px-1.5 py-0.5 rounded-md bg-surface-base border border-border-base text-11-regular text-text-weak hover:bg-surface-raised-base-hover"
|
|
||||||
onClick={() => prompt.context.addActive()}
|
|
||||||
>
|
|
||||||
<Icon name="plus-small" size="small" />
|
|
||||||
<span>{language.t("prompt.context.includeActiveFile")}</span>
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
<For each={prompt.context.items()}>
|
<For each={prompt.context.items()}>
|
||||||
{(item) => {
|
{(item) => {
|
||||||
const preview = createMemo(() => selectionPreview(item.path, item.selection, item.preview))
|
const preview = createMemo(() => selectionPreview(item.path, item.selection, item.preview))
|
||||||
|
|||||||
@@ -122,14 +122,12 @@ function createPromptSession(dir: string, id: string | undefined) {
|
|||||||
prompt: Prompt
|
prompt: Prompt
|
||||||
cursor?: number
|
cursor?: number
|
||||||
context: {
|
context: {
|
||||||
activeTab: boolean
|
|
||||||
items: (ContextItem & { key: string })[]
|
items: (ContextItem & { key: string })[]
|
||||||
}
|
}
|
||||||
}>({
|
}>({
|
||||||
prompt: clonePrompt(DEFAULT_PROMPT),
|
prompt: clonePrompt(DEFAULT_PROMPT),
|
||||||
cursor: undefined,
|
cursor: undefined,
|
||||||
context: {
|
context: {
|
||||||
activeTab: true,
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -157,14 +155,7 @@ function createPromptSession(dir: string, id: string | undefined) {
|
|||||||
cursor: createMemo(() => store.cursor),
|
cursor: createMemo(() => store.cursor),
|
||||||
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
||||||
context: {
|
context: {
|
||||||
activeTab: createMemo(() => store.context.activeTab),
|
|
||||||
items: createMemo(() => store.context.items),
|
items: createMemo(() => store.context.items),
|
||||||
addActive() {
|
|
||||||
setStore("context", "activeTab", true)
|
|
||||||
},
|
|
||||||
removeActive() {
|
|
||||||
setStore("context", "activeTab", false)
|
|
||||||
},
|
|
||||||
add(item: ContextItem) {
|
add(item: ContextItem) {
|
||||||
const key = keyForItem(item)
|
const key = keyForItem(item)
|
||||||
if (store.context.items.find((x) => x.key === key)) return
|
if (store.context.items.find((x) => x.key === key)) return
|
||||||
@@ -243,10 +234,7 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
|
|||||||
cursor: () => session().cursor(),
|
cursor: () => session().cursor(),
|
||||||
dirty: () => session().dirty(),
|
dirty: () => session().dirty(),
|
||||||
context: {
|
context: {
|
||||||
activeTab: () => session().context.activeTab(),
|
|
||||||
items: () => session().context.items(),
|
items: () => session().context.items(),
|
||||||
addActive: () => session().context.addActive(),
|
|
||||||
removeActive: () => session().context.removeActive(),
|
|
||||||
add: (item: ContextItem) => session().context.add(item),
|
add: (item: ContextItem) => session().context.add(item),
|
||||||
remove: (key: string) => session().context.remove(key),
|
remove: (key: string) => session().context.remove(key),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1810,8 +1810,6 @@ export default function Page() {
|
|||||||
let pending: { x: number; y: number } | undefined
|
let pending: { x: number; y: number } | undefined
|
||||||
let codeScroll: HTMLElement[] = []
|
let codeScroll: HTMLElement[] = []
|
||||||
|
|
||||||
const [selectionPopoverTop, setSelectionPopoverTop] = createSignal<number | undefined>()
|
|
||||||
|
|
||||||
const path = createMemo(() => file.pathFromTab(tab))
|
const path = createMemo(() => file.pathFromTab(tab))
|
||||||
const state = createMemo(() => {
|
const state = createMemo(() => {
|
||||||
const p = path()
|
const p = path()
|
||||||
@@ -1855,17 +1853,6 @@ export default function Page() {
|
|||||||
if (file.ready()) return file.selectedLines(p) ?? null
|
if (file.ready()) return file.selectedLines(p) ?? null
|
||||||
return handoff.files[p] ?? null
|
return handoff.files[p] ?? null
|
||||||
})
|
})
|
||||||
const selection = createMemo(() => {
|
|
||||||
const range = selectedLines()
|
|
||||||
if (!range) return
|
|
||||||
return selectionFromLines(range)
|
|
||||||
})
|
|
||||||
const selectionLabel = createMemo(() => {
|
|
||||||
const sel = selection()
|
|
||||||
if (!sel) return
|
|
||||||
if (sel.startLine === sel.endLine) return `L${sel.startLine}`
|
|
||||||
return `L${sel.startLine}-${sel.endLine}`
|
|
||||||
})
|
|
||||||
|
|
||||||
let wrap: HTMLDivElement | undefined
|
let wrap: HTMLDivElement | undefined
|
||||||
let textarea: HTMLTextAreaElement | undefined
|
let textarea: HTMLTextAreaElement | undefined
|
||||||
@@ -1991,7 +1978,6 @@ export default function Page() {
|
|||||||
commentedLines={commentedLines()}
|
commentedLines={commentedLines()}
|
||||||
onRendered={() => {
|
onRendered={() => {
|
||||||
requestAnimationFrame(restoreScroll)
|
requestAnimationFrame(restoreScroll)
|
||||||
requestAnimationFrame(updateSelectionPopover)
|
|
||||||
requestAnimationFrame(scheduleComments)
|
requestAnimationFrame(scheduleComments)
|
||||||
}}
|
}}
|
||||||
onLineSelected={(range: SelectedLineRange | null) => {
|
onLineSelected={(range: SelectedLineRange | null) => {
|
||||||
@@ -2119,61 +2105,6 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateSelectionPopover = () => {
|
|
||||||
const el = scroll
|
|
||||||
if (!el) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sel = selection()
|
|
||||||
if (!sel) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = el.querySelector("diffs-container")
|
|
||||||
if (!(host instanceof HTMLElement)) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = host.shadowRoot
|
|
||||||
if (!root) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const marker =
|
|
||||||
(root.querySelector(
|
|
||||||
'[data-selected-line="last"], [data-selected-line="single"]',
|
|
||||||
) as HTMLElement | null) ?? (root.querySelector("[data-selected-line]") as HTMLElement | null)
|
|
||||||
|
|
||||||
if (!marker) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerRect = el.getBoundingClientRect()
|
|
||||||
const markerRect = marker.getBoundingClientRect()
|
|
||||||
setSelectionPopoverTop(markerRect.bottom - containerRect.top + el.scrollTop + 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
selection,
|
|
||||||
(sel) => {
|
|
||||||
if (!sel) {
|
|
||||||
setSelectionPopoverTop(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(updateSelectionPopover)
|
|
||||||
},
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const getCodeScroll = () => {
|
const getCodeScroll = () => {
|
||||||
const el = scroll
|
const el = scroll
|
||||||
if (!el) return []
|
if (!el) return []
|
||||||
@@ -2312,41 +2243,9 @@ export default function Page() {
|
|||||||
ref={(el: HTMLDivElement) => {
|
ref={(el: HTMLDivElement) => {
|
||||||
scroll = el
|
scroll = el
|
||||||
restoreScroll()
|
restoreScroll()
|
||||||
updateSelectionPopover()
|
|
||||||
}}
|
}}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<Show when={activeTab() === tab}>
|
|
||||||
<Show when={selectionPopoverTop() !== undefined && selection()}>
|
|
||||||
{(sel) => (
|
|
||||||
<div class="absolute z-20 right-6" style={{ top: `${selectionPopoverTop() ?? 0}px` }}>
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="bottom"
|
|
||||||
title="Add selection to context"
|
|
||||||
keybind={command.keybind("context.addSelection")}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="group relative flex items-center gap-2 h-6 px-2.5 rounded-md bg-surface-raised-stronger-non-alpha border border-border-weak-base text-12-medium text-text-strong shadow-xs-border whitespace-nowrap hover:bg-surface-raised-stronger-hover hover:border-border-hover focus:outline-none focus-visible:shadow-xs-border-focus"
|
|
||||||
onClick={() => {
|
|
||||||
const p = path()
|
|
||||||
if (!p) return
|
|
||||||
addSelectionToContext(p, sel())
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span class="pointer-events-none absolute -left-1 top-1/2 size-2.5 -translate-y-1/2 rotate-45 bg-surface-raised-stronger-non-alpha border-l border-b border-border-weak-base group-hover:bg-surface-raised-stronger-hover group-hover:border-border-hover" />
|
|
||||||
<Icon name="plus-small" size="small" />
|
|
||||||
<span>
|
|
||||||
{language.t("session.context.addToContext", {
|
|
||||||
selection: selectionLabel() ?? "",
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={state()?.loaded && isImage()}>
|
<Match when={state()?.loaded && isImage()}>
|
||||||
<div class="px-6 py-4 pb-40">
|
<div class="px-6 py-4 pb-40">
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: calc(100% + 12px);
|
right: calc(100% + 12px);
|
||||||
z-index: 40;
|
z-index: 6;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-width: min(320px, calc(100vw - 48px));
|
max-width: min(320px, calc(100vw - 48px));
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
[data-slot="session-review-comment-anchor"] {
|
[data-slot="session-review-comment-anchor"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
z-index: 30;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="session-review-comment-button"] {
|
[data-slot="session-review-comment-button"] {
|
||||||
|
|||||||
Reference in New Issue
Block a user